Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 32 additions & 8 deletions lib/wallets/wallet/impl/ethereum_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,7 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
int nonce,
BigInt chainId,
BigInt baseFee,
BigInt maxBaseFee,
BigInt maxFeePerGas,
BigInt priorityFee,
})
>
Expand All @@ -465,9 +465,21 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
atBlock: const web3.BlockNum.pending(),
);

// A miner charges: baseFee + min(priorityFee, maxFeePerGas - baseFee).
// So maxFeePerGas has to cover the base fee AND leave room for the tip on
// top. If maxFeePerGas only equals the base fee, that min() is 0 and the
// miner is tipped nothing no matter what priorityFee says.
final feeObject = await fees;

// The oracle's slow/medium/fast are *total* prices (base + tip), so the tip
// for a tier is that tier minus the base fee.
final baseFee = feeObject.suggestBaseFee;
BigInt maxBaseFee = baseFee;

// Doubled to leave headroom: the base fee can climb 12.5% per block, and by
// the time this tx is mined it will not be the base fee we just read. Any
// headroom we don't use is not charged, so this is free insurance against
// the tx becoming unmineable a few blocks from now.
BigInt maxBaseFee = baseFee * BigInt.two;
BigInt priorityFee;

switch (txData.feeRateType!) {
Expand All @@ -487,23 +499,32 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
break;

case FeeRateType.custom:
// Not doubled: the user set this cap on purpose, so honour it exactly.
priorityFee = txData.ethEIP1559Fee!.priorityFeeWei;
maxBaseFee = txData.ethEIP1559Fee!.maxBaseFeeWei;
break;
}

// Only reachable via custom fees now, since the tiers above always give
// maxBaseFee twice the base fee. It catches a user capping the base fee
// below what the network currently charges, which can never be mined.
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");
}

// There used to be a second check here rejecting priorityFee > maxBaseFee.
// It compared a tip against a base fee ceiling, which are unrelated
// quantities, and it fired on the fast tier every time the network's tip
// was larger than its base fee (routine now that base fees are sub-gwei).
// The real EIP-1559 rule is maxFeePerGas >= priorityFee, which the sum
// below satisfies by construction, so no check is needed.

return (
nonce: nonce,
chainId: chainId,
baseFee: baseFee,
maxBaseFee: maxBaseFee,
// Base fee ceiling plus the tip, so the tip has somewhere to fit.
maxFeePerGas: maxBaseFee + priorityFee,
priorityFee: priorityFee,
);
}
Expand Down Expand Up @@ -533,17 +554,20 @@ class EthereumWallet extends Bip39Wallet with PrivateKeyInterface {
nonce: prep.nonce,
maxFeePerGas: eth_wallet.EtherAmount.fromBigInt(
eth_wallet.EtherUnit.wei,
prep.maxBaseFee,
prep.maxFeePerGas,
),
maxPriorityFeePerGas: eth_wallet.EtherAmount.fromBigInt(
eth_wallet.EtherUnit.wei,
prep.priorityFee,
),
);

// Quote the fee we expect to pay (base + tip), not maxFeePerGas. The latter
// includes the unused headroom, which would roughly double the number shown
// to the user for gas they will never be charged for.
final feeEstimate = await estimateFeeFor(
Amount.zero,
prep.maxBaseFee + prep.priorityFee,
prep.baseFee + prep.priorityFee,
);

return txData.copyWith(
Expand Down
8 changes: 6 additions & 2 deletions lib/wallets/wallet/impl/sub_wallets/eth_token_wallet.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,17 +248,21 @@ class EthTokenWallet extends Wallet {
nonce: prep.nonce,
maxFeePerGas: eth_wallet.EtherAmount.fromBigInt(
eth_wallet.EtherUnit.wei,
prep.maxBaseFee,
prep.maxFeePerGas,
),
maxPriorityFeePerGas: eth_wallet.EtherAmount.fromBigInt(
eth_wallet.EtherUnit.wei,
prep.priorityFee,
),
);

// Token sends share ethWallet.internalSharedPrepareSend, so the zero-tip
// bug hit ETH and every ERC-20 (USDC included) identically, and the one
// fix covers both. As in ethereum_wallet, quote base + tip rather than
// maxFeePerGas, which carries headroom the user is never charged for.
final feeEstimate = await estimateFeeFor(
Amount.zero,
prep.maxBaseFee + prep.priorityFee,
prep.baseFee + prep.priorityFee,
);
return txData.copyWith(
fee: feeEstimate,
Expand Down