@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
JavaScript
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