From 68dd3f2809b0aac9fd78d9f6e5b37d7f01c5516a Mon Sep 17 00:00:00 2001 From: David Adegoke <64401859+Blazebrain@users.noreply.github.com> Date: Thu, 6 Nov 2025 15:10:36 +0100 Subject: [PATCH] refactor: Optimize gas fee calculations for Base chain transactions (#2613) * refactor: Optimize gas fee calculations for Base chain transactions * feat: Added an abstract method for priority fee calculations in EVMChainWallet class, and implemented it across the evm wallets. * fix: Estimated fee on send screen for base wallet different from fee on swipe to send bottomsheet --- cw_arbitrum/lib/arbitrum_wallet.dart | 4 ++ cw_base/lib/base_wallet.dart | 12 ++++ cw_ethereum/lib/ethereum_wallet.dart | 13 +++- cw_evm/lib/evm_chain_transaction_info.dart | 2 +- cw_evm/lib/evm_chain_wallet.dart | 82 +++++----------------- cw_polygon/lib/polygon_wallet.dart | 32 ++++++++- 6 files changed, 77 insertions(+), 68 deletions(-) diff --git a/cw_arbitrum/lib/arbitrum_wallet.dart b/cw_arbitrum/lib/arbitrum_wallet.dart index 6181f11a2..39680566f 100644 --- a/cw_arbitrum/lib/arbitrum_wallet.dart +++ b/cw_arbitrum/lib/arbitrum_wallet.dart @@ -15,6 +15,7 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; @@ -34,6 +35,9 @@ class ArbitrumWallet extends EVMChainWallet { @override bool get hasPriorityFee => false; + @override + int getTotalPriorityFee(EVMChainTransactionPriority priority) => 0; + @override Future initErc20TokensBox() async { final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.arbitrumBoxName}"; diff --git a/cw_base/lib/base_wallet.dart b/cw_base/lib/base_wallet.dart index f3e5878a4..a54ace643 100644 --- a/cw_base/lib/base_wallet.dart +++ b/cw_base/lib/base_wallet.dart @@ -15,8 +15,10 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; +import 'package:web3dart/web3dart.dart'; class BaseWallet extends EVMChainWallet { BaseWallet({ @@ -31,6 +33,16 @@ class BaseWallet extends EVMChainWallet { super.passphrase, }) : super(nativeCurrency: CryptoCurrency.baseEth); + @override + int getTotalPriorityFee(EVMChainTransactionPriority priority) { + return switch (priority) { + EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(), + EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(), + EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), + _ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(), + }; + } + @override Future initErc20TokensBox() async { final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.baseBoxName}"; diff --git a/cw_ethereum/lib/ethereum_wallet.dart b/cw_ethereum/lib/ethereum_wallet.dart index edf101171..a77bff6a3 100644 --- a/cw_ethereum/lib/ethereum_wallet.dart +++ b/cw_ethereum/lib/ethereum_wallet.dart @@ -15,8 +15,10 @@ import 'package:cw_ethereum/ethereum_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; +import 'package:web3dart/web3dart.dart'; class EthereumWallet extends EVMChainWallet { EthereumWallet({ @@ -31,6 +33,11 @@ class EthereumWallet extends EVMChainWallet { super.passphrase, }) : super(nativeCurrency: CryptoCurrency.eth); + @override + int getTotalPriorityFee(EVMChainTransactionPriority priority) { + return EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + } + @override void addInitialTokens() { final initialErc20Tokens = DefaultEthereumErc20Tokens().initialErc20Tokens; @@ -38,9 +45,11 @@ class EthereumWallet extends EVMChainWallet { for (final token in initialErc20Tokens) { if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) { evmChainErc20TokensBox.put(token.contractAddress, token); - } else { // update existing token + } else { + // update existing token final existingToken = evmChainErc20TokensBox.get(token.contractAddress); - evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled)); + evmChainErc20TokensBox.put( + token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled)); } } } diff --git a/cw_evm/lib/evm_chain_transaction_info.dart b/cw_evm/lib/evm_chain_transaction_info.dart index d016646ae..781dd5738 100644 --- a/cw_evm/lib/evm_chain_transaction_info.dart +++ b/cw_evm/lib/evm_chain_transaction_info.dart @@ -61,7 +61,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo { @override String feeFormatted() { final amount = (ethFee / BigInt.from(10).pow(18)).toString(); - return '${amount.substring(0, min(10, amount.length))} $feeCurrency'; + return '${amount.substring(0, min(18, amount.length))} $feeCurrency'; } Map toJson() => { diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 30ce827f3..0db8781c7 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -296,7 +296,7 @@ abstract class EVMChainWalletBase int priorityFee = 0; if (hasPriorityFee) { if (priority is EVMChainTransactionPriority) { - priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + priorityFee = getTotalPriorityFee(priority); } } @@ -324,7 +324,7 @@ abstract class EVMChainWalletBase int priorityFee = 0; if (hasPriorityFee) { if (priority is EVMChainTransactionPriority) { - priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + priorityFee = getTotalPriorityFee(priority); } } @@ -348,6 +348,8 @@ abstract class EVMChainWalletBase } } + int getTotalPriorityFee(EVMChainTransactionPriority priority); + /// Allows more customization to the fetch estimatedFees flow. /// /// We are able to pass in: @@ -365,7 +367,7 @@ abstract class EVMChainWalletBase int priorityFee = 0; if (hasPriorityFee && priority != null) { if (priority is EVMChainTransactionPriority) { - priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt(); + priorityFee = getTotalPriorityFee(priority); } } @@ -375,46 +377,10 @@ abstract class EVMChainWalletBase int maxFeePerGas; int adjustedGasPrice; - bool isPolygon = _client.chainId == 137; - - if (gasBaseFee != null) { - // MaxFeePerGas with EIP1559; - maxFeePerGas = gasBaseFee + priorityFee; - } else { - // MaxFeePerGas with gasPrice - maxFeePerGas = gasPrice + priorityFee; - } + maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee); adjustedGasPrice = maxFeePerGas; - // Polygon has a minimum priority fee of 25 gwei - if (isPolygon) { - int minPriorityFee = 25; - int minPriorityFeeWei = - EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt(); - - // Calculate user selected priority-based additional fee on top of minimum - int additionalPriorityFee = 0; - switch (priority) { - case EVMChainTransactionPriority.slow: - // We use minimum priority fee only - additionalPriorityFee = 0; - break; - case EVMChainTransactionPriority.medium: - // We add 15 gwei on top of minimum - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt(); - break; - case EVMChainTransactionPriority.fast: - // We add 35 gwei on top of minimum - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt(); - break; - } - - int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee; - adjustedGasPrice = gasPrice + totalPriorityFee; - maxFeePerGas = gasPrice + totalPriorityFee; - } - final estimatedGas = await _client.getEstimatedGasUnitsForTransaction( contractAddress: contractAddress, senderAddress: _evmChainPrivateKey.address, @@ -561,7 +527,9 @@ abstract class EVMChainWalletBase EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0); totalAmount = parseFixed( - EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent), exponent); + EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent), + exponent, + ); } if (output.sendAll && transactionCurrency is Erc20Token) { @@ -581,32 +549,20 @@ abstract class EVMChainWalletBase if (output.sendAll && transactionCurrency is! Erc20Token) { if (_client.chainId == 8453) { - // Add a safety buffer for Base chain to account for gas price fluctuations - // Use 1% buffer or minimum 1000 wei (0.000001 ETH), whichever is higher - final gasBufferPercent = - estimatedFeesForTransaction * BigInt.from(101) ~/ BigInt.from(100); - final gasBufferMin = estimatedFeesForTransaction + BigInt.from(1000); + // Applying a small buffer to account for gas price fluctuations + // 10% or minimum 10,000 wei, whichever is higher + final refinedGasFee = estimatedFeesForTransaction; + final gasBufferPercent = refinedGasFee * BigInt.from(110) ~/ BigInt.from(100); + final gasBufferMin = refinedGasFee + BigInt.from(10000); final gasBuffer = gasBufferPercent > gasBufferMin ? gasBufferPercent : gasBufferMin; + // Using the buffered fee for the final amount totalAmount = (currencyBalance.balance - gasBuffer); - - // Re-estimate gas with the correct amount to get more accurate gas estimation - final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction( - amount: totalAmount, - receivingAddressHex: toAddress, - priority: _credentials.priority, - contractAddress: contractAddress, - ); - - // Use the higher of the two gas estimations to be safe - final refinedGasFee = BigInt.from(refinedGasFeesModel.estimatedGasFee); - estimatedFeesForTransaction = refinedGasFee > gasBuffer ? refinedGasFee : gasBuffer; - estimatedGasUnitsForTransaction = refinedGasFeesModel.estimatedGasUnits; - maxFeePerGasForTransaction = refinedGasFeesModel.maxFeePerGas; + estimatedFeesForTransaction = gasBuffer; + } else { + // Calculating the final amount with the estimated gas fee + totalAmount = (currencyBalance.balance - estimatedFeesForTransaction); } - - // Final amount calculation with the higher gas estimation - totalAmount = (currencyBalance.balance - estimatedFeesForTransaction); } // check the fees on the base currency diff --git a/cw_polygon/lib/polygon_wallet.dart b/cw_polygon/lib/polygon_wallet.dart index 5b587992f..e77c112a3 100644 --- a/cw_polygon/lib/polygon_wallet.dart +++ b/cw_polygon/lib/polygon_wallet.dart @@ -11,12 +11,14 @@ import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_evm/evm_chain_transaction_history.dart'; import 'package:cw_evm/evm_chain_transaction_info.dart'; import 'package:cw_evm/evm_chain_transaction_model.dart'; +import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_wallet.dart'; import 'package:cw_evm/evm_erc20_balance.dart'; import 'package:cw_polygon/default_polygon_erc20_tokens.dart'; import 'package:cw_polygon/polygon_client.dart'; import 'package:cw_polygon/polygon_transaction_history.dart'; import 'package:cw_polygon/polygon_transaction_info.dart'; +import 'package:web3dart/web3dart.dart'; class PolygonWallet extends EVMChainWallet { PolygonWallet({ @@ -31,6 +33,30 @@ class PolygonWallet extends EVMChainWallet { super.passphrase, }) : super(nativeCurrency: CryptoCurrency.maticpoly); + @override + int getTotalPriorityFee(EVMChainTransactionPriority priority) { + // Polygon has a minimum priority fee of 25 gwei + + int minPriorityFee = 25; + int minPriorityFeeWei = EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt(); + + // Calculate user selected priority-based additional fee on top of minimum + int additionalPriorityFee = 0; + switch (priority) { + case EVMChainTransactionPriority.slow: + additionalPriorityFee = 0; + break; + case EVMChainTransactionPriority.medium: + additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt(); + break; + case EVMChainTransactionPriority.fast: + additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt(); + break; + } + + return (minPriorityFeeWei + additionalPriorityFee); + } + @override Future initErc20TokensBox() async { final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}"; @@ -48,9 +74,11 @@ class PolygonWallet extends EVMChainWallet { for (final token in initialErc20Tokens) { if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) { evmChainErc20TokensBox.put(token.contractAddress, token); - } else { // update existing token + } else { + // update existing token final existingToken = evmChainErc20TokensBox.get(token.contractAddress); - evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled)); + evmChainErc20TokensBox.put( + token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled)); } } }