UNPKG

solana-dex-parser

Version:

Solana Dex Transaction Parser

221 lines (201 loc) 7.72 kB
import { DEX_PROGRAM_IDS, DEX_PROGRAMS } from './constants'; import { InstructionClassifier } from './instruction-classifier'; import { JupiterParser, MeteoraDLMMPoolParser, MeteoraParser, MeteoraPoolsParser, MoonshotParser, OrcaLiquidityParser, OrcaParser, PumpfunParser, PumpswapLiquidityParser, PumpswapParser, RaydiumCLPoolParser, RaydiumCPMMPoolParser, RaydiumParser, RaydiumV4PoolParser, RaydiumLaunchpadParser, } from './parsers'; import { TransactionAdapter } from './transaction-adapter'; import { TransactionUtils } from './transaction-utils'; import { ClassifiedInstruction, DexInfo, ParseConfig, ParseResult, PoolEvent, SolanaTransaction, TradeInfo, TransferData, } from './types'; import { getProgramName } from './utils'; /** * Interface for DEX trade parsers */ type ParserConstructor = new ( adapter: TransactionAdapter, dexInfo: DexInfo, transferActions: Record<string, TransferData[]>, classifiedInstructions: ClassifiedInstruction[] ) => { processTrades(): TradeInfo[]; }; /** * Interface for liquidity pool parsers */ type ParserLiquidityConstructor = new ( adapter: TransactionAdapter, transferActions: Record<string, TransferData[]>, classifiedInstructions: ClassifiedInstruction[] ) => { processLiquidity(): PoolEvent[]; }; /** * Main parser class for Solana DEX transactions */ export class DexParser { // Trade parser mapping private readonly parserMap: Record<string, ParserConstructor> = { [DEX_PROGRAMS.JUPITER.id]: JupiterParser, [DEX_PROGRAMS.JUPITER_DCA.id]: JupiterParser, [DEX_PROGRAMS.MOONSHOT.id]: MoonshotParser, [DEX_PROGRAMS.METEORA.id]: MeteoraParser, [DEX_PROGRAMS.METEORA_POOLS.id]: MeteoraParser, [DEX_PROGRAMS.PUMP_FUN.id]: PumpfunParser, [DEX_PROGRAMS.PUMP_SWAP.id]: PumpswapParser, [DEX_PROGRAMS.RAYDIUM_ROUTE.id]: RaydiumParser, [DEX_PROGRAMS.RAYDIUM_CL.id]: RaydiumParser, [DEX_PROGRAMS.RAYDIUM_CPMM.id]: RaydiumParser, [DEX_PROGRAMS.RAYDIUM_V4.id]: RaydiumParser, [DEX_PROGRAMS.RAYDIUM_LCP.id]: RaydiumLaunchpadParser, [DEX_PROGRAMS.ORCA.id]: OrcaParser, }; // Liquidity parser mapping private readonly parseLiquidityMap: Record<string, ParserLiquidityConstructor> = { [DEX_PROGRAMS.METEORA.id]: MeteoraDLMMPoolParser, [DEX_PROGRAMS.METEORA_POOLS.id]: MeteoraPoolsParser, [DEX_PROGRAMS.RAYDIUM_V4.id]: RaydiumV4PoolParser, [DEX_PROGRAMS.RAYDIUM_CPMM.id]: RaydiumCPMMPoolParser, [DEX_PROGRAMS.RAYDIUM_CL.id]: RaydiumCLPoolParser, [DEX_PROGRAMS.ORCA.id]: OrcaLiquidityParser, [DEX_PROGRAMS.PUMP_FUN.id]: PumpswapLiquidityParser, [DEX_PROGRAMS.PUMP_SWAP.id]: PumpswapLiquidityParser, }; constructor() {} /** * Parse transaction with specific type */ private parseWithClassifier( tx: SolanaTransaction, config: ParseConfig = { tryUnknowDEX: true }, parseType: 'trades' | 'liquidity' | 'transfer' | 'all' ): ParseResult { const result: ParseResult = { state: true, trades: [], liquidities: [], transfers: [], }; try { const adapter = new TransactionAdapter(tx, config); const utils = new TransactionUtils(adapter); const classifier = new InstructionClassifier(adapter); // Get DEX information and validate const dexInfo = utils.getDexInfo(classifier); const allProgramIds = classifier.getAllProgramIds(); const transferActions = utils.getTransferActions(['mintTo', 'burn', 'mintToChecked', 'burnChecked']); // Try specific parser first if (dexInfo.programId && [DEX_PROGRAMS.JUPITER.id, DEX_PROGRAMS.JUPITER_DCA.id].includes(dexInfo.programId)) { if (parseType === 'trades' || parseType === 'all') { const jupiterInstructions = classifier.getInstructions(dexInfo.programId); const parser = new JupiterParser(adapter, dexInfo, transferActions, jupiterInstructions); result.trades.push(...parser.processTrades()); } return result; } // Process instructions for each program for (const programId of allProgramIds) { const classifiedInstructions = classifier.getInstructions(programId); // Process trades if needed if (parseType === 'trades' || parseType === 'all') { if (config?.programIds && !config.programIds.some((id) => id == programId)) continue; if (config?.ignoreProgramIds && config.ignoreProgramIds.some((id) => id == programId)) continue; const TradeParserClass = this.parserMap[programId]; if (TradeParserClass) { const parser = new TradeParserClass( adapter, { ...dexInfo, programId: programId, amm: getProgramName(programId) }, transferActions, classifiedInstructions ); result.trades.push(...parser.processTrades()); } else if (config?.tryUnknowDEX) { // Handle unknown DEX programs const transfers = Object.entries(transferActions).find(([key]) => key.startsWith(programId))?.[1]; if (transfers && transfers.length >= 2) { const trade = utils.processSwapData(transfers, { ...dexInfo, programId: programId, amm: getProgramName(programId), }); if (trade) result.trades.push(trade); } } } // Process liquidity if needed if (parseType === 'liquidity' || parseType === 'all') { if (config?.programIds && !config.programIds.some((id) => id == programId)) continue; if (config?.ignoreProgramIds && config.ignoreProgramIds.some((id) => id == programId)) continue; const LiquidityParserClass = this.parseLiquidityMap[programId]; if (LiquidityParserClass) { const parser = new LiquidityParserClass(adapter, transferActions, classifiedInstructions); result.liquidities.push(...parser.processLiquidity()); } } } // Deduplicate trades if (result.trades.length > 0) { result.trades = [...new Map(result.trades.map((item) => [`${item.idx}-${item.signature}`, item])).values()]; } // Process transfer if needed (if no trades and no liquidity) if (result.trades.length == 0 && result.liquidities.length == 0) { if (parseType === 'transfer' || parseType === 'all') { if (!allProgramIds.some((id) => DEX_PROGRAM_IDS.includes(id))) { result.transfers.push(...Object.values(transferActions).flat()); } } } } catch (error) { const msg = `Parse error: ${tx?.transaction?.signatures?.[0]} ${error}`; console.error(error); result.state = false; result.msg = msg; } return result; } /** * Parse trades from transaction */ public parseTrades(tx: SolanaTransaction, config?: ParseConfig): TradeInfo[] { return this.parseWithClassifier(tx, config, 'trades').trades; } /** * Parse liquidity events from transaction */ public parseLiquidity(tx: SolanaTransaction, config?: ParseConfig): PoolEvent[] { return this.parseWithClassifier(tx, config, 'liquidity').liquidities; } /** * Parse transfers from transaction (if no trades and no liquidity) */ public parseTransfers(tx: SolanaTransaction, config?: ParseConfig): TransferData[] { return this.parseWithClassifier(tx, config, 'transfer').transfers; } /** * Parse both trades and liquidity events from transaction */ public parseAll(tx: SolanaTransaction, config?: ParseConfig): ParseResult { return this.parseWithClassifier(tx, config, 'all'); } }