EIP-1559 手续费机制详解

EIP-1559 是 2021 年 8 月以太坊 London 升级引入的手续费改革,它彻底改变了 Gas 的定价方式。如果你在做钱包或交易所,发交易时不理解这套机制,要么手续费估高了用户体验差,要么估低了交易迟迟不上链。


老机制的问题

EIP-1559 之前,以太坊用的是一价拍卖(First Price Auction)

  • 用户自己出一个 gasPrice
  • 矿工优先打包出价最高的交易
  • 网络拥堵时用户只能盲猜出多少合适

结果就是:拥堵时大家拼命加价,但实际上很多人出了远超必要的手续费,钱白白浪费。


EIP-1559 的核心变化

EIP-1559 把手续费拆成两部分:

实际手续费 = (baseFee + priorityFee) × gasUsed
参数谁决定去哪里
baseFee协议自动计算直接销毁(burn)
priorityFee(小费)用户设置给验证者
maxFeePerGas用户设置上限最多愿意付多少

用户实际支付:

实际支付 = min(maxFeePerGas, baseFee + priorityFee) × gasUsed

如果 maxFeePerGas 设得比 baseFee + priorityFee 高,多出来的部分退还给用户,不会浪费。


baseFee 怎么算

baseFee 由上一个区块的拥堵程度决定,每个区块自动调整:

  • 每个区块的目标 Gas 用量是 1500 万(区块上限 3000 万的一半)
  • 上个区块超过目标 → baseFee 最多涨 12.5%
  • 上个区块低于目标 → baseFee 最多降 12.5%
下一块 baseFee = 当前 baseFee × (1 + 0.125 × (实际Gas - 目标Gas) / 目标Gas)

这个设计让 baseFee 相对可预测——连续几个满块之后,baseFee 会快速上涨并抑制需求,最终趋于稳定。

实际体感:网络空闲时 baseFee 可以低到 1-2 Gwei,极度拥堵时(如热门 NFT mint)可以飙到几百 Gwei。


三个参数怎么设

发一笔 EIP-1559 交易需要设三个参数:

maxFeePerGas(愿意付的上限)

这是你愿意每单位 Gas 最多花多少,包含 baseFee

maxFeePerGas = baseFee × 2 + maxPriorityFeePerGas

乘以 2 是给 baseFee 留缓冲,防止你提交后 baseFee 继续上涨导致交易卡住。如果 baseFee 没涨到那么高,多出来的钱会退回来。

maxPriorityFeePerGas(矿工小费)

这是给验证者的激励,决定你的交易被优先打包的概率。

普通转账:1-2 Gwei 够了 DeFi 操作 / 时间敏感:3-5 Gwei 抢购 / 套利:10 Gwei+

gasLimit

预估这笔交易最多消耗多少 Gas,普通 ETH 转账固定是 21000,合约调用需要模拟估算(eth_estimateGas)。


// 1. 获取当前 baseFee
final block = await client.getBlockInformation(blockNumber: 'latest');
final baseFee = block.baseFeePerGas!; // EtherAmount

// 2. 设置 priorityFee(小费)
final priorityFee = EtherAmount.fromInt(EtherUnit.gwei, 2); // 2 Gwei

// 3. 计算 maxFeePerGas(baseFee × 2 + 小费,留缓冲)
final maxFee = EtherAmount.fromBigInt(
  EtherUnit.wei,
  baseFee.getInWei * BigInt.two + priorityFee.getInWei,
);

// 4. 构造交易
final tx = Transaction(
  to: EthereumAddress.fromHex('0xRecipient...'),
  value: EtherAmount.fromInt(EtherUnit.finney, 10),
  maxGas: 21000,
  maxFeePerGas: maxFee,
  maxPriorityFeePerGas: priorityFee,
);

final hash = await client.sendTransaction(credentials, tx, chainId: 1);

交易加速和取消

发出去的交易"迟迟没上链",一般有两种情况:

情况一:baseFee 涨过了你的 maxFeePerGas

节点会直接把交易踢出 mempool,或者一直排在队尾等 baseFee 降回来。
判断方法:

// 拿当前 baseFee
final block = await client.getBlockInformation(blockNumber: 'latest');
final currentBaseFee = block.baseFeePerGas!.getInWei;

// 和原交易的 maxFeePerGas 对比
if (currentBaseFee > originalMaxFeePerGas) {
  // baseFee 已经超过你的上限,交易会一直卡着
  // 需要加速替换
}

情况二:交易还在 mempool,但 priorityFee 太低竞争不过别人

baseFee 没超,但区块一直满,你的小费不够被优先打包。
这种情况比较难直接判断,一般用时间兜底——超过 N 分钟未确认就触发提示。

实践上的判断逻辑

Future<bool> isTransactionStuck({
  required String txHash,
  required BigInt originalMaxFeePerGas,
  required Duration timeout,
  required DateTime submittedAt,
}) async {
  // 1. 先查交易是否已确认
  final receipt = await client.getTransactionReceipt(txHash);
  if (receipt != null) return false; // 已上链,没卡

  // 2. 和当前 baseFee 对比
  final block = await client.getBlockInformation(blockNumber: 'latest');
  final currentBaseFee = block.baseFeePerGas!.getInWei;
  if (currentBaseFee > originalMaxFeePerGas) return true; // baseFee 超限,必然卡

  // 3. 时间兜底:超过超时时间还没确认,认为卡住
  return DateTime.now().difference(submittedAt) > timeout;
}

实践中 timeout 一般设 3-5 分钟,正常交易在非拥堵时段都能在这个时间内确认。


交易发出去之后 baseFee 涨了,交易卡在 mempool 里,怎么处理?

加速(Speed Up):用同一个 nonce 重新发一笔,提高 maxFeePerGasmaxPriorityFeePerGas,新交易会替换旧的。

// 用同一个 nonce,提高手续费(至少涨 10%)
final speedUpTx = Transaction(
  nonce: originalNonce,           // 关键:nonce 相同
  to: originalTo,
  value: originalValue,
  maxFeePerGas: newHigherMaxFee,  // 至少比原来高 10%
  maxPriorityFeePerGas: newHigherPriorityFee,
  maxGas: 21000,
);

取消(Cancel):用同一个 nonce 给自己转 0 ETH,手续费设高一点,覆盖掉原交易。

final cancelTx = Transaction(
  nonce: originalNonce,     // 相同 nonce
  to: myOwnAddress,         // 发给自己
  value: EtherAmount.zero(),
  maxFeePerGas: highFee,    // 设高一点确保被优先打包
  maxPriorityFeePerGas: highPriorityFee,
  maxGas: 21000,
);

注意:替换交易要求新的 maxPriorityFeePerGas 至少比原来高 10%,否则节点会直接拒绝。


几个常见误区

误区 1:maxFeePerGas 设高了会多花钱

不会,多出来的退回来。maxFeePerGas 只是上限,实际花费是 baseFee + priorityFee

误区 2:priorityFee 设高了一定更快

不一定。区块不拥堵时,1 Gwei 的小费和 10 Gwei 效果一样,都是下个块就打包。只有竞争激烈时小费才有区分效果。

误区 3:baseFee 销毁对用户没影响

有影响,间接的。ETH 通缩压力上升,长期对 ETH 价格有支撑,这也是 EIP-1559 的设计目标之一。


总结

EIP-1559 之前EIP-1559 之后
定价方式用户竞价baseFee 自动调整 + 小费
手续费去向全给矿工baseFee 销毁,小费给验证者
可预测性差(盲猜)好(baseFee 可提前知道)
退款没有有(maxFee 没用完退回)

做钱包的话,Gas 估算是用户体验的关键点之一。估太低交易卡,估太高显得不专业。实践中可以参考 eth_feeHistory 接口拿近几十个块的历史数据,再根据用户选择的速度档位(慢/普通/快)动态计算合适的 maxPriorityFeePerGas