refactor: Optimize gas fee calculations for Base chain transactions (#2613)
What changed, and why it matters
This commit refactors how gas fees are calculated for Base, Polygon, Ethereum, and Arbitrum wallets in Cake Wallet. It moves chain-specific priority fee logic out of shared code into each chain's wallet class, fixes a mismatch between the fee shown on the send screen and the fee shown on the final confirmation screen, and increases the safety buffer used when sending the entire balance on Base. There is no direct evidence this fixes an active exploit or security vulnerability, but it does reduce the risk of users accidentally overpaying or underpaying fees and improves consistency in fee display.
Treat as a routine refactor and fee-calculation accuracy improvement. Review the new Base buffer logic and fixed priority fees to ensure they remain reasonable under volatile network conditions. No urgent security response is indicated by the available evidence, but wallet teams should verify that the removed Polygon inline logic and the Base buffer change do not cause transaction underpayment or stuck transactions.
Security signals we found
Refactored fee estimation logic to reduce chain-specific branching in shared code
Moved Polygon minimum priority fee handling into Polygon-specific wallet class
Added Base-specific fixed priority fee values
Increased Base 'send all' gas buffer from 1%/1000 wei to 10%/10000 wei
Removed re-estimation loop in Base 'send all' path
Fixed fee display mismatch between send screen and swipe-to-send bottom sheet
No explicit security advisory, CVE, or exploit disclosure in commit or references
Evidence from the diff
The patch introduces an abstract getTotalPriorityFee(EVMChainTransactionPriority) method on EVMChainWalletBase and implements it per chain: Arbitrum returns 0, Base returns fixed mwei values (1/3/5), Ethereum returns priority.tip in gwei, and Polygon returns a 25 gwei minimum plus tiered additions. The shared fee-estimation logic is simplified by removing the inline Polygon special case and the hardcoded EtherAmount.fromInt(EtherUnit.gwei, priority.tip) call. It also changes the Base ‘send all’ path: instead of re-estimating gas and taking the higher of two estimates, it now applies a single 10% or 10,000 wei buffer. A formatting change extends the fee string from 10 to 18 characters. The commit message frames this as a refactor/optimization and a UI fee-consistency fix, not a security fix.
Changed components
cw_arbitrum/lib/arbitrum_wallet.dartcw_base/lib/base_wallet.dartcw_ethereum/lib/ethereum_wallet.dartcw_evm/lib/evm_chain_transaction_info.dartcw_evm/lib/evm_chain_wallet.dartcw_polygon/lib/polygon_wallet.dartInspect captured patch +77 / −68
diff --git a/cw_arbitrum/lib/arbitrum_wallet.dart b/cw_arbitrum/lib/arbitrum_wallet.dart
index 6181f11a..39680566 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<void> 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 f3e5878a..a54ace64 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<void> 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 edf10117..a77bff6a 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 d016646a..781dd573 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<String, dynamic> toJson() => {
diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart
index 30ce827f..0db8781c 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 5b587992..e77c112a 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<void> 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));
}
}
}
Why this scored 28/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.