UNPKG

@xswap-link/sdk

Version:
218 lines (182 loc) 6.36 kB
/** * This file contains code adapted from the Jupiter Protocol's instruction parser * Source: https://github.com/jup-ag/instruction-parser * Modified for use in XSwap */ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ParsedInstruction, PublicKey } from "@solana/web3.js"; import { BorshCoder } from "@coral-xyz/anchor"; import { IDL } from "@src/contracts/idl/jupiter"; import { PartialInstruction, RoutePlan, TransactionWithMeta, } from "@src/models/SolanaTransaction"; export class InstructionParser { private coder: BorshCoder; private programId: PublicKey; constructor(programId: PublicKey) { this.programId = programId; this.coder = new BorshCoder(IDL); } getInstructionNameAndTransferAuthorityAndLastAccount( instructions: PartialInstruction[], ) { for (const instruction of instructions) { if (!instruction.programId.equals(this.programId)) { continue; } const ix = this.coder.instruction.decode(instruction.data, "base58"); if (ix && instruction && this.isRouting(ix.name)) { const instructionName = ix.name; const authorityIndex = this.getTransferAuthorityIndex(instructionName); if (authorityIndex === undefined) { continue; } const transferAuthority = instruction.accounts[authorityIndex]?.toString() || ""; const lastAccount = instruction.accounts[instruction.accounts.length - 1]?.toString() || ""; return [ix.name, transferAuthority, lastAccount]; } } return []; } getTransferAuthorityIndex(instructionName: string) { switch (instructionName) { case "route": case "exactOutRoute": case "routeWithTokenLedger": return 1; case "sharedAccountsRoute": case "sharedAccountsRouteWithTokenLedger": case "sharedAccountsExactOutRoute": return 2; } } getInstructions(tx: TransactionWithMeta): PartialInstruction[] { const parsedInstructions: PartialInstruction[] = []; for (const instruction of tx.transaction.message.instructions) { if (instruction.programId.equals(this.programId)) { parsedInstructions.push(instruction as never); } } for (const instructions of tx.meta?.innerInstructions || []) { for (const instruction of instructions.instructions) { if (instruction.programId.equals(this.programId)) { parsedInstructions.push(instruction as any); } } } return parsedInstructions; } // Extract the position of the initial and final swap from the swap array. getInitialAndFinalSwapPositions(instructions: PartialInstruction[]) { for (const instruction of instructions) { if (!instruction.programId.equals(this.programId)) { continue; } const ix = this.coder.instruction.decode(instruction.data, "base58"); // This will happen because now event is also an CPI instruction. if (!ix) { continue; } if (this.isRouting(ix.name)) { const routePlan = (ix.data as any).routePlan as RoutePlan; const inputIndex = 0; const outputIndex = routePlan.length; const initialPositions: number[] = []; for (let j = 0; j < routePlan.length; j++) { if (routePlan[j].inputIndex === inputIndex) { initialPositions.push(j); } } const finalPositions: number[] = []; for (let j = 0; j < routePlan.length; j++) { if (routePlan[j].outputIndex === outputIndex) { finalPositions.push(j); } } if ( finalPositions.length === 0 && this.isCircular((ix.data as any).routePlan) ) { for (let j = 0; j < (ix.data as any).routePlan.length; j++) { if ((ix.data as any).routePlan[j].outputIndex === 0) { finalPositions.push(j); } } } return [initialPositions, finalPositions]; } } } getExactOutAmount(instructions: (ParsedInstruction | PartialInstruction)[]) { for (const instruction of instructions) { if (!instruction.programId.equals(this.programId)) { continue; } if (!("data" in instruction)) continue; // Guard in case it is a parsed decoded instruction, should be impossible const ix = this.coder.instruction.decode(instruction.data, "base58"); if (ix && this.isExactIn(ix.name)) { return (ix.data as any).quotedOutAmount.toString(); } } return; } getExactInAmount(instructions: (ParsedInstruction | PartialInstruction)[]) { for (const instruction of instructions) { if (!instruction.programId.equals(this.programId)) { continue; } if (!("data" in instruction)) continue; // Guard in case it is a parsed decoded instruction, should be impossible const ix = this.coder.instruction.decode(instruction.data, "base58"); if (ix && this.isExactOut(ix.name)) { return (ix.data as any).quotedInAmount.toString(); } } return; } isExactIn(name: string) { return ( name === "route" || name === "routeWithTokenLedger" || name === "sharedAccountsRoute" || name === "sharedAccountsRouteWithTokenLedger" ); } isExactOut(name: string) { return name === "sharedAccountsExactOutRoute" || name === "exactOutRoute"; } isRouting(name: string) { return ( name === "route" || name === "routeWithTokenLedger" || name === "sharedAccountsRoute" || name === "sharedAccountsRouteWithTokenLedger" || name === "sharedAccountsExactOutRoute" || name === "exactOutRoute" ); } isCircular(routePlan: RoutePlan) { if (!routePlan || routePlan.length === 0) { return false; } const indexMap = new Map( routePlan.map((obj) => [obj.inputIndex, obj.outputIndex]), ); const visited = new Set(); let currentIndex = routePlan[0].inputIndex; while (!visited.has(currentIndex)) { if (visited.has(currentIndex)) { return currentIndex === routePlan[0].inputIndex; } visited.add(currentIndex); if (!indexMap.has(currentIndex)) { return false; } currentIndex = indexMap.get(currentIndex); } } }