solana-dex-parser
Version:
Solana Dex Transaction Parser
140 lines (127 loc) • 5.26 kB
text/typescript
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,
};
}
}