@xswap-link/sdk
Version:
JavaScript SDK for XSwap platform
183 lines (150 loc) • 5.43 kB
text/typescript
/**
* 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;
}