import { CloverDexType, RouterType } from '../consts'; import { IRouterContext } from './RouterContext'; import { Account, Address, Blockhash, FullySignedTransaction, Instruction, Rpc, Signature, SolanaRpcApi, Transaction, } from '@solana/kit'; import BN from 'bn.js'; import { TOKEN_PROGRAM_ADDRESS } from '@solana-program/token'; import { AddressLookupTable } from '@solana-program/address-lookup-table'; export interface Router { readonly routerType: RouterType; readonly connection: Rpc; route: (params: RouteParams, ctx: IRouterContext) => Promise; quote: (params: QuoteParams) => Promise; } type RouteOutputBase = { instructions?: Ixs; /** @deprecated Use `instructions` instead */ ixsRouter?: Instruction[]; amountsExactIn: AmountExactIn; amountsExactOut: AmountExactOut; swapType: SwapType; responseTimeGetQuoteMs: number; responseTimeSwapIxsMs: number; routerType: RouterType; priceImpactBps?: number; simulatedPriceImpactBps?: number; guaranteedPriceImpactBps?: number; priceDifferenceFromPriceSourceBps?: number; simulatedPriceDifferenceFromPriceSourceBps?: number; guaranteedPriceDifferenceFromPriceSourceBps?: number; expiryTime: number; perReferenceId?: string; birdeyeTokenInPriceInSol?: number; birdeyeTokenOutPriceInSol?: number; spotPriceTokenInAmount?: BN; spotPriceTokenOutAmount?: BN; inputMintProgramOwner?: Address; outputMintProgramOwner?: Address; inputTokenDecimals?: number; outputTokenDecimals?: number; jupRequestId?: string; cloverDexType?: CloverDexType; skipLimoLogsForRoute?: boolean; simulationResult?: RouteSimulationResult; requestedMaxAccounts?: number; }; type WithTransaction = { transaction: Transaction; lookupTableAccounts: Account[]; }; type WithoutTransaction = { transaction?: never; lookupTableAccounts?: Account[]; }; // Reusable union for tx presence type RouteOutputUnion = WithTransaction | WithoutTransaction; // Concrete aliases export type RouterOutput = RouteOutputBase & RouteOutputUnion; export type RouteOutput = RouteOutputBase & RouteOutputUnion; export type RouterInstructionsBase = { createInAtaIxs: Instruction[]; createOutAtaIxs: Instruction[]; wrapSolIxs: Instruction[]; swapIxs: Instruction[]; unwrapSolIxs: Instruction[]; }; export type LimoRouteInstructions = { limoLogsStartIxs: Instruction[]; limoLedgerStartIxs: Instruction[]; limoLogsEndIxs: Instruction[]; limoLedgerEndIxs: Instruction[]; }; // What routers produce (limo fields optional) export type RouterInstructions = RouterInstructionsBase & Partial; export type RouteInstructions = RouterInstructionsBase & LimoRouteInstructions; export type AmountExactIn = { amountIn: BN; amountOutGuaranteed: BN; amountOut: BN; amountOutSimulated?: BN; }; export type AmountExactOut = { amountOut: BN; amountInGuaranteed: BN; amountIn: BN; amountInSimulated?: BN; }; export type SwapType = 'exactIn' | 'exactOut'; export type RouteSimulationResult = { /** * Indicates if the simulation was successful */ success: boolean; /** * Cause of the failure if `success` is false */ errorReason?: SimulationErrorReason; /** * Logs generated during the simulation */ logs?: string[]; /** * Timestamp of the simulation */ simulationTimestamp?: number; }; export enum SimulationErrorReason { Unknown = 'UNKNOWN', SlippageExceeded = 'SLIPPAGE_EXCEEDED', TooManyAccountLocks = 'TOO_MANY_ACCOUNT_LOCKS', TransactionTooLarge = 'TRANSACTION_TOO_LARGE', } export type RouteParams = { tokenIn: Address; tokenOut: Address; amount: BN; swapType: SwapType; executor: Address; maxSlippageBps: number; referrerPda?: Address; includeSetupIxs?: boolean; wrapAndUnwrapSol?: boolean; routerTypes?: RouterType[]; includeLimoLogs?: boolean; includeRfq?: boolean; /** * Base per-router timeout in milliseconds. * Clover routes may use up to 4x this budget internally while composing inner routes. */ timeoutMs?: number; atLeastOneNoMoreThanTimeoutMS?: number; withSimulation?: boolean; /** * Optional flag to filter out failed simulations from the results * Only applicable if `withSimulation` is true */ filterFailedSimulations?: boolean; destinationTokenAccount?: Address; preferredMaxAccounts?: number | number[]; requestPriceImpact?: boolean; perMinimumQuoteLifetimeSeconds?: number; // Minimum lifetime for per swap execution assertSwapBalances?: boolean; overrideAssertMaxInputAmountChange?: BN; // Optional override for assert max input amount change overrideAssertMinOutputAmountChange?: BN; // Optional override for assert min output amount change simulateWithMockInputAmount?: boolean; // Optional flag to simulate with a mock input amount simulateWithTopHolder?: boolean; // Optional flag to simulate with a top-holder executor wallet }; export type RouteOrQuoteParams = { tokenIn: Address; tokenOut: Address; amount: BN; swapType: SwapType; executor?: Address; maxSlippageBps: number; referrerPda?: Address; includeSetupIxs?: boolean; wrapAndUnwrapSol?: boolean; routerTypes?: RouterType[]; includeLimoLogs?: boolean; includeRfq?: boolean; /** * Base per-router timeout in milliseconds. * Clover routes may use up to 4x this budget internally while composing inner routes. */ timeoutMs?: number; atLeastOneNoMoreThanTimeoutMS?: number; withSimulation?: boolean; filterFailedSimulations?: boolean; destinationTokenAccount?: Address; preferredMaxAccounts?: number | number[]; requestPriceImpact?: boolean; perMinimumQuoteLifetimeSeconds?: number; // Minimum lifetime for per swap execution assertSwapBalances?: boolean; overrideAssertMaxInputAmountChange?: BN; // Optional override for assert max input amount change overrideAssertMinOutputAmountChange?: BN; // Optional override for assert min output amount change simulateWithMockInputAmount?: boolean; // Optional flag to simulate with a mock input amount simulateWithTopHolder?: boolean; // Optional flag to simulate with a top-holder executor wallet }; // Utility types to ensure all optional parameters are properly handled type RequiredParams = 'tokenIn' | 'tokenOut' | 'amount' | 'swapType' | 'maxSlippageBps'; type RouteRequiredParams = RequiredParams | 'executor'; type RouteOnlyParams = | 'referrerPda' | 'includeSetupIxs' | 'wrapAndUnwrapSol' | 'includeLimoLogs' | 'withSimulation' | 'destinationTokenAccount'; // Ensure RouteParams contains all parameters from RouteOrQuoteParams except those quote-specific type _RouteParamsCheck = Required> & Pick>; // Ensure QuoteParams contains all shared parameters except route-only ones type _QuoteParamsCheck = Required> & Pick>; // Type assertions to ensure compatibility - these will cause TypeScript errors if the types diverge /* eslint-disable @typescript-eslint/no-unused-vars */ const _routeParamsTypeCheck: _RouteParamsCheck = {} as RouteParams; const _quoteParamsTypeCheck: _QuoteParamsCheck = {} as QuoteParams; /* eslint-enable @typescript-eslint/no-unused-vars */ // This should be called when the preferredMaxAccounts should have 1 element. The function extracts that element from value: number | number[] | undefined) export function extractPreferredMaxAccounts(value: number | number[] | undefined): number | undefined { if (Array.isArray(value)) return value[0]; return value; } export function asPreferredMaxAccountsArray(value: number | number[] | undefined): number[] | undefined { if (value === undefined) return undefined; if (Array.isArray(value)) return value; return [value]; } export const SOL_MINT_INFO: MintInfo = { tokenProgramId: TOKEN_PROGRAM_ADDRESS, decimals: 9, }; export type MintInfo = { tokenProgramId: Address; decimals: number; }; export type QuoteParams = { tokenIn: Address; tokenOut: Address; amount: BN; swapType: SwapType; maxSlippageBps: number; routerTypes?: RouterType[]; includeRfq?: boolean; /** * Base per-router timeout in milliseconds. * Clover routes may use up to 4x this budget internally while composing inner routes. */ timeoutMs?: number; atLeastOneNoMoreThanTimeoutMS?: number; preferredMaxAccounts?: number | number[]; executor?: Address; // always undefined for quotes, but included for consistency with RouteParams requestPriceImpact?: boolean; }; export type QuoteParamsSerialized = { tokenIn: string; tokenOut: string; amount: string; swapType: string; maxSlippageBps: number; routerTypes?: string[]; includeRfq?: string; /** * Base per-router timeout in milliseconds. * Clover routes may use up to 4x this budget internally while composing inner routes. */ timeoutMs?: number; atLeastOneNoMoreThanTimeoutMS?: number; preferredMaxAccounts?: number | number[]; requestPriceImpact?: boolean; executor?: string; // always undefined for quotes, but included for consistency with RouteParams }; // Function to get the keys of QuoteParams in a type-safe manner (compilation error if keys are missing) export function getQuoteParamsKeys(): (keyof QuoteParams)[] { const keyMap: { [K in keyof QuoteParams]-?: K } = { tokenIn: 'tokenIn', tokenOut: 'tokenOut', amount: 'amount', swapType: 'swapType', maxSlippageBps: 'maxSlippageBps', routerTypes: 'routerTypes', includeRfq: 'includeRfq', timeoutMs: 'timeoutMs', atLeastOneNoMoreThanTimeoutMS: 'atLeastOneNoMoreThanTimeoutMS', preferredMaxAccounts: 'preferredMaxAccounts', requestPriceImpact: 'requestPriceImpact', executor: 'executor', }; return Object.keys(keyMap) as (keyof QuoteParams)[]; } // Function to get the keys of RouteParams in a type-safe manner (compliation error if keys are missing) export function getRouteParamsKeys(): (keyof RouteParams)[] { const keyMap: { [K in keyof RouteParams]-?: K } = { tokenIn: 'tokenIn', tokenOut: 'tokenOut', amount: 'amount', swapType: 'swapType', executor: 'executor', maxSlippageBps: 'maxSlippageBps', referrerPda: 'referrerPda', includeSetupIxs: 'includeSetupIxs', wrapAndUnwrapSol: 'wrapAndUnwrapSol', routerTypes: 'routerTypes', includeLimoLogs: 'includeLimoLogs', includeRfq: 'includeRfq', timeoutMs: 'timeoutMs', atLeastOneNoMoreThanTimeoutMS: 'atLeastOneNoMoreThanTimeoutMS', withSimulation: 'withSimulation', filterFailedSimulations: 'filterFailedSimulations', destinationTokenAccount: 'destinationTokenAccount', preferredMaxAccounts: 'preferredMaxAccounts', requestPriceImpact: 'requestPriceImpact', perMinimumQuoteLifetimeSeconds: 'perMinimumQuoteLifetimeSeconds', assertSwapBalances: 'assertSwapBalances', overrideAssertMaxInputAmountChange: 'overrideAssertMaxInputAmountChange', overrideAssertMinOutputAmountChange: 'overrideAssertMinOutputAmountChange', simulateWithMockInputAmount: 'simulateWithMockInputAmount', simulateWithTopHolder: 'simulateWithTopHolder', }; return Object.keys(keyMap) as (keyof RouteParams)[]; } // Function to get the keys of RouteOutputSerialized in a type-safe manner (compilation error if keys are missing) export function getRouteOutputSerializedKeys(): (keyof RouteOutputSerialized)[] { const keyMap: { [K in keyof RouteOutputSerialized]-?: K } = { instructions: 'instructions', ixsRouterBs58: 'ixsRouterBs58', transactionBs58: 'transactionBs58', amountsExactIn: 'amountsExactIn', amountsExactOut: 'amountsExactOut', swapType: 'swapType', responseTimeGetQuoteMs: 'responseTimeGetQuoteMs', responseTimeSwapIxsMs: 'responseTimeSwapIxsMs', priceImpactBps: 'priceImpactBps', simulatedPriceImpactBps: 'simulatedPriceImpactBps', guaranteedPriceImpactBps: 'guaranteedPriceImpactBps', priceDifferenceFromPriceSourceBps: 'priceDifferenceFromPriceSourceBps', simulatedPriceDifferenceFromPriceSourceBps: 'simulatedPriceDifferenceFromPriceSourceBps', guaranteedPriceDifferenceFromPriceSourceBps: 'guaranteedPriceDifferenceFromPriceSourceBps', routerType: 'routerType', lookupTableAccountsBs58: 'lookupTableAccountsBs58', expiryTime: 'expiryTime', perReferenceId: 'perReferenceId', birdeyeTokenInPriceInSol: 'birdeyeTokenInPriceInSol', birdeyeTokenOutPriceInSol: 'birdeyeTokenOutPriceInSol', spotPriceTokenInAmount: 'spotPriceTokenInAmount', spotPriceTokenOutAmount: 'spotPriceTokenOutAmount', inputMintProgramOwner: 'inputMintProgramOwner', outputMintProgramOwner: 'outputMintProgramOwner', inputTokenDecimals: 'inputTokenDecimals', outputTokenDecimals: 'outputTokenDecimals', jupRequestId: 'jupRequestId', withSimulation: 'withSimulation', cloverDexType: 'cloverDexType', skipLimoLogsForRoute: 'skipLimoLogsForRoute', simulationResult: 'simulationResult', requestedMaxAccounts: 'requestedMaxAccounts', }; return Object.keys(keyMap) as (keyof RouteOutputSerialized)[]; } export type RouteParamsSerialized = { tokenIn: string; tokenOut: string; amount: string; swapType: string; executor: string; maxSlippageBps: number; referrerPda?: string; includeSetupIxs?: string; wrapAndUnwrapSol?: string; routerTypes?: string[]; includeLimoLogs?: string; includeRfq?: string; /** * Base per-router timeout in milliseconds. * Clover routes may use up to 4x this budget internally while composing inner routes. */ timeoutMs?: number; atLeastOneNoMoreThanTimeoutMS?: number; withSimulation?: string; /** * Optional flag to filter out failed simulations from the results * Only applicable if `withSimulation` is true */ filterFailedSimulations?: string; destinationTokenAccount?: string; preferredMaxAccounts?: number | number[]; requestPriceImpact?: string; perMinimumQuoteLifetimeSeconds?: number; // Minimum lifetime for per swap execution assertSwapBalances?: string; overrideAssertMaxInputAmountChange?: string; // Optional override for assert max input amount change overrideAssertMinOutputAmountChange?: string; // Optional override for assert min output amount change simulateWithMockInputAmount?: string; // Optional flag to simulate with a mock input amount simulateWithTopHolder?: string; // Optional flag to simulate with a top-holder executor wallet }; type RouteOutputSerializedBase = { instructions?: RouteInstructionsSerialized; /** * @deprecated Use `instructions` instead */ ixsRouterBs58?: InstructionSerialized[]; amountsExactIn: AmountExactInSerialized; amountsExactOut: AmountExactOutSerialized; swapType: string; responseTimeGetQuoteMs: number; responseTimeSwapIxsMs: number; priceImpactBps?: number; simulatedPriceImpactBps?: number; guaranteedPriceImpactBps?: number; priceDifferenceFromPriceSourceBps?: number; simulatedPriceDifferenceFromPriceSourceBps?: number; guaranteedPriceDifferenceFromPriceSourceBps?: number; routerType: string; expiryTime: number; perReferenceId?: string; birdeyeTokenInPriceInSol?: number; birdeyeTokenOutPriceInSol?: number; spotPriceTokenInAmount?: string; spotPriceTokenOutAmount?: string; inputMintProgramOwner?: string; outputMintProgramOwner?: string; inputTokenDecimals?: number; outputTokenDecimals?: number; jupRequestId?: string; withSimulation?: string; cloverDexType?: CloverDexType; skipLimoLogsForRoute?: string; // Indicates if limo logs should be skipped for this route (based on simulation success) /** * Optional simulation result object that contains the success status, error message, and logs */ simulationResult?: RouteSimulationResultSerialized; requestedMaxAccounts?: number; }; type WithTransactionSerialized = { transactionBs58: string; lookupTableAccountsBs58: AddressLookupTableAccountSerialized[]; }; type WithoutTransactionSerialized = { transactionBs58?: never; lookupTableAccountsBs58?: AddressLookupTableAccountSerialized[]; }; export type RouteOutputSerialized = RouteOutputSerializedBase & (WithTransactionSerialized | WithoutTransactionSerialized); export type AmountExactInSerialized = { amountIn: string; amountOutGuaranteed: string; amountOut: string; amountOutSimulated?: string; }; export type AmountExactOutSerialized = { amountOut: string; amountInGuaranteed: string; amountIn: string; amountInSimulated?: string; }; export type ExecutePerRouteParams = { userSignature: string; perReferenceId: string; userWallet?: string; quoteExpiryMs?: number; }; export type RouteInstructionsSerialized = { createInAtaIxs: InstructionSerialized[]; createOutAtaIxs: InstructionSerialized[]; wrapSolIxs: InstructionSerialized[]; limoLogsStartIxs: InstructionSerialized[]; limoLedgerStartIxs: InstructionSerialized[]; swapIxs: InstructionSerialized[]; limoLedgerEndIxs: InstructionSerialized[]; limoLogsEndIxs: InstructionSerialized[]; unwrapSolIxs: InstructionSerialized[]; }; export type InstructionSerialized = { programId: string; // base58 string representing the PublicKey of the program data: string; // base64 string representing the data keys: Array<{ pubkey: string; // base58 string representing the PublicKey isSigner: boolean; isWritable: boolean; }>; }; export type AddressLookupTableStateSerialized = { deactivationSlot: string; lastExtendedSlot: number; lastExtendedSlotStartIndex: number; authority?: string; addresses: string[]; }; export type AddressLookupTableAccountSerialized = { key: string; state: AddressLookupTableStateSerialized; }; export type RouteSimulationResultSerialized = { /** * Indicates if the simulation was successful */ success: boolean; /** * Cause of the failure if `success` is false */ errorReason?: string; /** * Logs generated during the simulation */ logs?: string[]; /** * Timestamp of the simulation in milliseconds since epoch */ simulationTimestamp?: number; }; export type BlockhashWithExpiry = { blockhash: Blockhash; lastValidBlockHeight: bigint; }; export type ExecuteRouteParams = { userToExecute: Address; router: RouteOutput; signTransaction: (tx: Transaction) => Promise; signPartialTransaction: (tx: Transaction) => Promise; executeTransaction: (tx: FullySignedTransaction) => Promise; confirmTransaction: (sig: Signature) => Promise; recentBlockhash: BlockhashWithExpiry; computeBudgetInstructions?: Instruction[]; }; export interface BirdEyePriceMultipleParams { list_address: string; } export interface BirdeyePriceData { value: number; updateUnixTime: number; updateHumanTime: string; priceInNative: number; priceChange24h: number; liquidity: number; } export interface BirdeyeMultiPriceResponse { success: boolean; data: { [tokenAddress: string]: BirdeyePriceData; }; } export interface GetJupiterPriceParams { ids: string; vsToken?: string; showExtraInfo?: boolean; } export interface GetJupiterPriceResponse { [key: string]: GetJupiterPriceTokenInfo; } export interface GetJupiterPriceTokenInfo { usdPrice: number; blockId?: number; decimals?: number; priceChange24h?: number; mint?: string; // The mint address of the token } export interface RoutesResponse { routes: RouteOutput[]; traceId: string; error?: Error; } export interface BestRouteResponse { bestRoute: RouteOutput | undefined; traceId: string; } export interface AsyncData { inputMintProgramOwner?: Address; outputMintProgramOwner?: Address; inputTokenDecimals?: number; outputTokenDecimals?: number; birdeyeTokenInPriceInSol?: number; birdeyeTokenOutPriceInSol?: number; } export interface TopHolderBirdeye { amount: string; decimals: number; mint: string; owner: string; token_account: string; ui_amount: number; } export interface TopHolderResponse { data: { items: TopHolderBirdeye[]; }; success: boolean; } export interface TokenAccountMetadata { address: Address; programAddress: Address; mint: Address; }