From 263c1b0592ee1f883b85bd6612731851ec6ec0cb Mon Sep 17 00:00:00 2001 From: cryptooda Date: Mon, 29 Dec 2025 20:40:47 +0700 Subject: [PATCH] Refactor swapGmxTokensImpl to improve wallet balance verification and precision handling. Introduce tolerance checks for requested amounts against wallet balances to prevent errors during swaps. Update logging to provide detailed information on final amounts used in transactions, ensuring better user feedback and error handling. --- .../src/plugins/custom/gmx.ts | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts index 2de0952e..62e968fc 100644 --- a/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts +++ b/src/Managing.Web3Proxy/src/plugins/custom/gmx.ts @@ -2941,10 +2941,7 @@ export const swapGmxTokensImpl = async ( throw new Error(`To token ${toTicker} has zero address - token not found or invalid`); } - // Calculate the from token amount with proper decimals - const fromTokenAmount = BigInt(Math.floor(amount * Math.pow(10, fromTokenData.decimals))); - - // Get actual wallet balance for the from token to verify we have enough + // Get actual wallet balance first to avoid precision issues let walletBalance: bigint; if (fromTokenData.isNative) { // For native tokens (ETH), get balance directly @@ -2966,11 +2963,31 @@ export const swapGmxTokensImpl = async ( walletBalance = balanceResult.data.token.balance.returnValues[0]; } + // Calculate the from token amount with proper decimals + const requestedAmountBigInt = BigInt(Math.floor(amount * Math.pow(10, fromTokenData.decimals))); + + // Convert to numbers for comparison (to handle precision issues) + const walletBalanceNumber = Number(walletBalance) / Math.pow(10, fromTokenData.decimals); + const requestedAmountNumber = amount; + + // If requested amount is very close to balance (within 0.000001 tolerance), use actual balance + // This handles floating point precision issues when swapping entire balance + const tolerance = 0.000001; + const difference = Math.abs(walletBalanceNumber - requestedAmountNumber); + + // Use actual balance if amounts are very close (likely trying to swap entire balance) + const fromTokenAmount = difference <= tolerance ? walletBalance : requestedAmountBigInt; + console.log('💰 Wallet balance check:', { fromTicker, fromTokenAddress: fromTokenData.address, - requestedAmount: fromTokenAmount.toString(), + requestedAmount: requestedAmountBigInt.toString(), walletBalance: walletBalance.toString(), + requestedAmountFormatted: requestedAmountNumber.toFixed(6), + walletBalanceFormatted: walletBalanceNumber.toFixed(6), + difference: difference.toFixed(6), + usingActualBalance: difference <= tolerance, + finalAmount: fromTokenAmount.toString(), hasEnough: walletBalance >= fromTokenAmount }); @@ -3006,7 +3023,13 @@ export const swapGmxTokensImpl = async ( } // Verify balance hasn't changed (someone else might have used the wallet) - if (latestWalletBalance !== walletBalance) { + // Allow small tolerance for potential rounding differences + const balanceDifference = latestWalletBalance > walletBalance + ? latestWalletBalance - walletBalance + : walletBalance - latestWalletBalance; + const maxAllowedDifference = BigInt(Math.floor(Math.pow(10, fromTokenData.decimals - 6))); // 0.000001 in token units + + if (balanceDifference > maxAllowedDifference) { const latestBalanceFormatted = Number(latestWalletBalance) / Math.pow(10, fromTokenData.decimals); const originalBalanceFormatted = Number(walletBalance) / Math.pow(10, fromTokenData.decimals); throw new Error( @@ -3016,10 +3039,15 @@ export const swapGmxTokensImpl = async ( ); } + // Re-calculate final amount using latest balance if we're using actual balance + const latestBalanceNumber = Number(latestWalletBalance) / Math.pow(10, fromTokenData.decimals); + const latestDifference = Math.abs(latestBalanceNumber - requestedAmountNumber); + const finalSwapAmount = latestDifference <= tolerance ? latestWalletBalance : fromTokenAmount; + // Verify we still have enough after re-check - if (latestWalletBalance < fromTokenAmount) { + if (latestWalletBalance < finalSwapAmount) { const latestBalanceFormatted = Number(latestWalletBalance) / Math.pow(10, fromTokenData.decimals); - const requestedFormatted = Number(fromTokenAmount) / Math.pow(10, fromTokenData.decimals); + const requestedFormatted = Number(finalSwapAmount) / Math.pow(10, fromTokenData.decimals); throw new Error( `Insufficient ${fromTicker} balance after verification! Wallet has ${latestBalanceFormatted.toFixed(6)} ${fromTicker} ` + `but trying to swap ${requestedFormatted.toFixed(6)} ${fromTicker}. ` + @@ -3027,14 +3055,20 @@ export const swapGmxTokensImpl = async ( ); } - console.log('✅ Token balance verified - wallet has sufficient funds and balance matches'); + // Update fromTokenAmount to use the final verified amount + const verifiedFromTokenAmount = finalSwapAmount; + + console.log('✅ Token balance verified - wallet has sufficient funds and balance matches', { + finalSwapAmount: verifiedFromTokenAmount.toString(), + finalSwapAmountFormatted: (Number(verifiedFromTokenAmount) / Math.pow(10, fromTokenData.decimals)).toFixed(6) + }); // Check and handle token allowance for SyntheticsRouter contract await approveTokenForContract( sdk, fromTicker, fromTokenData, - fromTokenAmount, + verifiedFromTokenAmount, "SyntheticsRouter" ); @@ -3042,7 +3076,7 @@ export const swapGmxTokensImpl = async ( sdk, fromTicker, fromTokenData, - fromTokenAmount, + verifiedFromTokenAmount, "ExchangeRouter" ); @@ -3060,7 +3094,7 @@ export const swapGmxTokensImpl = async ( // Try using the SDK's built-in swap method first try { const swapParams = { - fromAmount: fromTokenAmount, + fromAmount: verifiedFromTokenAmount, fromTokenAddress: fromTokenData.address, toTokenAddress: toTokenData.address, allowedSlippageBps: allowedSlippageBps, @@ -3117,7 +3151,7 @@ export const swapGmxTokensImpl = async ( await createSwapOrderTxn(sdk, { fromTokenAddress: fromTokenData.address, - fromTokenAmount: fromTokenAmount, + fromTokenAmount: verifiedFromTokenAmount, toTokenAddress: toTokenData.address, swapPath: swapPath, orderType: OrderType.MarketSwap,