Web3.py EIP-1559 交易 Gas 费计算:baseFee 波动与余额预留

EIP-1559 Gas 费结构

EIP-1559 交易有两个关键字段:

  • maxFeePerGas:你愿意支付的最高单价(base fee + priority fee)
  • maxPriorityFeePerGas:给矿工的小费(“tip”)

节点在验证交易时检查:

balance >= value + gasLimit × maxFeePerGas

实际扣费是:

gasUsed × (baseFeePerGas + priorityFeePerGas)

其中 baseFeePerGas 由网络自动调整,gasUsed <= gasLimit

典型实现(web3.py)

from web3 import Web3

w3 = Web3(Web3.HTTPProvider("https://mainnet-rpc.example.xyz"))

def send_max(private_key, dst_address, chain_id):
    account = w3.eth.account.from_key(private_key)
    from_address = account.address

    balance = w3.eth.get_balance(from_address)
    nonce = w3.eth.get_transaction_count(from_address)

    # 动态读取 baseFee
    base_fee = w3.eth.get_block("latest")["baseFeePerGas"]

    # 用节点推荐的 priority fee,更可靠
    priority_fee = w3.eth.max_priority_fee

    # maxFee 加 buffer,防 baseFee 上涨
    max_fee_per_gas = base_fee * 2 + priority_fee

    # 估算 gas,比写死更安全
    gas_limit = w3.eth.estimate_gas({
        "from": from_address,
        "to": dst_address,
        "value": 1,
    })
    gas_limit = int(gas_limit * 1.2)  # 20% buffer

    fee = gas_limit * max_fee_per_gas

    # 预留少量 wei 防止因 baseFee 微小波动导致余额不足
    reserve = w3.to_wei(0.000001, "ether")
    amount = balance - fee - reserve

    if amount <= 0:
        print(f"余额不足,balance={balance}, fee={fee}")
        return None

    tx = {
        "from": from_address,
        "to": Web3.to_checksum_address(dst_address),
        "value": amount,
        "gas": gas_limit,
        "maxFeePerGas": max_fee_per_gas,
        "maxPriorityFeePerGas": priority_fee,
        "nonce": nonce,
        "chainId": chain_id,
        "type": 2,
    }

    signed = w3.eth.account.sign_transaction(tx, private_key)
    tx_hash = w3.eth.send_raw_transaction(signed.raw_transaction)
    return tx_hash.hex()

常见坑

坑一:maxFee 太紧,baseFee 上涨就失败

# 错误写法:maxFee 刚好等于 baseFee + priority
max_fee = base_fee + priority_fee   # 一旦下一个区块 baseFee 上涨,交易立即失效

改为:

max_fee = base_fee * 2 + priority_fee   # 留足余量,只多花极少的钱

坑二:写死 gasLimit

gas_limit = 21000   # ETH 原生转账是这个值,但其他链可能不同

更安全的方式:

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

坑三:不预留 reserve 导致余额精确到 wei 后失败

节点验证 balance >= value + gasLimit × maxFeePerGas。如果 amount = balance - fee 算完后 baseFee 微涨,条件就不满足。

预留 0.000001 ETH(约 1,000,000,000,000 wei)几乎没有损失但能避免大量边缘失败。

坑四:priority_fee 写死

priority_fee = w3.to_wei(0.1, "gwei")   # 可能太低导致交易迟迟不确认

改为从节点获取推荐值:

priority_fee = w3.eth.max_priority_fee   # 节点返回当前合理的 tip