All files / src/parsers/jupiter parser-jupiter.ts

10.76% Statements 7/65
0% Branches 0/38
0% Functions 0/10
11.86% Lines 7/59

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 1401x 1x 1x 1x 1x 1x   1x                                                                                                                                                                                                                                                                        
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 }) => {
      Iif (this.isJupiterRouteEventInstruction(instruction, programId)) {
        const event = this.parseJupiterRouteEventInstruction(instruction, `${outerIndex}-${innerIndex ?? 0}`);
        Iif (event) {
          const data = this.processSwapData([event]);
          Iif (data) {
            trades.push(data);
          }
        }
      }
    });
 
    return trades;
  }
 
  private isJupiterRouteEventInstruction(instruction: any, programId: string): boolean {
    Iif (programId !== DEX_PROGRAMS.JUPITER.id) return false;
 
    const data = getInstructionData(instruction);
    Iif (!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);
      Iif (!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 {
    Iif (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);
      Iif (outAmount && inAmount === outAmount) {
        info.tokenIn.delete(mint);
        info.tokenOut.delete(mint);
      }
    }
  }
 
  private convertToTradeInfo(info: JupiterSwapInfo): TradeInfo | null {
    Iif (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);
  }
}