/** * Utility Functions */ import { ComputeBudgetProgram, Connection, TransactionInstruction } from "@solana/web3.js"; // Re-export utilities from submodules export * from "./simulation.js"; export * from "./jito.js"; /** * Priority level for transaction fees */ export enum PriorityLevel { Fast = "fast", Turbo = "turbo", Ultra = "ultra", } /** * Configuration for priority fee calculation */ export interface PriorityFeeConfig { level: PriorityLevel; baseMultiplier: number; maxMicroLamports: number; } /** * Priority fee configuration by level * - Fast: 50th percentile, 1.0x multiplier, 1k max (minimal fallback) * - Turbo: 75th percentile, 2.0x multiplier, 5k max (minimal fallback) * - Ultra: 90th percentile, 5.0x multiplier, 10k max (minimal fallback) */ export const PRIORITY_CONFIGS: Record = { [PriorityLevel.Fast]: { level: PriorityLevel.Fast, baseMultiplier: 1.0, maxMicroLamports: 1_000, }, [PriorityLevel.Turbo]: { level: PriorityLevel.Turbo, baseMultiplier: 2.0, maxMicroLamports: 5_000, }, [PriorityLevel.Ultra]: { level: PriorityLevel.Ultra, baseMultiplier: 5.0, maxMicroLamports: 10_000, }, }; /** * Default priority fee in micro-lamports per compute unit * Minimum value (1) is used as fallback when RPC doesn't support priority fee queries */ export const DEFAULT_PRIORITY_FEE_MICRO_LAMPORTS = 1; // Minimum priority fee /** * Create a Compute Unit limit instruction */ export function createComputeUnitLimitInstruction(units: number): TransactionInstruction { return ComputeBudgetProgram.setComputeUnitLimit({ units }); } /** * Create a Priority Fee instruction */ export function createPriorityFeeInstruction(microLamportsPerCu: number): TransactionInstruction { return ComputeBudgetProgram.setComputeUnitPrice({ microLamports: microLamportsPerCu, }); } /** * Get recent priority fees from the network * Returns the median priority fee from recent slots * * @param connection - Solana connection * @param fallbackMicroLamports - Fallback value if RPC doesn't support priority fees (e.g., devnet) */ export async function getRecentPriorityFee( connection: Connection, fallbackMicroLamports: number = DEFAULT_PRIORITY_FEE_MICRO_LAMPORTS ): Promise { try { const recentFees = await connection.getRecentPrioritizationFees(); if (!recentFees || recentFees.length === 0) { return fallbackMicroLamports; } // Sort by prioritization fee and get median const sortedFees = recentFees .map((f) => f.prioritizationFee) .filter((f) => f > 0) .sort((a, b) => a - b); if (sortedFees.length === 0) { return fallbackMicroLamports; } const medianIndex = Math.floor(sortedFees.length / 2); const medianFee = sortedFees[medianIndex]; // Apply a multiplier for faster confirmation (1.5x median) return Math.max(Math.ceil(medianFee * 1.5), fallbackMicroLamports); } catch { // RPC might not support this method (e.g., some devnet endpoints) return fallbackMicroLamports; } } /** * Calculate priority fee using percentile-based approach * Falls back to tier-based fees if RPC call fails * * @param connection - Solana connection * @param level - Priority level (Fast/Turbo/Ultra) * @param fallbackMicroLamports - Optional fallback value (defaults to level's max) * @returns Priority fee in microLamports per compute unit */ export async function calculatePriorityFee( connection: Connection, level: PriorityLevel, fallbackMicroLamports?: number ): Promise { try { const recentFees = await connection.getRecentPrioritizationFees(); if (!recentFees || recentFees.length === 0) { return fallbackMicroLamports ?? PRIORITY_CONFIGS[level].maxMicroLamports; } // Extract and sort fees const sortedFees = recentFees .map((f) => f.prioritizationFee) .filter((f) => f > 0) .sort((a, b) => a - b); if (sortedFees.length === 0) { return fallbackMicroLamports ?? PRIORITY_CONFIGS[level].maxMicroLamports; } // Calculate percentile based on level let percentileIndex: number; switch (level) { case PriorityLevel.Fast: percentileIndex = Math.floor(sortedFees.length * 0.5); // 50th percentile break; case PriorityLevel.Turbo: percentileIndex = Math.floor(sortedFees.length * 0.75); // 75th percentile break; case PriorityLevel.Ultra: percentileIndex = Math.floor(sortedFees.length * 0.9); // 90th percentile break; } const percentileFee = sortedFees[percentileIndex]; const config = PRIORITY_CONFIGS[level]; // Apply multiplier and cap at max const calculatedFee = Math.ceil(percentileFee * config.baseMultiplier); return Math.min(calculatedFee, config.maxMicroLamports); } catch (error) { const fallback = fallbackMicroLamports ?? PRIORITY_CONFIGS[level].maxMicroLamports; console.warn( `[calculatePriorityFee] RPC call failed (likely devnet without priority fee support), using fallback for ${level}: ${fallback} microLamports/CU`, error ); return fallback; } } /** * Create compute budget instructions for a transaction * * @param cuLimit - Compute unit limit * @param connection - Optional connection to fetch dynamic priority fee * @param priorityFeeMicroLamports - Optional manual priority fee override * @returns Array of compute budget instructions to prepend to transaction */ export async function createComputeBudgetInstructions( cuLimit: number, connection?: Connection, priorityFeeMicroLamports?: number ): Promise { const instructions: TransactionInstruction[] = []; // Add compute unit limit instructions.push(createComputeUnitLimitInstruction(cuLimit)); // Add priority fee let priorityFee = priorityFeeMicroLamports; if (priorityFee === undefined && connection) { priorityFee = await getRecentPriorityFee(connection); } else if (priorityFee === undefined) { priorityFee = DEFAULT_PRIORITY_FEE_MICRO_LAMPORTS; } instructions.push(createPriorityFeeInstruction(priorityFee)); return instructions; } /** * Sleep for a specified number of milliseconds */ export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Format lamports to SOL string */ export function formatSol(lamports: number | bigint): string { const sol = Number(lamports) / 1e9; return sol.toFixed(9); } /** * Format cents to dollar string */ export function formatDollars(cents: number): string { return `$${(cents / 100).toFixed(2)}`; } /** * Format probability as percentage */ export function formatPercentage(probability: number): string { return `${(probability * 100).toFixed(1)}%`; }