EIP-1559 Gas 费计算:Python Web3 转账扣余额的正确姿势

做链上转账时,想把账户余额全部转走,需要提前扣除 Gas 费。EIP-1559 下的费用计算和普通 Legacy 交易有些区别,容易算错。

常见的错误写法

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

这里有两个问题:

问题一:fee 计算偏高但不算错

gas_limit * max_fee_per_gas最坏情况下的费用上限,实际扣款是:

actual_fee = gas_used × (base_fee + priority_fee)

gas_used ≤ gas_limit,所以实际花费通常比预留值少。转账类交易 gas_used 基本等于 gas_limit,误差不大;合约调用误差可能较大。

问题二:maxFeePerGas 应该 > base_fee + priority_fee

EIP-1559 规范里,maxFeePerGas 是你愿意支付的上限,真正矿工收到的是:

effective_gas_price = base_fee + priority_fee

如果下一个区块 base_fee 涨了,用 maxFeePerGas = base_fee + priority_fee 可能导致交易因 maxFeePerGas < 新 base_fee 而被拒绝。

正确写法

def send_all_balance(w3, private_key, to_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)

    # base_fee 加 20% 缓冲,防止下一块涨价
    base_fee = w3.eth.get_block("latest")["baseFeePerGas"]
    base_fee_with_buffer = int(base_fee * 1.2)
    priority_fee = w3.to_wei(0.1, "gwei")
    max_fee_per_gas = base_fee_with_buffer + priority_fee

    gas_limit = 21000  # 纯转账固定 21000;合约调用需要 estimateGas

    # 预留的 Gas 费上限
    max_gas_cost = gas_limit * max_fee_per_gas
    amount = balance - max_gas_cost

    if amount <= 0:
        print(f"余额不足:balance={w3.from_wei(balance, 'ether')} ETH")
        return None

    tx = {
        "from": from_address,
        "to": w3.to_checksum_address(to_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)

    print(f"发送: {w3.from_wei(amount, 'ether')} ETH")
    print(f"Gas 预留: {w3.from_wei(max_gas_cost, 'ether')} ETH")
    print(f"TX: {tx_hash.hex()}")
    return tx_hash.hex()

EIP-1559 费用结构

字段含义
baseFeePerGas协议自动设置,每笔交易必须支付,销毁
maxPriorityFeePerGas给矿工的小费,激励打包
maxFeePerGas用户愿意支付的上限
实际每 Gas 费用min(maxFeePerGas, baseFee + priorityFee)

实际从账户扣除:

actual_cost = gas_used × effective_gas_price

其中 effective_gas_price = baseFee + priorityFee(不超过 maxFeePerGas)。

多余的 (maxFeePerGas - effectiveGasPrice) × gasUsed 会退还给发送方,但退还发生在交易上链后,所以发送前账户余额必须 ≥ gasLimit × maxFeePerGas

纯转账 vs 合约调用

纯 ETH 转账 gas 固定是 21000,可以直接写死。合约调用要用 estimate_gas

gas_limit = w3.eth.estimate_gas({
    "from": from_address,
    "to": contract_address,
    "data": encoded_data,
})
gas_limit = int(gas_limit * 1.1)  # 加 10% 缓冲

合约执行失败时 gas 仍会被扣,所以建议先 call 验证不会 revert 再发送。

常见的链 gas_limit

不同链的标准转账 gas 不完全一样,普通 EVM 链一般是 21000,部分 L2 有所不同:

GAS_LIMITS = {
    "ethereum": 21000,
    "polygon": 21000,
    "bsc": 21000,
    "gravity": 30000,  # 某些链会高一些
}

不确定的情况下用 estimate_gas 最安全。

一句话总结

EIP-1559 转账预留费用用 gasLimit × maxFeePerGasmaxFeePerGas 要比当前 baseFee 高出一定缓冲(一般 20%),防止下一块 baseFee 上涨导致交易失败。纯转账 gas 固定 21000,合约调用用 estimateGas。