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 重新发一笔,提高 maxFeePerGas 和 maxPriorityFeePerGas,新交易会替换旧的。
// 用同一个 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。