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

David Adegoke committed Nov 6, 2025 at 15:10 UTC 68dd3f2809b0aac9fd78d9f6e5b37d7f01c5516a
6 files changed +77 -68
cw_arbitrum/lib/arbitrum_wallet.dart
+4
@@ -15,6 +15,7 @@ import 'package:cw_core/wallet_keys_file.dart';
15 import 'package:cw_evm/evm_chain_transaction_history.dart';
16 import 'package:cw_evm/evm_chain_transaction_info.dart';
17 import 'package:cw_evm/evm_chain_transaction_model.dart';
18 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
19 import 'package:cw_evm/evm_chain_wallet.dart';
20 import 'package:cw_evm/evm_erc20_balance.dart';
21
@@ -34,6 +35,9 @@ class ArbitrumWallet extends EVMChainWallet {
35 @override
36 bool get hasPriorityFee => false;
37
38 + @override
39 + int getTotalPriorityFee(EVMChainTransactionPriority priority) => 0;
40 +
41 @override
42 Future<void> initErc20TokensBox() async {
43 final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.arbitrumBoxName}";
cw_base/lib/base_wallet.dart
+12
@@ -15,8 +15,10 @@ import 'package:cw_core/wallet_keys_file.dart';
15 import 'package:cw_evm/evm_chain_transaction_history.dart';
16 import 'package:cw_evm/evm_chain_transaction_info.dart';
17 import 'package:cw_evm/evm_chain_transaction_model.dart';
18 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
19 import 'package:cw_evm/evm_chain_wallet.dart';
20 import 'package:cw_evm/evm_erc20_balance.dart';
21 +import 'package:web3dart/web3dart.dart';
22
23 class BaseWallet extends EVMChainWallet {
24 BaseWallet({
@@ -31,6 +33,16 @@ class BaseWallet extends EVMChainWallet {
33 super.passphrase,
34 }) : super(nativeCurrency: CryptoCurrency.baseEth);
35
36 + @override
37 + int getTotalPriorityFee(EVMChainTransactionPriority priority) {
38 + return switch (priority) {
39 + EVMChainTransactionPriority.fast => EtherAmount.fromInt(EtherUnit.mwei, 5).getInWei.toInt(),
40 + EVMChainTransactionPriority.medium => EtherAmount.fromInt(EtherUnit.mwei, 3).getInWei.toInt(),
41 + EVMChainTransactionPriority.slow => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
42 + _ => EtherAmount.fromInt(EtherUnit.mwei, 1).getInWei.toInt(),
43 + };
44 + }
45 +
46 @override
47 Future<void> initErc20TokensBox() async {
48 final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${Erc20Token.baseBoxName}";
cw_ethereum/lib/ethereum_wallet.dart
+11 -2
@@ -15,8 +15,10 @@ import 'package:cw_ethereum/ethereum_transaction_info.dart';
15 import 'package:cw_evm/evm_chain_transaction_history.dart';
16 import 'package:cw_evm/evm_chain_transaction_info.dart';
17 import 'package:cw_evm/evm_chain_transaction_model.dart';
18 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
19 import 'package:cw_evm/evm_chain_wallet.dart';
20 import 'package:cw_evm/evm_erc20_balance.dart';
21 +import 'package:web3dart/web3dart.dart';
22
23 class EthereumWallet extends EVMChainWallet {
24 EthereumWallet({
@@ -31,6 +33,11 @@ class EthereumWallet extends EVMChainWallet {
33 super.passphrase,
34 }) : super(nativeCurrency: CryptoCurrency.eth);
35
36 + @override
37 + int getTotalPriorityFee(EVMChainTransactionPriority priority) {
38 + return EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
39 + }
40 +
41 @override
42 void addInitialTokens() {
43 final initialErc20Tokens = DefaultEthereumErc20Tokens().initialErc20Tokens;
@@ -38,9 +45,11 @@ class EthereumWallet extends EVMChainWallet {
45 for (final token in initialErc20Tokens) {
46 if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
47 evmChainErc20TokensBox.put(token.contractAddress, token);
41 - } else { // update existing token
48 + } else {
49 + // update existing token
50 final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
43 - evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
51 + evmChainErc20TokensBox.put(
52 + token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
53 }
54 }
55 }
cw_evm/lib/evm_chain_transaction_info.dart
+1 -1
@@ -61,7 +61,7 @@ abstract class EVMChainTransactionInfo extends TransactionInfo {
61 @override
62 String feeFormatted() {
63 final amount = (ethFee / BigInt.from(10).pow(18)).toString();
64 - return '${amount.substring(0, min(10, amount.length))} $feeCurrency';
64 + return '${amount.substring(0, min(18, amount.length))} $feeCurrency';
65 }
66
67 Map<String, dynamic> toJson() => {
cw_evm/lib/evm_chain_wallet.dart
+19 -63
@@ -296,7 +296,7 @@ abstract class EVMChainWalletBase
296 int priorityFee = 0;
297 if (hasPriorityFee) {
298 if (priority is EVMChainTransactionPriority) {
299 - priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
299 + priorityFee = getTotalPriorityFee(priority);
300 }
301 }
302
@@ -324,7 +324,7 @@ abstract class EVMChainWalletBase
324 int priorityFee = 0;
325 if (hasPriorityFee) {
326 if (priority is EVMChainTransactionPriority) {
327 - priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
327 + priorityFee = getTotalPriorityFee(priority);
328 }
329 }
330
@@ -348,6 +348,8 @@ abstract class EVMChainWalletBase
348 }
349 }
350
351 + int getTotalPriorityFee(EVMChainTransactionPriority priority);
352 +
353 /// Allows more customization to the fetch estimatedFees flow.
354 ///
355 /// We are able to pass in:
@@ -365,7 +367,7 @@ abstract class EVMChainWalletBase
367 int priorityFee = 0;
368 if (hasPriorityFee && priority != null) {
369 if (priority is EVMChainTransactionPriority) {
368 - priorityFee = EtherAmount.fromInt(EtherUnit.gwei, priority.tip).getInWei.toInt();
370 + priorityFee = getTotalPriorityFee(priority);
371 }
372 }
373
@@ -375,46 +377,10 @@ abstract class EVMChainWalletBase
377 int maxFeePerGas;
378 int adjustedGasPrice;
379
378 - bool isPolygon = _client.chainId == 137;
379 -
380 - if (gasBaseFee != null) {
381 - // MaxFeePerGas with EIP1559;
382 - maxFeePerGas = gasBaseFee + priorityFee;
383 - } else {
384 - // MaxFeePerGas with gasPrice
385 - maxFeePerGas = gasPrice + priorityFee;
386 - }
380 + maxFeePerGas = gasBaseFee != null ? (gasBaseFee + priorityFee) : (gasPrice + priorityFee);
381
382 adjustedGasPrice = maxFeePerGas;
383
390 - // Polygon has a minimum priority fee of 25 gwei
391 - if (isPolygon) {
392 - int minPriorityFee = 25;
393 - int minPriorityFeeWei =
394 - EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
395 -
396 - // Calculate user selected priority-based additional fee on top of minimum
397 - int additionalPriorityFee = 0;
398 - switch (priority) {
399 - case EVMChainTransactionPriority.slow:
400 - // We use minimum priority fee only
401 - additionalPriorityFee = 0;
402 - break;
403 - case EVMChainTransactionPriority.medium:
404 - // We add 15 gwei on top of minimum
405 - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
406 - break;
407 - case EVMChainTransactionPriority.fast:
408 - // We add 35 gwei on top of minimum
409 - additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
410 - break;
411 - }
412 -
413 - int totalPriorityFee = minPriorityFeeWei + additionalPriorityFee;
414 - adjustedGasPrice = gasPrice + totalPriorityFee;
415 - maxFeePerGas = gasPrice + totalPriorityFee;
416 - }
417 -
384 final estimatedGas = await _client.getEstimatedGasUnitsForTransaction(
385 contractAddress: contractAddress,
386 senderAddress: _evmChainPrivateKey.address,
@@ -561,7 +527,9 @@ abstract class EVMChainWalletBase
527 EVMChainFormatter.parseEVMChainAmountToDouble(output.formattedCryptoAmount ?? 0);
528
529 totalAmount = parseFixed(
564 - EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent), exponent);
530 + EVMChainFormatter.truncateDecimals(totalOriginalAmount.toString(), exponent),
531 + exponent,
532 + );
533 }
534
535 if (output.sendAll && transactionCurrency is Erc20Token) {
@@ -581,32 +549,20 @@ abstract class EVMChainWalletBase
549
550 if (output.sendAll && transactionCurrency is! Erc20Token) {
551 if (_client.chainId == 8453) {
584 - // Add a safety buffer for Base chain to account for gas price fluctuations
585 - // Use 1% buffer or minimum 1000 wei (0.000001 ETH), whichever is higher
586 - final gasBufferPercent =
587 - estimatedFeesForTransaction * BigInt.from(101) ~/ BigInt.from(100);
588 - final gasBufferMin = estimatedFeesForTransaction + BigInt.from(1000);
552 + // Applying a small buffer to account for gas price fluctuations
553 + // 10% or minimum 10,000 wei, whichever is higher
554 + final refinedGasFee = estimatedFeesForTransaction;
555 + final gasBufferPercent = refinedGasFee * BigInt.from(110) ~/ BigInt.from(100);
556 + final gasBufferMin = refinedGasFee + BigInt.from(10000);
557 final gasBuffer = gasBufferPercent > gasBufferMin ? gasBufferPercent : gasBufferMin;
558
559 + // Using the buffered fee for the final amount
560 totalAmount = (currencyBalance.balance - gasBuffer);
592 -
593 - // Re-estimate gas with the correct amount to get more accurate gas estimation
594 - final refinedGasFeesModel = await calculateActualEstimatedFeeForCreateTransaction(
595 - amount: totalAmount,
596 - receivingAddressHex: toAddress,
597 - priority: _credentials.priority,
598 - contractAddress: contractAddress,
599 - );
600 -
601 - // Use the higher of the two gas estimations to be safe
602 - final refinedGasFee = BigInt.from(refinedGasFeesModel.estimatedGasFee);
603 - estimatedFeesForTransaction = refinedGasFee > gasBuffer ? refinedGasFee : gasBuffer;
604 - estimatedGasUnitsForTransaction = refinedGasFeesModel.estimatedGasUnits;
605 - maxFeePerGasForTransaction = refinedGasFeesModel.maxFeePerGas;
561 + estimatedFeesForTransaction = gasBuffer;
562 + } else {
563 + // Calculating the final amount with the estimated gas fee
564 + totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
565 }
607 -
608 - // Final amount calculation with the higher gas estimation
609 - totalAmount = (currencyBalance.balance - estimatedFeesForTransaction);
566 }
567
568 // check the fees on the base currency
cw_polygon/lib/polygon_wallet.dart
+30 -2
@@ -11,12 +11,14 @@ import 'package:cw_core/wallet_keys_file.dart';
11 import 'package:cw_evm/evm_chain_transaction_history.dart';
12 import 'package:cw_evm/evm_chain_transaction_info.dart';
13 import 'package:cw_evm/evm_chain_transaction_model.dart';
14 +import 'package:cw_evm/evm_chain_transaction_priority.dart';
15 import 'package:cw_evm/evm_chain_wallet.dart';
16 import 'package:cw_evm/evm_erc20_balance.dart';
17 import 'package:cw_polygon/default_polygon_erc20_tokens.dart';
18 import 'package:cw_polygon/polygon_client.dart';
19 import 'package:cw_polygon/polygon_transaction_history.dart';
20 import 'package:cw_polygon/polygon_transaction_info.dart';
21 +import 'package:web3dart/web3dart.dart';
22
23 class PolygonWallet extends EVMChainWallet {
24 PolygonWallet({
@@ -31,6 +33,30 @@ class PolygonWallet extends EVMChainWallet {
33 super.passphrase,
34 }) : super(nativeCurrency: CryptoCurrency.maticpoly);
35
36 + @override
37 + int getTotalPriorityFee(EVMChainTransactionPriority priority) {
38 + // Polygon has a minimum priority fee of 25 gwei
39 +
40 + int minPriorityFee = 25;
41 + int minPriorityFeeWei = EtherAmount.fromInt(EtherUnit.gwei, minPriorityFee).getInWei.toInt();
42 +
43 + // Calculate user selected priority-based additional fee on top of minimum
44 + int additionalPriorityFee = 0;
45 + switch (priority) {
46 + case EVMChainTransactionPriority.slow:
47 + additionalPriorityFee = 0;
48 + break;
49 + case EVMChainTransactionPriority.medium:
50 + additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 15).getInWei.toInt();
51 + break;
52 + case EVMChainTransactionPriority.fast:
53 + additionalPriorityFee = EtherAmount.fromInt(EtherUnit.gwei, 35).getInWei.toInt();
54 + break;
55 + }
56 +
57 + return (minPriorityFeeWei + additionalPriorityFee);
58 + }
59 +
60 @override
61 Future<void> initErc20TokensBox() async {
62 final boxName = "${walletInfo.name.replaceAll(" ", "_")}_ ${Erc20Token.polygonBoxName}";
@@ -48,9 +74,11 @@ class PolygonWallet extends EVMChainWallet {
74 for (final token in initialErc20Tokens) {
75 if (!evmChainErc20TokensBox.containsKey(token.contractAddress)) {
76 evmChainErc20TokensBox.put(token.contractAddress, token);
51 - } else { // update existing token
77 + } else {
78 + // update existing token
79 final existingToken = evmChainErc20TokensBox.get(token.contractAddress);
53 - evmChainErc20TokensBox.put(token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
80 + evmChainErc20TokensBox.put(
81 + token.contractAddress, Erc20Token.copyWith(token, enabled: existingToken!.enabled));
82 }
83 }
84 }