solana-dex-parser
Version:
Solana Dex Transaction Parser
140 lines (118 loc) • 5.07 kB
text/typescript
import { deserializeUnchecked } from 'borsh';
import { DEX_PROGRAMS, DISCRIMINATORS } from '../../constants';
import { convertToUiAmount, JupiterSwapEventData, JupiterSwapInfo, TradeInfo } from '../../types';
import { getInstructionData, getProgramName, getTradeType } from '../../utils';
import { BaseParser } from '../base-parser';
import { JupiterLayout } from './layout';
export class JupiterParser extends BaseParser {
public processTrades(): TradeInfo[] {
const trades: TradeInfo[] = [];
this.classifiedInstructions.forEach(({ instruction, programId, outerIndex, innerIndex }) => {
if (this.isJupiterRouteEventInstruction(instruction, programId)) {
const event = this.parseJupiterRouteEventInstruction(instruction, `${outerIndex}-${innerIndex ?? 0}`);
if (event) {
const data = this.processSwapData([event]);
if (data) {
trades.push(data);
}
}
}
});
return trades;
}
private isJupiterRouteEventInstruction(instruction: any, programId: string): boolean {
if (programId !== DEX_PROGRAMS.JUPITER.id) return false;
const data = getInstructionData(instruction);
if (!data || data.length < 16) return false;
return Buffer.from(data.slice(0, 16)).equals(Buffer.from(DISCRIMINATORS.JUPITER.ROUTE_EVENT));
}
private parseJupiterRouteEventInstruction(instruction: any, idx: string): JupiterSwapEventData | null {
try {
const data = getInstructionData(instruction);
if (!data || data.length < 16) return null;
const eventData = data.slice(16);
const layout = deserializeUnchecked(JupiterLayout.schema, JupiterLayout, Buffer.from(eventData));
const swapEvent = layout.toSwapEvent();
return {
...swapEvent,
inputMintDecimals: this.adapter.getTokenDecimals(swapEvent.inputMint.toBase58()) || 0,
outputMintDecimals: this.adapter.getTokenDecimals(swapEvent.outputMint.toBase58()) || 0,
idx,
};
} catch (error) {
console.error('Parse Jupiter route event error:', error);
return null;
}
}
private processSwapData(events: JupiterSwapEventData[]): TradeInfo | null {
if (events.length === 0) return null;
const intermediateInfo = this.buildIntermediateInfo(events);
return this.convertToTradeInfo(intermediateInfo);
}
private buildIntermediateInfo(events: JupiterSwapEventData[]): JupiterSwapInfo {
const info: JupiterSwapInfo = {
amms: [],
tokenIn: new Map<string, bigint>(),
tokenOut: new Map<string, bigint>(),
decimals: new Map<string, number>(),
idx: '',
};
for (const event of events) {
const inputMint = event.inputMint.toBase58();
const outputMint = event.outputMint.toBase58();
info.tokenIn.set(inputMint, BigInt((info.tokenIn.get(inputMint) || BigInt(0)) + event.inputAmount));
info.tokenOut.set(outputMint, BigInt((info.tokenOut.get(outputMint) || BigInt(0)) + event.outputAmount));
info.decimals.set(inputMint, event.inputMintDecimals);
info.decimals.set(outputMint, event.outputMintDecimals);
info.idx = event.idx;
info.amms.push(getProgramName(event.amm.toBase58()));
}
this.removeIntermediateTokens(info);
return info;
}
private removeIntermediateTokens(info: JupiterSwapInfo): void {
for (const [mint, inAmount] of info.tokenIn.entries()) {
const outAmount = info.tokenOut.get(mint);
if (outAmount && inAmount === outAmount) {
info.tokenIn.delete(mint);
info.tokenOut.delete(mint);
}
}
}
private convertToTradeInfo(info: JupiterSwapInfo): TradeInfo | null {
if (info.tokenIn.size !== 1 || info.tokenOut.size !== 1) return null;
const [[inMint, inAmount]] = Array.from(info.tokenIn.entries());
const [[outMint, outAmount]] = Array.from(info.tokenOut.entries());
const inDecimals = info.decimals.get(inMint) || 0;
const outDecimals = info.decimals.get(outMint) || 0;
const signerIndex = this.containsDCAProgram() ? 2 : 0;
const signer = this.adapter.getAccountKey(signerIndex);
const trade = {
type: getTradeType(inMint, outMint),
inputToken: {
mint: inMint,
amount: convertToUiAmount(inAmount, inDecimals),
amountRaw: inAmount.toString(),
decimals: inDecimals,
},
outputToken: {
mint: outMint,
amount: convertToUiAmount(outAmount, outDecimals),
amountRaw: outAmount.toString(),
decimals: outDecimals,
},
user: signer,
programId: this.dexInfo.programId,
amm: info.amms?.[0] || this.dexInfo.amm || '',
route: this.dexInfo.route || '',
slot: this.adapter.slot,
timestamp: this.adapter.blockTime,
signature: this.adapter.signature,
idx: info.idx,
} as TradeInfo;
return this.utils.attachTokenTransferInfo(trade, this.transferActions);
}
private containsDCAProgram(): boolean {
return this.adapter.accountKeys.some((key) => key === DEX_PROGRAMS.JUPITER_DCA.id);
}
}