UNPKG

@drift-labs/sdk

Version:
448 lines (422 loc) • 9.08 kB
import { AddressLookupTableAccount, Connection, PublicKey, TransactionInstruction, TransactionMessage, VersionedTransaction, } from '@solana/web3.js'; import fetch from 'node-fetch'; import { BN } from '@coral-xyz/anchor'; export type SwapMode = 'ExactIn' | 'ExactOut'; export interface MarketInfo { id: string; inAmount: number; inputMint: string; label: string; lpFee: Fee; notEnoughLiquidity: boolean; outAmount: number; outputMint: string; platformFee: Fee; priceImpactPct: number; } export interface Fee { amount: number; mint: string; pct: number; } export interface Route { amount: number; inAmount: number; marketInfos: MarketInfo[]; otherAmountThreshold: number; outAmount: number; priceImpactPct: number; slippageBps: number; swapMode: SwapMode; } /** * * @export * @interface RoutePlanStep */ export interface RoutePlanStep { /** * * @type {SwapInfo} * @memberof RoutePlanStep */ swapInfo: SwapInfo; /** * * @type {number} * @memberof RoutePlanStep */ percent: number; } export interface SwapInfo { /** * * @type {string} * @memberof SwapInfo */ ammKey: string; /** * * @type {string} * @memberof SwapInfo */ label?: string; /** * * @type {string} * @memberof SwapInfo */ inputMint: string; /** * * @type {string} * @memberof SwapInfo */ outputMint: string; /** * * @type {string} * @memberof SwapInfo */ inAmount: string; /** * * @type {string} * @memberof SwapInfo */ outAmount: string; /** * * @type {string} * @memberof SwapInfo */ feeAmount: string; /** * * @type {string} * @memberof SwapInfo */ feeMint: string; } /** * * @export * @interface PlatformFee */ export interface PlatformFee { /** * * @type {string} * @memberof PlatformFee */ amount?: string; /** * * @type {number} * @memberof PlatformFee */ feeBps?: number; } /** * * @export * @interface QuoteResponse */ export interface QuoteResponse { /** * * @type {string} * @memberof QuoteResponse */ inputMint: string; /** * * @type {string} * @memberof QuoteResponse */ inAmount: string; /** * * @type {string} * @memberof QuoteResponse */ outputMint: string; /** * * @type {string} * @memberof QuoteResponse */ outAmount: string; /** * * @type {string} * @memberof QuoteResponse */ otherAmountThreshold: string; /** * * @type {SwapMode} * @memberof QuoteResponse */ swapMode: SwapMode; /** * * @type {number} * @memberof QuoteResponse */ slippageBps: number; /** * * @type {PlatformFee} * @memberof QuoteResponse */ platformFee?: PlatformFee; /** * * @type {string} * @memberof QuoteResponse */ priceImpactPct: string; /** * * @type {Array<RoutePlanStep>} * @memberof QuoteResponse */ routePlan: Array<RoutePlanStep>; /** * * @type {number} * @memberof QuoteResponse */ contextSlot?: number; /** * * @type {number} * @memberof QuoteResponse */ timeTaken?: number; /** * * @type {string} * @memberof QuoteResponse */ error?: string; /** * * @type {string} * @memberof QuoteResponse */ errorCode?: string; } export const RECOMMENDED_JUPITER_API_VERSION = '/v1'; export const RECOMMENDED_JUPITER_API = 'https://lite-api.jup.ag/swap'; export class JupiterClient { url: string; connection: Connection; lookupTableCahce = new Map<string, AddressLookupTableAccount>(); constructor({ connection, url }: { connection: Connection; url?: string }) { this.connection = connection; this.url = url ?? RECOMMENDED_JUPITER_API; } /** * Get routes for a swap * @param inputMint the mint of the input token * @param outputMint the mint of the output token * @param amount the amount of the input token * @param slippageBps the slippage tolerance in basis points * @param swapMode the swap mode (ExactIn or ExactOut) * @param onlyDirectRoutes whether to only return direct routes */ public async getQuote({ inputMint, outputMint, amount, maxAccounts = 50, // 50 is an estimated amount with buffer slippageBps = 50, swapMode = 'ExactIn', onlyDirectRoutes = false, excludeDexes, autoSlippage = false, maxAutoSlippageBps, usdEstimate, }: { inputMint: PublicKey; outputMint: PublicKey; amount: BN; maxAccounts?: number; slippageBps?: number; swapMode?: SwapMode; onlyDirectRoutes?: boolean; excludeDexes?: string[]; autoSlippage?: boolean; maxAutoSlippageBps?: number; usdEstimate?: number; }): Promise<QuoteResponse> { const params = new URLSearchParams({ inputMint: inputMint.toString(), outputMint: outputMint.toString(), amount: amount.toString(), slippageBps: autoSlippage ? '0' : slippageBps.toString(), swapMode, onlyDirectRoutes: onlyDirectRoutes.toString(), maxAccounts: maxAccounts.toString(), autoSlippage: autoSlippage.toString(), maxAutoSlippageBps: autoSlippage ? maxAutoSlippageBps.toString() : '0', autoSlippageCollisionUsdValue: autoSlippage ? usdEstimate.toString() : '0', ...(excludeDexes && { excludeDexes: excludeDexes.join(',') }), }); if (swapMode === 'ExactOut') { params.delete('maxAccounts'); } const apiVersionParam = this.url === RECOMMENDED_JUPITER_API ? RECOMMENDED_JUPITER_API_VERSION : ''; const quote = await ( await fetch(`${this.url}${apiVersionParam}/quote?${params.toString()}`) ).json(); return quote as QuoteResponse; } /** * Get a swap transaction for quote * @param quoteResponse quote to perform swap * @param userPublicKey the signer's wallet public key * @param slippageBps the slippage tolerance in basis points */ public async getSwap({ quote, userPublicKey, slippageBps = 50, }: { quote: QuoteResponse; userPublicKey: PublicKey; slippageBps?: number; }): Promise<VersionedTransaction> { if (!quote) { throw new Error('Jupiter swap quote not provided. Please try again.'); } const apiVersionParam = this.url === RECOMMENDED_JUPITER_API ? RECOMMENDED_JUPITER_API_VERSION : ''; const resp = await ( await fetch(`${this.url}${apiVersionParam}/swap`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ quoteResponse: quote, userPublicKey, slippageBps, }), }) ).json(); if (!('swapTransaction' in resp)) { throw new Error( `swapTransaction not found, error from Jupiter: ${resp.error} ${ ', ' + (resp.message ?? '') }` ); } const { swapTransaction } = resp; try { const swapTransactionBuf = Buffer.from(swapTransaction, 'base64'); return VersionedTransaction.deserialize(swapTransactionBuf); } catch (err) { throw new Error( 'Something went wrong with creating the Jupiter swap transaction. Please try again.' ); } } /** * Get the transaction message and lookup tables for a transaction * @param transaction */ public async getTransactionMessageAndLookupTables({ transaction, }: { transaction: VersionedTransaction; }): Promise<{ transactionMessage: TransactionMessage; lookupTables: AddressLookupTableAccount[]; }> { const message = transaction.message; const lookupTables = ( await Promise.all( message.addressTableLookups.map(async (lookup) => { return await this.getLookupTable(lookup.accountKey); }) ) ).filter((lookup) => lookup); const transactionMessage = TransactionMessage.decompile(message, { addressLookupTableAccounts: lookupTables, }); return { transactionMessage, lookupTables, }; } async getLookupTable( accountKey: PublicKey ): Promise<AddressLookupTableAccount> { if (this.lookupTableCahce.has(accountKey.toString())) { return this.lookupTableCahce.get(accountKey.toString()); } return (await this.connection.getAddressLookupTable(accountKey)).value; } /** * Get the jupiter instructions from transaction by filtering out instructions to compute budget and associated token programs * @param transactionMessage the transaction message * @param inputMint the input mint * @param outputMint the output mint */ public getJupiterInstructions({ transactionMessage, inputMint, outputMint, }: { transactionMessage: TransactionMessage; inputMint: PublicKey; outputMint: PublicKey; }): TransactionInstruction[] { return transactionMessage.instructions.filter((instruction) => { if ( instruction.programId.toString() === 'ComputeBudget111111111111111111111111111111' ) { return false; } if ( instruction.programId.toString() === 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA' ) { return false; } if ( instruction.programId.toString() === '11111111111111111111111111111111' ) { return false; } if ( instruction.programId.toString() === 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' ) { const mint = instruction.keys[3].pubkey; if (mint.equals(inputMint) || mint.equals(outputMint)) { return false; } } return true; }); } }