UNPKG

@xswap-link/sdk

Version:
183 lines (150 loc) 5.43 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 */ import { Event, Program, Provider } from "@coral-xyz/anchor"; import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; import { JUPITER_V6_PROGRAM_ID } from "@src/constants"; import { IDL, Jupiter } from "@src/contracts/idl/jupiter"; import { SwapEvent, TransactionWithMeta } from "@src/models/SolanaTransaction"; import { getEvents } from "@src/services/solana/jupiter/get-events"; import { InstructionParser } from "@src/services/solana/jupiter/instruction-parser"; import { BigNumberUtil } from "@src/services/solana/jupiter/utils"; import BigNumber from "bignumber.js"; import { unpackMint } from "@solana/spl-token"; import { Transaction } from "@src/models"; export const program = new Program<Jupiter>( IDL, JUPITER_V6_PROGRAM_ID, {} as Provider, ); type AccountInfoMap = Map<string, AccountInfo<Buffer>>; export type SwapAttributes = Transaction; const reduceEventData = <T>(events: Event[], name: string) => events.reduce((acc, event) => { if (event.name === name) { acc.push(event.data as T); } return acc; }, new Array<T>()); export async function extractSolanaSwapData( signature: string, connection: Connection, tx: TransactionWithMeta, ): Promise<SwapAttributes | undefined> { const programId = JUPITER_V6_PROGRAM_ID; const accountInfosMap: AccountInfoMap = new Map(); const logMessages = tx.meta?.logMessages; if (!logMessages) { throw new Error("Missing log messages..."); } const parser = new InstructionParser(programId); const events = getEvents(program, tx); const swapEvents = reduceEventData<SwapEvent>(events, "SwapEvent"); if (swapEvents.length === 0) { // Not a swap event, for example: https://solscan.io/tx/5ZSozCHmAFmANaqyjRj614zxQY8HDXKyfAs2aAVjZaadS4DbDwVq8cTbxmM5m5VzDcfhysTSqZgKGV1j2A2Hqz1V return; } const accountsToBeFetched = new Array<PublicKey>(); swapEvents.forEach((swapEvent) => { accountsToBeFetched.push(swapEvent.inputMint); accountsToBeFetched.push(swapEvent.outputMint); }); const accountInfos = await connection.getMultipleAccountsInfo( accountsToBeFetched, ); accountsToBeFetched.forEach((account, index) => { const accountInfo = accountInfos[index]; if (accountInfo) { accountInfosMap.set(account.toBase58(), accountInfo); } }); const swapData = await parseSwapEvents(accountInfosMap, swapEvents); const instructions = parser.getInstructions(tx); const positions = parser.getInitialAndFinalSwapPositions(instructions); if (!positions) { throw new Error("Failed to get initial and final swap positions"); } const [initialPositions, finalPositions] = positions as [number[], number[]]; const initialPosition = initialPositions[0] as number; const finalPosition = finalPositions[0] as number; const inMint = swapData[initialPosition]?.inMint as string; const inSwapData = swapData.filter( (swap, index) => initialPositions?.includes(index) && swap.inMint === inMint, ); const inAmount = inSwapData.reduce((acc, curr) => { return acc.plus(new BigNumber(curr.inAmount || "0")); }, new BigNumber(0)); const outMint = swapData[finalPosition]?.outMint; const outSwapData = swapData.filter( (swap, index) => finalPositions?.includes(index) && swap.outMint === outMint, ); const outAmount = outSwapData.reduce((acc, curr) => { return acc.plus(new BigNumber(curr.outAmount || "0")); }, new BigNumber(0)); const isSuccessful = tx.meta?.status?.Ok === null; const swap = {} as SwapAttributes; swap.hash = signature; swap.timestamp = tx.blockTime ?? 0; swap.amountWei = inAmount.toString(); swap.tokenAddress = inMint; swap.tokenOutAmount = outAmount.toString(); swap.tokenOutAddress = outMint; swap.sourceChainId = "mainnet-beta"; swap.targetChainId = "mainnet-beta"; swap.status = isSuccessful ? "DONE" : "REVERTED"; return swap; } async function parseSwapEvents( accountInfosMap: AccountInfoMap, swapEvents: SwapEvent[], ) { const swapData = await Promise.all( swapEvents.map((swapEvent) => extractSwapData(accountInfosMap, swapEvent)), ); return swapData; } async function extractSwapData( accountInfosMap: AccountInfoMap, swapEvent: SwapEvent, ) { const inMint = swapEvent.inputMint.toBase58(); const inAmount = swapEvent.inputAmount.toString(); const inTokenDecimals = extractMintDecimals( accountInfosMap, swapEvent.inputMint, ); const inAmountInDecimal = BigNumberUtil.fromBN( swapEvent.inputAmount, inTokenDecimals, ); const outMint = swapEvent.outputMint.toBase58(); const outAmount = swapEvent.outputAmount.toString(); const outTokenDecimals = extractMintDecimals( accountInfosMap, swapEvent.outputMint, ); const outAmountInDecimal = BigNumberUtil.fromBN( swapEvent.outputAmount, outTokenDecimals, ); return { inMint, inAmount, inAmountInDecimal, outMint, outAmount, outAmountInDecimal, }; } function extractMintDecimals(accountInfosMap: AccountInfoMap, mint: PublicKey) { const mintData = accountInfosMap.get(mint.toBase58()); if (mintData) { const mintInfo = unpackMint(mint, mintData, mintData.owner); return mintInfo.decimals; } return; }