UNPKG

@shogun-sdk/money-legos

Version:

Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.

132 lines (130 loc) 6.53 kB
import { VersionedTransaction } from '@solana/web3.js'; import { isNativeAddress, SOLANA_CHAIN_ID } from '../config/chains.js'; import { isWrappedAddress } from '../utils/address.js'; import { getInsufficientGasWebAppError } from '../utils/errors.js'; import { estimateEvmTransaction, getQuoteNativeBridgeFee, getSolanaProvider } from '../utils/rpc.js'; import { getNativeBalanceRpc } from '../utils/getNativeBalance.js'; import { DEFAULT_JITO_TIP } from '../lib/jito.js'; // TODO: make dynamic calulation of fee const TODO_BRIDGE_FEE = BigInt(30000000); // 0.03 SOL in lamports // 150000 lamports is typical minimum fee for swap const MIN_SOLANA_FEE = 150000; const decodeSolanaTransactions = (calldatas) => { return calldatas?.map((calldata) => { const messageBuffer = Buffer.from(calldata.data, 'base64'); return VersionedTransaction.deserialize(messageBuffer); }); }; export class FeeService { async estimateSolanaTransactionFees(provider, transactions) { try { // Estimate fees using existing method const fees = await Promise.all(transactions.map(async (tx) => { try { const feeResponse = await provider.getFeeForMessage(tx.message, 'processed'); if (!feeResponse?.value) { return BigInt(MIN_SOLANA_FEE); } return BigInt(feeResponse.value); } catch (err) { console.error('Error getting fee for message:', err); return BigInt(MIN_SOLANA_FEE); } })); const totalFee = fees.reduce((acc, fee) => acc + fee, BigInt(0)); // Not possible to simulate bundle for solana https://intensitylabsgroup.slack.com/archives/C06MTL2GR42/p1739272343867779?thread_ts=1739266229.767679&cid=C06MTL2GR42 /* // Simulate bundle to get balance changes try { const simulationResult = await simulateBundle({ encodedTransactions: }); // Extract pre and post balances from simulation if (simulationResult) { // Return both fees and balance changes return { fees: totalFee, preBalances: simulationResult.preBalances, postBalances: simulationResult.postBalances, }; } } catch (err) { console.warn('Failed to simulate bundle for balance changes:', err); } */ // Return just fees if simulation fails return { fees: totalFee }; } catch (err) { console.error('Failed to estimate Solana transaction fees:', err); // Return minimum fee * number of transactions as fallback return { fees: BigInt(MIN_SOLANA_FEE * transactions.length), }; } } async checkHasSufficientBalanceForGas(senderAddress, srcNetwork, quote, setSuggestedSlippage, priorityFee = DEFAULT_JITO_TIP, getErrorFallback = getInsufficientGasWebAppError, userDefinedFeeGwei = 0) { const nativeBalance = await getNativeBalanceRpc(senderAddress, srcNetwork); try { if (srcNetwork === SOLANA_CHAIN_ID) { const provider = await getSolanaProvider(); const transactions = decodeSolanaTransactions(quote.calldatas); // TODO https://intensitylabsgroup.slack.com/archives/C06MTL2GR42/p1739266229767679 const hasBridgeCalldata = quote.calldatas.find((tx) => tx.label === 'bridge'); const { bridgeFee } = getQuoteNativeBridgeFee(quote); const estimatedFees = (await this.estimateSolanaTransactionFees(provider, transactions)).fees + (BigInt(quote.jitoTipsTotal || BigInt(priorityFee)) + (hasBridgeCalldata ? bridgeFee : BigInt(0))); const isWrapped = isWrappedAddress(quote.inputAmount?.address); const isNative = isNativeAddress(quote.inputAmount?.address); const nativeBalanceUsed = isWrapped || isNative ? BigInt(quote.inputAmount?.value) : BigInt(0); const minimumNativeBalance = estimatedFees + nativeBalanceUsed; // not adding bridgeFee, because it's already included in the quote.inputAmount.value from dextra const isEnoughBalance = BigInt(nativeBalance) >= minimumNativeBalance; return { fees: { totalTxCost: minimumNativeBalance, estimatedFees: estimatedFees, bridgeFee, }, error: isEnoughBalance ? undefined : getErrorFallback(estimatedFees + bridgeFee, srcNetwork), provider: provider, }; } else { const { maxFeePerGas, maxPriorityFeePerGas, gasLimit, estimatedFees, minimumNativeBalance, provider, bridgeFee, } = await estimateEvmTransaction(srcNetwork, quote, senderAddress, userDefinedFeeGwei); const isEnoughBalance = BigInt(nativeBalance) >= minimumNativeBalance; return { fees: { maxPriorityFeePerGas, maxFeePerGas, gasLimit, totalTxCost: minimumNativeBalance, estimatedFees, bridgeFee, }, provider, error: isEnoughBalance ? undefined : getErrorFallback(estimatedFees + bridgeFee, srcNetwork), }; } } catch (error) { setSuggestedSlippage?.(error.suggestedSlippageValue); return { fees: { totalTxCost: BigInt(0), }, provider: null, error: error.message, }; } } } // TODO https://intensitylabsgroup.slack.com/archives/C06MTL2GR42/p1739266229767679 // TODO: make dynamic calulation of fee Object.defineProperty(FeeService, "getConstSolanaBridgeCostFee", { enumerable: true, configurable: true, writable: true, value: (quote) => { const hasBridgeCallata = quote.calldatas.find((tx) => tx.label === 'bridge'); return hasBridgeCallata ? TODO_BRIDGE_FEE : BigInt(0); } }); //# sourceMappingURL=FeeService.js.map