UNPKG

@lifi/sdk

Version:

LI.FI Any-to-Any Cross-Chain-Swap SDK

191 lines (175 loc) 5.01 kB
import { Connection, type VersionedTransaction } from '@solana/web3.js' import { uint8ArrayToBase64 } from '../../../utils/uint8ArrayToBase64.js' import { JITO_TIP_ACCOUNTS } from './constants.js' export type SimulateBundleResult = { value: { summary: 'succeeded' | { failed: { error: any; tx_signature: string } } transactionResults: Array<{ err: any logs: string[] | null unitsConsumed?: number }> } } export type BundleStatus = { bundle_id: string transactions: string[] slot: number confirmation_status: 'processed' | 'confirmed' | 'finalized' err: | { Ok: null } | any } export type BundleStatusResult = { context: { slot: number } value: BundleStatus[] } /** * Makes a direct RPC request to an endpoint * */ async function rpcRequest<T>( endpoint: string, method: string, params: any[] ): Promise<T> { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params, }), }) if (!response.ok) { throw new Error(`Jito RPC Error: ${response.status} ${response.statusText}`) } const data = await response.json() if (data.error) { throw new Error(`Jito RPC Error: ${data.error.message}`) } return data.result } /** * Extended Connection class with Jito bundle support * Adds simulateBundle, sendBundle, and getTipAccounts methods */ export class JitoConnection extends Connection { private tipAccountsCache: string[] | null = null /** * Check if an RPC endpoint supports Jito bundles * @param rpcUrl - The RPC endpoint URL to check * @returns true if the endpoint supports Jito bundle methods */ static async isJitoRpc(rpcUrl: string): Promise<boolean> { try { // method exists if the request is successfull and doesn't throw an error await rpcRequest(rpcUrl, 'getTipAccounts', []) return true } catch { return false } } /** * Makes a direct RPC request to the Jito-enabled endpoint */ protected async rpcRequest<T>(method: string, params: any[]): Promise<T> { try { return await rpcRequest(this.rpcEndpoint, method, params) } catch (error) { console.error(`Jito RPC request failed: ${method}`, { endpoint: this.rpcEndpoint, params, error, }) throw error } } /** * Serialize a transaction to base64 for RPC submission */ private serializeTransaction(transaction: VersionedTransaction): string { return uint8ArrayToBase64(transaction.serialize()) } /** * Get the tip accounts from the Jito endpoint, using fallbacks if results are empty * Results are cached to avoid repeated RPC calls */ async getTipAccounts(): Promise<string[]> { if (this.tipAccountsCache) { return this.tipAccountsCache } try { const accounts = await this.rpcRequest<string[]>('getTipAccounts', []) if (!accounts.length) { throw new Error('RPC has no tip accounts') } this.tipAccountsCache = accounts return accounts } catch (error) { const fallbackAccounts = JITO_TIP_ACCOUNTS console.warn( `Failed to fetch tip accounts from RPC, using fallback`, error ) return fallbackAccounts } } /** * Get a random Jito tip account to reduce contention */ async getRandomTipAccount(): Promise<string> { const accounts = await this.getTipAccounts() return accounts[Math.floor(Math.random() * accounts.length)] } /** * Manually refresh the tip accounts cache * Useful for long-running processes that may need updated tip accounts */ async refreshTipAccounts(): Promise<string[]> { this.tipAccountsCache = null return this.getTipAccounts() } /** * Simulate a bundle before sending it * @param bundle - Array of signed transactions * @returns Simulation result */ async simulateBundle( bundle: VersionedTransaction[] ): Promise<SimulateBundleResult> { const encodedTransactions = bundle.map((tx) => this.serializeTransaction(tx) ) return this.rpcRequest<SimulateBundleResult>('simulateBundle', [ { encodedTransactions }, ]) } /** * Send a bundle to the Jito block engine * @param bundle - Array of signed transactions * @returns Bundle UUID */ async sendBundle(bundle: VersionedTransaction[]): Promise<string> { const encodedTransactions = bundle.map((tx) => this.serializeTransaction(tx) ) return this.rpcRequest<string>('sendBundle', [encodedTransactions]) } /** * Get the status of submitted bundles * @param bundleIds - Array of bundle UUIDs to check * @returns Bundle status information */ async getBundleStatuses(bundleIds: string[]): Promise<BundleStatusResult> { return this.rpcRequest<BundleStatusResult>('getBundleStatuses', [bundleIds]) } }