What changed, and why it matters
This commit changes how Ethereum transaction fees are calculated in Stack Wallet. Previously, the wallet set the maximum base fee equal to the current network base fee, which could cause transactions to fail if network fees rose even slightly. The fix doubles the maximum base fee for preset fee options to give transactions more headroom. It also removes a check that prevented the priority fee from exceeding the maximum base fee. The commit adds extra logging for fee values but does not otherwise change security-sensitive code.
Review whether removing the priorityFee > maxBaseFee check is intentional and safe; under EIP-1559, maxFeePerGas must cover both base and priority, so the remaining maxBaseFee field name may be misleading. Consider adding validation that maxFeePerGas >= baseFee + priorityFee, or clarify naming. Otherwise, treat as a routine fee-calculation improvement and monitor for stuck transactions.
Security signals we found
EIP-1559 max base fee calculation changed from current base fee to 2x current base fee
Removal of priorityFee > maxBaseFee validation check
Fee object values now logged at trace level
No explicit security disclosure or CVE referenced in commit
Evidence from the diff
The patch modifies EIP-1559 fee construction in EthereumWallet._buildTx. Before: maxBaseFee was set to the current suggested base fee, and priorityFee was derived by subtracting baseFee from the chosen fee-rate tier (fast/medium/slow), clamped to zero. After: for non-custom presets, maxBaseFee is now baseFee * 2. The priorityFee calculation is refactored into a switch expression but remains logically equivalent. The guard if (priorityFee > maxBaseFee) throw is removed. The custom fee path still uses user-supplied maxBaseFeeWei and priorityFeeWei. The EthereumAPI fee-fetching method now logs the returned EthFeeObject via Logging.instance.t. A toString() was added to EthFeeObject. An unused import (fee_rate_type_enum.dart) was removed and an import was reordered.
Changed components
lib/wallets/wallet/impl/ethereum_wallet.dartlib/services/ethereum/ethereum_api.dartlib/models/paymint/fee_object_model.dartInspect captured patch +39 / −36
diff --git a/lib/models/paymint/fee_object_model.dart b/lib/models/paymint/fee_object_model.dart
index 0c00f80..888f8ed 100644
--- a/lib/models/paymint/fee_object_model.dart
+++ b/lib/models/paymint/fee_object_model.dart
@@ -44,4 +44,16 @@ class EthFeeObject extends FeeObject {
required super.medium,
required super.slow,
});
+
+ @override
+ String toString() =>
+ "{\n"
+ " fast: $fast,\n"
+ " medium: $medium,\n"
+ " slow: $slow,\n"
+ " suggestBaseFee: $suggestBaseFee,\n"
+ " numberOfBlocksFast: $numberOfBlocksFast,\n"
+ " numberOfBlocksAverage: $numberOfBlocksAverage,\n"
+ " numberOfBlocksSlow: $numberOfBlocksSlow,\n"
+ "}";
}
diff --git a/lib/services/ethereum/ethereum_api.dart b/lib/services/ethereum/ethereum_api.dart
index efffc51..cf00ce3 100644
--- a/lib/services/ethereum/ethereum_api.dart
+++ b/lib/services/ethereum/ethereum_api.dart
@@ -256,7 +256,7 @@ abstract class EthereumAPI {
throw response.exception!;
}
- return EthFeeObject(
+ final fees = EthFeeObject(
suggestBaseFee: response.value!.suggestBaseFee.shift(9).toBigInt(),
numberOfBlocksFast: response.value!.numberOfBlocksFast,
numberOfBlocksAverage: response.value!.numberOfBlocksAverage,
@@ -265,6 +265,8 @@ abstract class EthereumAPI {
medium: response.value!.average.shift(9).toBigInt(),
slow: response.value!.low.shift(9).toBigInt(),
);
+ Logging.instance.t(fees);
+ return fees;
}
static Future<void> _addContractInfoToServer(String contractAddress) async {
diff --git a/lib/wallets/wallet/impl/ethereum_wallet.dart b/lib/wallets/wallet/impl/ethereum_wallet.dart
index 8291106..354d7fe 100644
--- a/lib/wallets/wallet/impl/ethereum_wallet.dart
+++ b/lib/wallets/wallet/impl/ethereum_wallet.dart
@@ -5,9 +5,9 @@ import 'package:decimal/decimal.dart';
import 'package:ethereum_addresses/ethereum_addresses.dart';
import 'package:http/http.dart';
import 'package:isar_community/isar.dart';
+import 'package:wallet/wallet.dart' as eth_wallet;
import 'package:web3dart/json_rpc.dart' show RPCError;
import 'package:web3dart/web3dart.dart' as web3;
-import 'package:wallet/wallet.dart' as eth_wallet;
import '../../../dto/ethereum/eth_tx_dto.dart';
import '../../../models/balance.dart';
@@ -21,7 +21,6 @@ import '../../../services/ethereum/ethereum_api.dart';
import '../../../services/event_bus/events/global/updated_in_background_event.dart';
import '../../../services/event_bus/global_event_bus.dart';
import '../../../utilities/amount/amount.dart';
-import '../../../utilities/enums/fee_rate_type_enum.dart';
import '../../../utilities/eth_commons.dart';
import '../../../utilities/logger.dart';
import '../../crypto_currency/crypto_currency.dart';
@@ -218,7 +217,9 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
final addressHex = (await getCurrentReceivingAddress())!.value;
final address = eth_wallet.EthereumAddress.fromHex(addressHex);
- final eth_wallet.EtherAmount ethBalance = await client.getBalance(address);
+ final eth_wallet.EtherAmount ethBalance = await client.getBalance(
+ address,
+ );
final balance = Balance(
total: Amount(
rawValue: ethBalance.getInWei,
@@ -448,9 +449,9 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
required TxData txData,
required eth_wallet.EthereumAddress myWeb3Address,
}) async {
- if (txData.feeRateType == null) throw Exception("Missing fee rate type.");
- if (txData.feeRateType == FeeRateType.custom &&
- txData.ethEIP1559Fee == null) {
+ final feeRateType = txData.feeRateType;
+ if (feeRateType == null) throw Exception("Missing fee rate type.");
+ if (feeRateType == .custom && txData.ethEIP1559Fee == null) {
throw Exception("Missing custom EIP-1559 values.");
}
@@ -466,37 +467,25 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
);
final feeObject = await fees;
- final baseFee = feeObject.suggestBaseFee;
- BigInt maxBaseFee = baseFee;
- BigInt priorityFee;
-
- switch (txData.feeRateType!) {
- case FeeRateType.fast:
- priorityFee = feeObject.fast - baseFee;
- if (priorityFee.isNegative) priorityFee = BigInt.zero;
- break;
-
- case FeeRateType.average:
- priorityFee = feeObject.medium - baseFee;
- if (priorityFee.isNegative) priorityFee = BigInt.zero;
- break;
-
- case FeeRateType.slow:
- priorityFee = feeObject.slow - baseFee;
- if (priorityFee.isNegative) priorityFee = BigInt.zero;
- break;
-
- case FeeRateType.custom:
- priorityFee = txData.ethEIP1559Fee!.priorityFeeWei;
- maxBaseFee = txData.ethEIP1559Fee!.maxBaseFeeWei;
- break;
- }
+ final BigInt baseFee = feeObject.suggestBaseFee;
+
+ // Presets get 2x headroom since base fee can rise 12.5% per block.
+ final BigInt maxBaseFee = feeRateType == .custom
+ ? txData.ethEIP1559Fee!.maxBaseFeeWei
+ : baseFee * BigInt.two;
+
+ final BigInt rawPriority = switch (feeRateType) {
+ .fast => feeObject.fast - baseFee,
+ .average => feeObject.medium - baseFee,
+ .slow => feeObject.slow - baseFee,
+ .custom => txData.ethEIP1559Fee!.priorityFeeWei,
+ };
+ final BigInt priorityFee = rawPriority.isNegative
+ ? BigInt.zero
+ : rawPriority;
if (baseFee > maxBaseFee) {
- throw Exception("Base cannot be greater than max base fee");
- }
- if (priorityFee > maxBaseFee) {
- throw Exception("Priority fee cannot be greater than max base fee");
+ throw Exception("Max base fee is below the current network base fee.");
}
return (
Why this scored 41/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.