solana-dex-parser
Version:
Solana Dex Transaction Parser
234 lines • 12.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DexParser = void 0;
const constants_1 = require("./constants");
const instruction_classifier_1 = require("./instruction-classifier");
const parsers_1 = require("./parsers");
const parser_boopfun_1 = require("./parsers/boopfun/parser-boopfun");
const parser_jupiter_dca_1 = require("./parsers/jupiter/parser-jupiter-dca");
const transaction_adapter_1 = require("./transaction-adapter");
const transaction_utils_1 = require("./transaction-utils");
const utils_1 = require("./utils");
/**
* Main parser class for Solana DEX transactions
*/
class DexParser {
constructor() {
// Trade parser mapping
this.parserMap = {
[constants_1.DEX_PROGRAMS.JUPITER.id]: parsers_1.JupiterParser,
[constants_1.DEX_PROGRAMS.JUPITER_DCA.id]: parser_jupiter_dca_1.JupiterDcaParser,
[constants_1.DEX_PROGRAMS.JUPITER_VA.id]: parsers_1.JupiterVAParser,
[constants_1.DEX_PROGRAMS.JUPITER_LIMIT_ORDER_V2.id]: parsers_1.JupiterLimitOrderV2Parser,
[constants_1.DEX_PROGRAMS.MOONSHOT.id]: parsers_1.MoonshotParser,
[constants_1.DEX_PROGRAMS.METEORA.id]: parsers_1.MeteoraParser,
[constants_1.DEX_PROGRAMS.METEORA_POOLS.id]: parsers_1.MeteoraParser,
[constants_1.DEX_PROGRAMS.METEORA_DAMM.id]: parsers_1.MeteoraParser,
[constants_1.DEX_PROGRAMS.PUMP_FUN.id]: parsers_1.PumpfunParser,
[constants_1.DEX_PROGRAMS.PUMP_SWAP.id]: parsers_1.PumpswapParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_ROUTE.id]: parsers_1.RaydiumParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_CL.id]: parsers_1.RaydiumParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_CPMM.id]: parsers_1.RaydiumParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_V4.id]: parsers_1.RaydiumParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_LCP.id]: parsers_1.RaydiumLaunchpadParser,
[constants_1.DEX_PROGRAMS.ORCA.id]: parsers_1.OrcaParser,
[constants_1.DEX_PROGRAMS.BOOP_FUN.id]: parser_boopfun_1.BoopfunParser,
};
// Liquidity parser mapping
this.parseLiquidityMap = {
[constants_1.DEX_PROGRAMS.METEORA.id]: parsers_1.MeteoraDLMMPoolParser,
[constants_1.DEX_PROGRAMS.METEORA_POOLS.id]: parsers_1.MeteoraPoolsParser,
[constants_1.DEX_PROGRAMS.METEORA_DAMM.id]: parsers_1.MeteoraDAMMPoolParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_V4.id]: parsers_1.RaydiumV4PoolParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_CPMM.id]: parsers_1.RaydiumCPMMPoolParser,
[constants_1.DEX_PROGRAMS.RAYDIUM_CL.id]: parsers_1.RaydiumCLPoolParser,
[constants_1.DEX_PROGRAMS.ORCA.id]: parsers_1.OrcaLiquidityParser,
[constants_1.DEX_PROGRAMS.PUMP_FUN.id]: parsers_1.PumpswapLiquidityParser,
[constants_1.DEX_PROGRAMS.PUMP_SWAP.id]: parsers_1.PumpswapLiquidityParser,
};
// Transfer parser mapping
this.parseTransferMap = {
[constants_1.DEX_PROGRAMS.JUPITER_DCA.id]: parser_jupiter_dca_1.JupiterDcaParser,
[constants_1.DEX_PROGRAMS.JUPITER_VA.id]: parsers_1.JupiterVAParser,
[constants_1.DEX_PROGRAMS.JUPITER_LIMIT_ORDER.id]: parsers_1.JupiterLimitOrderParser,
[constants_1.DEX_PROGRAMS.JUPITER_LIMIT_ORDER_V2.id]: parsers_1.JupiterLimitOrderV2Parser,
};
}
/**
* Parse transaction with specific type
*/
parseWithClassifier(tx, config = { tryUnknowDEX: true, aggregateTrades: true }, parseType) {
const result = {
state: true,
fee: { amount: '0', uiAmount: 0, decimals: 9 },
trades: [],
liquidities: [],
transfers: [],
moreEvents: {},
context: {},
};
try {
const adapter = new transaction_adapter_1.TransactionAdapter(tx, config);
const utils = new transaction_utils_1.TransactionUtils(adapter);
const classifier = new instruction_classifier_1.InstructionClassifier(adapter);
result.context = {
utils,
adapter,
};
// Get DEX information and validate
const dexInfo = utils.getDexInfo(classifier);
const allProgramIds = classifier.getAllProgramIds();
if (config?.programIds && !config.programIds.some((id) => allProgramIds.includes(id))) {
result.state = false;
return result;
}
const transferActions = utils.getTransferActions(['mintTo', 'burn', 'mintToChecked', 'burnChecked']);
// Process fee
result.fee = adapter.fee;
// Process user balance change
result.solBalanceChange = adapter.getAccountSolBalanceChanges(false)?.get(adapter.signer);
result.tokenBalanceChange = adapter.getAccountTokenBalanceChanges(true)?.get(adapter.signer);
// Try specific parser first
if (dexInfo.programId &&
[
constants_1.DEX_PROGRAMS.JUPITER.id,
constants_1.DEX_PROGRAMS.JUPITER_DCA.id,
constants_1.DEX_PROGRAMS.JUPITER_DCA_KEEPER1.id,
constants_1.DEX_PROGRAMS.JUPITER_DCA_KEEPER2.id,
constants_1.DEX_PROGRAMS.JUPITER_DCA_KEEPER3.id,
constants_1.DEX_PROGRAMS.JUPITER_VA.id,
constants_1.DEX_PROGRAMS.JUPITER_LIMIT_ORDER_V2.id,
].includes(dexInfo.programId)) {
if (parseType === 'trades' || parseType === 'all') {
const jupiterInstructions = classifier.getInstructions(dexInfo.programId);
const TradeParserClass = this.parserMap[dexInfo.programId];
if (TradeParserClass) {
const parser = new TradeParserClass(adapter, { ...dexInfo, programId: dexInfo.programId, amm: (0, utils_1.getProgramName)(dexInfo.programId) }, transferActions, jupiterInstructions);
const trades = parser.processTrades();
if (trades.length > 0) {
if (config.aggregateTrades == true) {
result.trades.push(utils.attachTradeFee((0, utils_1.getFinalSwap)(trades)));
}
else {
result.trades.push(...trades);
}
}
}
}
if (result.trades.length > 0) {
return result;
}
}
// Process instructions for each program
for (const programId of allProgramIds) {
if (config?.programIds && !config.programIds.some((id) => id == programId))
continue;
if (config?.ignoreProgramIds && config.ignoreProgramIds.some((id) => id == programId))
continue;
const classifiedInstructions = classifier.getInstructions(programId);
// Process trades if needed
if (parseType === 'trades' || parseType === 'all') {
const TradeParserClass = this.parserMap[programId];
if (TradeParserClass) {
const parser = new TradeParserClass(adapter, { ...dexInfo, programId: programId, amm: (0, utils_1.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 && transfers.some((it) => adapter.isSupportedToken(it.info.mint))) {
const trade = utils.processSwapData(transfers, {
...dexInfo,
programId: programId,
amm: (0, utils_1.getProgramName)(programId),
});
if (trade)
result.trades.push(utils.attachTokenTransferInfo(trade, transferActions));
}
}
}
// Process liquidity if needed
if (parseType === 'liquidity' || parseType === 'all') {
const LiquidityParserClass = this.parseLiquidityMap[programId];
if (LiquidityParserClass) {
const parser = new LiquidityParserClass(adapter, transferActions, classifiedInstructions);
result.liquidities.push(...utils.attachUserBalanceToLPs(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 (dexInfo.programId) {
const classifiedInstructions = classifier.getInstructions(dexInfo.programId);
const TransferParserClass = this.parseTransferMap[dexInfo.programId];
if (TransferParserClass) {
const parser = new TransferParserClass(adapter, dexInfo, transferActions, classifiedInstructions);
result.transfers.push(...parser.processTransfers());
}
}
if (result.transfers.length == 0) {
result.transfers.push(...Object.values(transferActions).flat());
}
}
}
// Process more events if needed
this.processMoreEvents(parseType, result, allProgramIds, adapter, transferActions, classifier);
}
catch (error) {
if (config.throwError) {
throw error;
}
const msg = `Parse error: ${tx?.transaction?.signatures?.[0]} ${error}`;
result.state = false;
result.msg = msg;
}
return result;
}
processMoreEvents(parseType, result, allProgramIds, adapter, transferActions, classifier) {
if (parseType === 'all') {
if (allProgramIds.includes(constants_1.DEX_PROGRAMS.PUMP_FUN.id)) {
result.moreEvents[constants_1.DEX_PROGRAMS.PUMP_FUN.name] = new parsers_1.PumpfunEventParser(adapter).parseInstructions(classifier.getInstructions(constants_1.DEX_PROGRAMS.PUMP_FUN.id));
}
if (allProgramIds.includes(constants_1.DEX_PROGRAMS.PUMP_SWAP.id)) {
result.moreEvents[constants_1.DEX_PROGRAMS.PUMP_SWAP.name] = new parsers_1.PumpswapEventParser(adapter).parseInstructions(classifier.getInstructions(constants_1.DEX_PROGRAMS.PUMP_SWAP.id));
}
if (allProgramIds.includes(constants_1.DEX_PROGRAMS.BOOP_FUN.id)) {
result.moreEvents[constants_1.DEX_PROGRAMS.BOOP_FUN.name] = new parsers_1.BoopfunEventParser(adapter, transferActions).parseInstructions(classifier.getInstructions(constants_1.DEX_PROGRAMS.BOOP_FUN.id));
}
if (allProgramIds.includes(constants_1.DEX_PROGRAMS.RAYDIUM_LCP.id)) {
result.moreEvents[constants_1.DEX_PROGRAMS.RAYDIUM_LCP.name] = new parsers_1.RaydiumLaunchpadEventParser(adapter).parseInstructions(classifier.getInstructions(constants_1.DEX_PROGRAMS.RAYDIUM_LCP.id));
}
}
}
/**
* Parse trades from transaction
*/
parseTrades(tx, config) {
return this.parseWithClassifier(tx, config, 'trades').trades;
}
/**
* Parse liquidity events from transaction
*/
parseLiquidity(tx, config) {
return this.parseWithClassifier(tx, config, 'liquidity').liquidities;
}
/**
* Parse transfers from transaction (if no trades and no liquidity)
*/
parseTransfers(tx, config) {
return this.parseWithClassifier(tx, config, 'transfer').transfers;
}
/**
* Parse both trades and liquidity events from transaction
*/
parseAll(tx, config) {
return this.parseWithClassifier(tx, config, 'all');
}
}
exports.DexParser = DexParser;
//# sourceMappingURL=dex-parser.js.map