EIP-1559 交易脚本的六个 Gas 坑:baseFee 突变到 nonce 冲突

写一个”清仓转账”的脚本——把地址里所有余额扫到目标地址,用 EIP-1559。核心代码大致这样:

base_fee = w3.eth.get_block("latest")["baseFeePerGas"]
priority_fee = w3.to_wei(0.1, "gwei")
max_fee_per_gas = base_fee + priority_fee

gas_limit = 30000
fee = gas_limit * max_fee_per_gas
amount = balance - fee

tx = {
    "to": w3.to_checksum_address(dst),
    "value": amount,
    "gas": gas_limit,
    "maxFeePerGas": max_fee_per_gas,
    "maxPriorityFeePerGas": priority_fee,
    "nonce": w3.eth.get_transaction_count(from_address),
    "chainId": CHAIN_ID,
    "type": 2,
}

跑起来经常报:

insufficient funds for gas * price + value

问题不是一处,是好几个细节叠加。

坑 1:maxFeePerGas 写死等于 baseFee + priority

节点校验时:

balance >= value + gasLimit × maxFeePerGas

baseFee 每个区块都会浮动(EIP-1559 每次可变 ±12.5%)。你算完刚好够,几秒后新块 baseFee 涨了:

max_fee < new_base_fee + priority

要么交易过不了、要么”钱不够”。MetaMask 的做法是:

max_fee_per_gas = base_fee * 2 + priority_fee

base_fee * 2 是常规冗余系数——baseFee 一个区块最多涨 12.5%,2x 完全够缓冲。多花的部分节点会退还给你,只按 effectiveGasPrice 收。

坑 2:gasLimit 硬编码

普通转账 21000 够,Gravity 这类链要 30000。合约调用差别就更大了。别写死,先估算再加冗余:

gas_limit = w3.eth.estimate_gas({
    "from": from_address,
    "to": dst,
    "value": 1,
})
gas_limit = int(gas_limit * 1.2)

* 1.2 是通用的 20% 余量,避免 estimation 边缘 case。

坑 3:priority fee 硬编码 0.1 gwei

网络拥堵时 0.1 gwei 的小费根本进不了下一个块。用节点建议值:

priority_fee = w3.eth.max_priority_fee

或者查 feeHistory 取近期百分位:

history = w3.eth.fee_history(5, "latest", [50])
priority_fee = max(history["reward"][-1][0], w3.to_wei(1, "gwei"))

坑 4:nonce 用了 latest

默认:

w3.eth.get_transaction_count(from_address)  # 等价于 "latest"

如果地址有未确认的 pending 交易,latest 会返回过时的 nonce,新交易和旧的撞车,两个都卡住。改成:

nonce = w3.eth.get_transaction_count(from_address, "pending")

坑 5:清仓转账没留冗余

amount = balance - fee 是理论极限。实际因为 baseFee 突变或 estimation 偏差,几乎必然凑不够。留一点小额缓冲:

reserve = w3.to_wei(0.0001, "ether")
amount = balance - fee - reserve

坑 6:chainId 抄错

"chainId": 127001,

链 ID 不匹配的交易节点直接拒。跟节点确认一次:

print(w3.eth.chain_id)

修正后的样板

base_fee = w3.eth.get_block("latest")["baseFeePerGas"]
priority_fee = w3.eth.max_priority_fee
max_fee_per_gas = base_fee * 2 + priority_fee

gas_limit = w3.eth.estimate_gas({
    "from": from_address,
    "to": dst,
    "value": 1,
})
gas_limit = int(gas_limit * 1.2)

fee_ceiling = gas_limit * max_fee_per_gas
reserve = w3.to_wei(0.0001, "ether")
amount = balance - fee_ceiling - reserve
if amount <= 0:
    raise RuntimeError("余额不足以支付 Gas + 冗余")

tx = {
    "to": w3.to_checksum_address(dst),
    "value": amount,
    "gas": gas_limit,
    "maxFeePerGas": max_fee_per_gas,
    "maxPriorityFeePerGas": priority_fee,
    "nonce": w3.eth.get_transaction_count(from_address, "pending"),
    "chainId": w3.eth.chain_id,
    "type": 2,
}

一句话总结

EIP-1559 参数不是”算准就行”,是”抗突变才稳”。maxFee = baseFee * 2 + prioritygas = estimate * 1.2nonce = pending,三件事一起做,出问题的概率会低一个数量级。