import { createJupiterApiClient, DefaultApi, QuoteResponse, SwapMode } from '@jup-ag/api'; import axios from 'axios'; import BN from 'bn.js'; import { JupiterBase } from './jupiterInterfaceBase'; import { QuoteParams, RouteOutput, RouteParams, safeBNToNumber, RouterContext, RouterOutput, extractPreferredMaxAccounts, } from '../swap_api_utils'; import { JUPITER_PRICE_API, RouterType } from '../consts'; import { Logger } from '../utils/Logger'; import { Address, Rpc, SolanaRpcApi } from '@solana/kit'; export abstract class JupiterRouterBase extends JupiterBase { private maxAccounts: number; private jupiterClient: DefaultApi; private logger: Logger; constructor( connection: Rpc, routerType: RouterType, jupiterUrl: string, maxAccounts: number, logger: Logger = console, ) { super(routerType, connection); this.maxAccounts = maxAccounts; this.jupiterClient = createJupiterApiClient({ basePath: jupiterUrl, }); this.logger = logger; } async route(params: RouteParams, ctx: RouterContext): Promise { try { const timeBeforeGetQuote = Date.now(); const quoteResponse = await this.jupiterClient.quoteGet({ inputMint: params.tokenIn, outputMint: params.tokenOut, amount: safeBNToNumber(params.amount), slippageBps: params.maxSlippageBps, maxAccounts: extractPreferredMaxAccounts(params.preferredMaxAccounts) || this.maxAccounts, swapMode: params.swapType === 'exactIn' ? SwapMode.ExactIn : SwapMode.ExactOut, excludeDexes: ['Crema'], }); if (params.swapType === 'exactIn' && quoteResponse.inAmount !== params.amount.toString()) { this.logger.info( `${this.routerType}: Swap quote inAmount ${ quoteResponse.inAmount } does not match requested amount ${params.amount.toString()}`, ); return undefined; } else if (params.swapType === 'exactOut' && quoteResponse.outAmount !== params.amount.toString()) { this.logger.info( `${this.routerType}: Swap quote outAmount ${quoteResponse.outAmount} does not match requested amount ${params.amount.toString()}`, ); return undefined; } const timeAfterGetQuote = Date.now(); const instructions = await this.jupiterClient.swapInstructionsPost({ swapRequest: { userPublicKey: params.executor, quoteResponse, wrapAndUnwrapSol: params.wrapAndUnwrapSol, destinationTokenAccount: params.destinationTokenAccount ? params.destinationTokenAccount : undefined, }, }); return [ await this.getRouterOutput(instructions, quoteResponse, params, ctx, timeAfterGetQuote, timeBeforeGetQuote), ]; } catch (error) { this.logger.error(`${this.routerType}: error`, error); return undefined; } } async quote(params: QuoteParams): Promise { try { const timeBeforeGetQuoteData = Date.now(); const quoteResponse = await this.jupiterClient.quoteGet({ inputMint: params.tokenIn, outputMint: params.tokenOut, amount: safeBNToNumber(params.amount), slippageBps: params.maxSlippageBps, maxAccounts: extractPreferredMaxAccounts(params.preferredMaxAccounts) || this.maxAccounts, swapMode: params.swapType === 'exactIn' ? 'ExactIn' : 'ExactOut', excludeDexes: ['Crema'], }); if (quoteResponse.inAmount !== params.amount.toString()) { this.logger.info( `${this.routerType}: Swap quote inAmount ${ quoteResponse.inAmount } does not match requested amount ${params.amount.toString()}`, ); return undefined; } const timeAfterGetQuoteData = Date.now(); return [await this.getQuoteOutput(quoteResponse, params, timeAfterGetQuoteData, timeBeforeGetQuoteData)]; } catch (error) { this.logger.error(`${this.routerType}: Failed to get quote`, error); return undefined; } } async getQuoteResponse( tokenIn: Address, tokenOut: Address, amountIn: BN, maxSlippageBps: number, ): Promise { const quoteResponse = await this.jupiterClient.quoteGet({ inputMint: tokenIn, outputMint: tokenOut, amount: safeBNToNumber(amountIn), slippageBps: maxSlippageBps, maxAccounts: this.maxAccounts, excludeDexes: ['Crema'], }); return quoteResponse; } } export async function getJupiterPrice(inputMint: Address, outputMint: Address): Promise { const params = { ids: inputMint.toString(), vsToken: outputMint.toString(), }; try { const res = await axios.get(JUPITER_PRICE_API, { params }); return res.data[inputMint.toString()]?.usdPrice || 0; } catch { return 0; } }