UNPKG

solana-dex-parser

Version:

Solana Dex Transaction Parser

140 lines (127 loc) 5.26 kB
import { deserializeUnchecked } from 'borsh'; import { Buffer } from 'buffer'; import { DEX_PROGRAMS, DISCRIMINATORS } from '../../constants'; import { InstructionClassifier } from '../../instruction-classifier'; import { TransactionAdapter } from '../../transaction-adapter'; import { ClassifiedInstruction, EventsParser, RaydiumLCPCompleteEvent, RaydiumLCPCreateEvent, RaydiumLCPEvent, RaydiumLCPTradeEvent, } from '../../types'; import { getInstructionData } from '../../utils'; import { PoolCreateEventLayout } from './layouts/raydium-lcp-create.layout'; import { RaydiumLCPTradeLayout } from './layouts/raydium-lcp-trade.layout'; export class RaydiumLaunchpadEventParser { constructor(private readonly adapter: TransactionAdapter) {} private readonly EventsParsers: Record<string, EventsParser<any>> = { CREATE: { discriminators: [DISCRIMINATORS.RAYDIUM_LCP.CREATE_EVENT], slice: 16, decode: this.decodeCreateEvent.bind(this), }, TRADE: { discriminators: [ DISCRIMINATORS.RAYDIUM_LCP.BUY_EXACT_IN, DISCRIMINATORS.RAYDIUM_LCP.BUY_EXACT_OUT, DISCRIMINATORS.RAYDIUM_LCP.SELL_EXACT_IN, DISCRIMINATORS.RAYDIUM_LCP.SELL_EXACT_OUT, ], slice: 8, decode: this.decodeTradeInstruction.bind(this), }, COMPLETE: { discriminators: [DISCRIMINATORS.RAYDIUM_LCP.MIGRATE_TO_AMM, DISCRIMINATORS.RAYDIUM_LCP.MIGRATE_TO_CPSWAP], slice: 8, decode: this.decodeCompleteInstruction.bind(this), }, }; public processEvents(): RaydiumLCPEvent[] { const instructions = new InstructionClassifier(this.adapter).getInstructions(DEX_PROGRAMS.RAYDIUM_LCP.id); return this.parseInstructions(instructions); } public parseInstructions(instructions: ClassifiedInstruction[]): RaydiumLCPEvent[] { return instructions .map(({ instruction, outerIndex, innerIndex }) => { try { const data = getInstructionData(instruction); for (const [type, parser] of Object.entries(this.EventsParsers)) { const discriminator = Buffer.from(data.slice(0, parser.slice)); if (parser.discriminators.some((it) => discriminator.equals(it))) { const options = { instruction, outerIndex, innerIndex, }; const eventData = parser.decode(data, options); if (!eventData) return null; return { type: type as 'TRADE' | 'CREATE' | 'COMPLETE', data: eventData, slot: this.adapter.slot, timestamp: this.adapter.blockTime || 0, signature: this.adapter.signature, idx: `${outerIndex}-${innerIndex ?? 0}`, }; } } } catch (error) { console.error('Failed to parse RaydiumLCP event:', error); } return null; }) .filter((event): event is RaydiumLCPEvent => event !== null); } private decodeTradeInstruction(data: Buffer, options: any): RaydiumLCPTradeEvent { const eventInstruction = this.adapter.getInnerInstruction( options.outerIndex, options.innerIndex ? options.innerIndex + 1 : 0 ); // find inner instruction if (!eventInstruction) { throw new Error('Event instruction not found'); } // get event data from inner instruction const eventData = getInstructionData(eventInstruction).slice(16); const layout = deserializeUnchecked(RaydiumLCPTradeLayout.schema, RaydiumLCPTradeLayout, Buffer.from(eventData)); const event = layout.toObject(); // get instruction accounts const accounts = this.adapter.getInstructionAccounts(options.instruction); event.user = accounts[0]; event.baseMint = accounts[9]; event.quoteMint = accounts[10]; return event as RaydiumLCPTradeEvent; } private decodeCreateEvent(data: Buffer, options: any): RaydiumLCPCreateEvent { const eventInstruction = this.adapter.instructions[options.outerIndex]; // find outer instruction if (!eventInstruction) { throw new Error('Event instruction not found'); } // parse event data const eventData = data.slice(16); const event = PoolCreateEventLayout.deserialize(eventData).toObject(); // get instruction accounts const accounts = this.adapter.getInstructionAccounts(eventInstruction); event.baseMint = accounts[6]; event.quoteMint = accounts[7]; return event as RaydiumLCPCreateEvent; } private decodeCompleteInstruction(data: Buffer, options: any): RaydiumLCPCompleteEvent { const discriminator = Buffer.from(data.slice(0, 8)); const accounts = this.adapter.getInstructionAccounts(options.instruction); const [baseMint, quoteMint, poolMint, lpMint] = discriminator.equals(DISCRIMINATORS.RAYDIUM_LCP.MIGRATE_TO_AMM) ? [accounts[1], accounts[2], accounts[13], accounts[16]] : [accounts[1], accounts[2], accounts[5], accounts[7]]; const amm = discriminator.equals(DISCRIMINATORS.RAYDIUM_LCP.MIGRATE_TO_AMM) ? DEX_PROGRAMS.RAYDIUM_V4.name : DEX_PROGRAMS.RAYDIUM_CPMM.name; return { baseMint, quoteMint, poolMint, lpMint, amm, }; } }