UNPKG

solana-dex-parser

Version:

Solana Dex Transaction Parser

397 lines 16.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TransactionUtils = void 0; const constants_1 = require("./constants"); const transfer_compiled_utils_1 = require("./transfer-compiled-utils"); const transfer_utils_1 = require("./transfer-utils"); const utils_1 = require("./utils"); class TransactionUtils { constructor(adapter) { this.adapter = adapter; /** * Sort and get LP tokens * make sure token0 is SPL Token, token1 is SOL/USDC/USDT * SOL,USDT > buy * SOL,DDD > buy * USDC,USDT/DDD > buy * USDT,USDC * DDD,USDC > sell * USDC,SOL > sell * USDT,SOL > sell * @param transfers * @returns */ this.getLPTransfers = (transfers) => { const tokens = transfers.filter((it) => it.type.includes('transfer')); if (tokens.length >= 2) { if (tokens[0].info.mint == constants_1.TOKENS.SOL || (this.adapter.isSupportedToken(tokens[0].info.mint) && !this.adapter.isSupportedToken(tokens[1].info.mint))) { return [tokens[1], tokens[0]]; } } return tokens; }; this.attachTokenTransferInfo = (trade, transferActions) => { const inputTransfer = Object.values(transferActions) .flat() .find((it) => it.info.mint == trade.inputToken.mint && it.info.tokenAmount?.amount == trade.inputToken.amountRaw); const outputTransfer = Object.values(transferActions) .flat() .find((it) => it.info.mint == trade.outputToken.mint && it.info.tokenAmount?.amount == trade.outputToken.amountRaw); if (inputTransfer) { trade.inputToken.authority = inputTransfer.info.authority; trade.inputToken.source = inputTransfer.info.source; trade.inputToken.destination = inputTransfer.info.destination; trade.inputToken.destinationOwner = inputTransfer.info.destinationOwner; trade.inputToken.destinationBalance = inputTransfer.info.destinationBalance; trade.inputToken.destinationPreBalance = inputTransfer.info.destinationPreBalance; trade.inputToken.sourceBalance = inputTransfer.info.sourceBalance; trade.inputToken.sourcePreBalance = inputTransfer.info.sourcePreBalance; } if (outputTransfer) { trade.outputToken.authority = outputTransfer.info.authority; trade.outputToken.source = outputTransfer.info.source; trade.outputToken.destination = outputTransfer.info.destination; trade.outputToken.destinationOwner = outputTransfer.info.destinationOwner; trade.outputToken.destinationBalance = outputTransfer.info.destinationBalance; trade.outputToken.destinationPreBalance = outputTransfer.info.destinationPreBalance; trade.outputToken.sourceBalance = outputTransfer.info.sourceBalance; trade.outputToken.sourcePreBalance = outputTransfer.info.sourcePreBalance; } return trade; }; } /** * Get DEX information from transaction */ getDexInfo(classifier) { const programIds = classifier.getAllProgramIds(); if (!programIds.length) return {}; for (const programId of programIds) { const dexProgram = Object.values(constants_1.DEX_PROGRAMS).find((dex) => dex.id === programId); if (dexProgram) { const isRoute = !dexProgram.tags.includes('amm'); return { programId: dexProgram.id, route: isRoute ? dexProgram.name : undefined, amm: !isRoute ? dexProgram.name : undefined, }; } } return { programId: programIds[0] }; } /** * Get transfer actions from transaction */ getTransferActions(extraTypes) { const actions = {}; const innerInstructions = this.adapter.innerInstructions; let groupKey = ''; // process transfers of program instructions innerInstructions?.forEach((set) => { const outerIndex = set.index; const outerInstruction = this.adapter.instructions[outerIndex]; const outerProgramId = this.adapter.getInstructionProgramId(outerInstruction); if (constants_1.SYSTEM_PROGRAMS.includes(outerProgramId)) return; groupKey = `${outerProgramId}:${outerIndex}`; set.instructions.forEach((ix, innerIndex) => { const innerProgramId = this.adapter.getInstructionProgramId(ix); // Special case for meteora vault if (!constants_1.SYSTEM_PROGRAMS.includes(innerProgramId) && !this.isIgnoredProgram(innerProgramId)) { groupKey = `${innerProgramId}:${outerIndex}-${innerIndex}`; return; } const transferData = this.parseInstructionAction(ix, `${outerIndex}-${innerIndex}`, extraTypes); if (transferData) { if (actions[groupKey]) { actions[groupKey].push(transferData); } else { actions[groupKey] = [transferData]; } } }); }); // process transfers without program if (Object.keys(actions).length == 0) { groupKey = 'transfer'; this.adapter.instructions?.forEach((ix, outerIndex) => { const transferData = this.parseInstructionAction(ix, `${outerIndex}`, extraTypes); if (transferData) { if (actions[groupKey]) { actions[groupKey].push(transferData); } else { actions[groupKey] = [transferData]; } } }); } return actions; } processTransferInstructions(outerIndex, extraTypes) { const innerInstructions = this.adapter.innerInstructions; if (!innerInstructions) return []; return innerInstructions .filter((set) => set.index === outerIndex) .flatMap((set) => set.instructions .map((instruction, idx) => { const items = this.parseInstructionAction(instruction, `${outerIndex}-${idx}`, extraTypes); return items; }) .filter((transfer) => transfer !== null)); } /** * Parse instruction actions (both parsed and compiled) * actions: transfer/transferCheced/mintTo/burn */ parseInstructionAction(instruction, idx, extraTypes) { const ix = this.adapter.getInstruction(instruction); // Handle parsed instruction if (ix.parsed) { return this.parseParsedInstructionAction(ix, idx, extraTypes); } // Handle compiled instruction return this.parseCompiledInstructionAction(ix, idx, extraTypes); } /** * Parse parsed instruction */ parseParsedInstructionAction(instruction, idx, extraTypes) { if ((0, transfer_utils_1.isTransfer)(instruction)) { return (0, transfer_utils_1.processTransfer)(instruction, idx, this.adapter); } if ((0, transfer_utils_1.isTransferCheck)(instruction)) { return (0, transfer_utils_1.processTransferCheck)(instruction, idx, this.adapter); } if (extraTypes) { const actions = extraTypes .map((it) => { if ((0, transfer_utils_1.isExtraAction)(instruction, it)) { return (0, transfer_utils_1.processExtraAction)(instruction, idx, this.adapter, it); } }) .filter((it) => !!it); return actions.length > 0 ? actions[0] : null; } return null; } /** * Parse compiled instruction */ parseCompiledInstructionAction(instruction, idx, extraTypes) { if ((0, transfer_compiled_utils_1.isCompiledTransfer)(instruction)) { return (0, transfer_compiled_utils_1.processCompiledTransfer)(instruction, idx, this.adapter); } if ((0, transfer_compiled_utils_1.isCompiledNativeTransfer)(instruction)) { return (0, transfer_compiled_utils_1.processCompiledNatvieTransfer)(instruction, idx, this.adapter); } if ((0, transfer_compiled_utils_1.isCompiledTransferCheck)(instruction)) { return (0, transfer_compiled_utils_1.processCompiledTransferCheck)(instruction, idx, this.adapter); } if (extraTypes) { const actions = extraTypes .map((it) => { if ((0, transfer_compiled_utils_1.isCompiledExtraAction)(instruction, it)) { return (0, transfer_compiled_utils_1.processCompiledExtraAction)(instruction, idx, this.adapter, it); } }) .filter((it) => !!it); return actions.length > 0 ? actions[0] : null; } return null; } /** * Get mint from instruction */ getMintFromInstruction(ix, info) { let mint = this.adapter.splTokenMap.get(info.destination)?.mint; if (!mint) mint = this.adapter.splTokenMap.get(info.source)?.mint; if (!mint && ix.programId === constants_1.TOKENS.NATIVE) mint = constants_1.TOKENS.SOL; return mint; } /** * Get token amount from instruction info */ getTokenAmount(info, decimals) { if (info.tokenAmount) return info.tokenAmount; const amount = info.amount || info.lamports || '0'; return { amount, decimals, uiAmount: Number(amount) / Math.pow(10, decimals), }; } /** * Check if program should be ignored for grouping */ isIgnoredProgram(programId) { return Object.values(constants_1.DEX_PROGRAMS) .filter((it) => it.tags.includes('vault')) .map((it) => it.id) .includes(programId); } /** * Get transfer info from transfer data */ getTransferInfo(transferData, timestamp, signature) { const { info } = transferData; if (!info || !info.tokenAmount) return null; const tokenInfo = { mint: info.mint || '', amount: info.tokenAmount.uiAmount, amountRaw: info.tokenAmount.amount, decimals: info.tokenAmount.decimals, }; return { type: info.source === info.authority ? 'TRANSFER_OUT' : 'TRANSFER_IN', token: tokenInfo, from: info.source, to: info.destination, timestamp, signature, }; } /** * Get transfer info list from transfer data */ getTransferInfoList(transferDataList) { const timestamp = this.adapter.blockTime || 0; const signature = this.adapter.signature; return transferDataList .map((data) => this.getTransferInfo(data, timestamp, signature)) .filter((info) => info !== null); } /** * Process swap data from transfers */ processSwapData(transfers, dexInfo) { if (!transfers.length) { throw new Error('No swap data provided'); } const uniqueTokens = this.extractUniqueTokens(transfers); if (uniqueTokens.length < 2) { throw `Insufficient unique tokens for swap`; } const signer = this.getSwapSigner(); const { inputToken, outputToken } = this.calculateTokenAmounts(signer, transfers, uniqueTokens); return { type: (0, utils_1.getTradeType)(inputToken.mint, outputToken.mint), inputToken, outputToken, user: signer, programId: dexInfo.programId, amm: dexInfo.amm, route: dexInfo.route || '', slot: this.adapter.slot, timestamp: this.adapter.blockTime || 0, signature: this.adapter.signature, idx: transfers[0].idx, }; } /** * Get signer for swap transaction */ getSwapSigner() { const defaultSigner = this.adapter.accountKeys[0]; // Check for Jupiter DCA program const isDCAProgram = this.adapter.accountKeys.find((key) => key === constants_1.DEX_PROGRAMS.JUPITER_DCA.id); return isDCAProgram ? this.adapter.accountKeys[2] : defaultSigner; } /** * Extract unique tokens from transfers */ extractUniqueTokens(transfers) { const uniqueTokens = []; const seenTokens = new Set(); transfers.forEach((transfer) => { const tokenInfo = this.getTransferTokenInfo(transfer); if (tokenInfo && !seenTokens.has(tokenInfo.mint)) { uniqueTokens.push(tokenInfo); seenTokens.add(tokenInfo.mint); } }); return uniqueTokens; } /** * Calculate token amounts for swap */ calculateTokenAmounts(signer, transfers, uniqueTokens) { let inputToken = uniqueTokens[0]; let outputToken = uniqueTokens[uniqueTokens.length - 1]; if (outputToken.source === signer) { [inputToken, outputToken] = [outputToken, inputToken]; } const amounts = this.sumTokenAmounts(transfers, inputToken.mint, outputToken.mint); return { inputToken: { ...inputToken, amount: amounts.inputAmount, amountRaw: amounts.inputAmountRaw.toString(), }, outputToken: { ...outputToken, amount: amounts.outputAmount, amountRaw: amounts.outputAmountRaw.toString(), }, }; } /** * Sum token amounts from transfers */ sumTokenAmounts(transfers, inputMint, outputMint) { const seenTransfers = new Set(); let inputAmount = 0; let outputAmount = 0; let inputAmountRaw = 0n; let outputAmountRaw = 0n; transfers.forEach((transfer) => { const tokenInfo = this.getTransferTokenInfo(transfer); if (!tokenInfo) return; const key = `${tokenInfo.amount}-${tokenInfo.mint}`; if (seenTransfers.has(key)) return; seenTransfers.add(key); if (tokenInfo.mint === inputMint) { inputAmount += tokenInfo.amount; inputAmountRaw += BigInt(tokenInfo.amountRaw); } if (tokenInfo.mint === outputMint) { outputAmount += tokenInfo.amount; outputAmountRaw += BigInt(tokenInfo.amountRaw); } }); return { inputAmount, inputAmountRaw, outputAmount, outputAmountRaw }; } /** * Get token info from transfer data */ getTransferTokenInfo(transfer) { return transfer?.info ? { mint: transfer.info.mint, amount: transfer.info.tokenAmount.uiAmount, amountRaw: transfer.info.tokenAmount.amount, decimals: transfer.info.tokenAmount.decimals, authority: transfer.info.authority, destination: transfer.info.destination, destinationOwner: transfer.info.destinationOwner, destinationBalance: transfer.info.destinationBalance, destinationPreBalance: transfer.info.destinationPreBalance, source: transfer.info.source, sourceBalance: transfer.info.sourceBalance, sourcePreBalance: transfer.info.sourcePreBalance, } : null; } } exports.TransactionUtils = TransactionUtils; //# sourceMappingURL=transaction-utils.js.map