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
}