Enhance ETH transfer logic in sendTokenImpl to reserve gas fees

- Updated sendTokenImpl to estimate gas costs for ETH transfers, ensuring sufficient balance is available for gas fees.
- Implemented gas estimation with fallback mechanisms for gas price and limit, improving reliability of ETH transactions.
- Adjusted the transfer amount based on available balance after accounting for estimated gas costs, providing clearer error messages for insufficient funds.
This commit is contained in:
2026-01-07 19:22:43 +07:00
parent 4a1e3c2231
commit fa66568ea2

View File

@@ -11,7 +11,7 @@ import {ARBITRUM} from '../../generated/gmxsdk/configs/chains.js'
import {TOKENS} from '../../generated/gmxsdk/configs/tokens.js' import {TOKENS} from '../../generated/gmxsdk/configs/tokens.js'
import {CONTRACTS} from '../../generated/gmxsdk/configs/contracts.js' import {CONTRACTS} from '../../generated/gmxsdk/configs/contracts.js'
import {getClientForAddress, getTokenDataFromTicker} from './gmx.js' import {getClientForAddress, getTokenDataFromTicker} from './gmx.js'
import {Address, erc20Abi} from 'viem' import {Address, erc20Abi, formatEther, parseEther} from 'viem'
import {Balance, Chain, Ticker} from '../../generated/ManagingApiTypes.js' import {Balance, Chain, Ticker} from '../../generated/ManagingApiTypes.js'
import {getCachedPrivySecrets} from './privy-secrets.js' import {getCachedPrivySecrets} from './privy-secrets.js'
@@ -1296,6 +1296,72 @@ export const sendTokenImpl = async (
const walletId = await getWalletIdFromAddress(senderAddress, fastify); const walletId = await getWalletIdFromAddress(senderAddress, fastify);
if (ticker === 'ETH') { if (ticker === 'ETH') {
// For ETH transfers, we need to reserve gas fees
// Get SDK to access public client for balance and gas estimation
const sdk = await getClientForAddress(senderAddress);
const balance = await sdk.publicClient.getBalance({ address: senderAddress as Address });
// Estimate gas for the transfer
// Use a test amount (1 ETH) to estimate gas, then scale if needed
const testAmount = parseEther('1');
let estimatedGasLimit: bigint;
try {
estimatedGasLimit = await sdk.publicClient.estimateGas({
to: recipientAddress as Address,
value: testAmount,
account: senderAddress as Address,
});
} catch (error) {
// If estimation fails, use a conservative default (21,000 is standard for simple transfers)
console.warn('Gas estimation failed, using default:', error);
estimatedGasLimit = 21000n;
}
// Get gas price
const feeData = await sdk.publicClient.estimateFeesPerGas({
type: 'legacy',
chain: sdk.chain,
});
let gasPrice = feeData.gasPrice || 0n;
// If gas price is 0 or unavailable, use a conservative fallback
// Arbitrum typically has gas prices around 0.1-1 gwei, so 1 gwei is a safe fallback
if (gasPrice === 0n) {
console.warn('Gas price estimation returned 0, using fallback of 1 gwei');
gasPrice = 1000000000n; // 1 gwei = 1,000,000,000 wei
}
// Calculate gas cost with 30% buffer for safety
const gasBufferMultiplier = 130n; // 30% buffer
const gasCost = (estimatedGasLimit * gasPrice * gasBufferMultiplier) / 100n;
// Calculate the maximum amount we can send (balance - gas cost)
const maxSendableAmount = balance > gasCost ? balance - gasCost : 0n;
// Use the minimum of requested amount and max sendable amount
const actualAmountToSend = amountBigInt > maxSendableAmount ? maxSendableAmount : amountBigInt;
if (actualAmountToSend <= 0n) {
const balanceEth = formatEther(balance);
const gasCostEth = formatEther(gasCost);
throw new Error(
`Insufficient ETH balance. Balance: ${balanceEth} ETH, ` +
`Estimated gas cost: ${gasCostEth} ETH. ` +
`Cannot send any amount as balance is less than required gas fees.`
);
}
// Log if we adjusted the amount
if (actualAmountToSend < amountBigInt) {
const requestedEth = formatEther(amountBigInt);
const sendingEth = formatEther(actualAmountToSend);
const gasCostEth = formatEther(gasCost);
console.log(
`⚠️ Adjusted ETH transfer amount: requested ${requestedEth} ETH, ` +
`sending ${sendingEth} ETH (reserved ${gasCostEth} ETH for gas fees)`
);
}
// Native ETH transfer: no allowance, no data, value is amount as hex string // Native ETH transfer: no allowance, no data, value is amount as hex string
const { hash } = await privy.wallets().ethereum().sendTransaction( const { hash } = await privy.wallets().ethereum().sendTransaction(
walletId, walletId,
@@ -1304,7 +1370,7 @@ export const sendTokenImpl = async (
params: { params: {
transaction: { transaction: {
to: recipientAddress as Address, to: recipientAddress as Address,
value: '0x' + amountBigInt.toString(16), // value in wei as hex string value: '0x' + actualAmountToSend.toString(16), // value in wei as hex string
chain_id: chainId, chain_id: chainId,
} }
}, },