A Comprehensive Guide to Building Gas Fee Options with EIP-1559 API

·

Understanding Ethereum's gas fee structure is crucial for developers and users alike. The introduction of EIP-1559 fundamentally changed how transaction fees work on the network, replacing the previous auction model with a more predictable system. This guide explains how to leverage the EIP-1559 API to build user-friendly gas fee options like "slow," "average," and "fast" for your applications.

Understanding the EIP-1559 Gas Fee Formula

The current Ethereum gas fee calculation follows a specific formula:

Units of gas used × (Base fee + Priority fee)

Let's break down each component:

This new mechanism creates a more stable fee market compared to the previous bidding system, where gas prices could fluctuate wildly due to speculative bidding.

How the Base Fee Mechanism Works

The base fee represents the most significant change introduced by EIP-1559. Unlike the previous system where users bid against each other, the base fee is algorithmically determined based on network congestion from the previous block.

The base fee automatically adjusts according to these principles:

This dynamic adjustment mechanism ensures that base fees can be accurately predicted, providing much-needed stability to Ethereum's fee market.

Calculating the Base Fee Programmatically

The base fee calculation follows a specific algorithm implemented in Ethereum clients. Here's a Python implementation that demonstrates this calculation:

from web3 import Web3

eth_json_rpc_endpoint = "YOUR_ETHEREUM_NODE_ENDPOINT"
ElasticityMultiplier = 2  # Maximum block size multiplier under EIP-1559
BaseFeeChangeDenominator = 8  # Base fee change parameter

def calc_base_fee_of_next_block(parent_block):
    parent_gas_target = parent_block.gasLimit // ElasticityMultiplier
    
    if parent_block.gasUsed == parent_gas_target:
        return parent_block.baseFeePerGas
        
    if parent_block.gasUsed > parent_gas_target:
        gas_used_delta = parent_block.gasUsed - parent_gas_target
        x = parent_block.baseFeePerGas * gas_used_delta
        y = x // parent_gas_target
        base_fee_delta = max(y // BaseFeeChangeDenominator, 1)
        return parent_block.baseFeePerGas + base_fee_delta
    else:
        gas_used_delta = parent_gas_target - parent_block.gasUsed
        x = parent_block.baseFeePerGas * gas_used_delta
        y = x // parent_gas_target
        base_fee_delta = y // BaseFeeChangeDenominator
        return max(parent_block.baseFeePerGas - base_fee_delta, 0)

def main():
    ethClient = Web3(Web3.HTTPProvider(eth_json_rpc_endpoint))
    block_number = ethClient.eth.block_number
    block = ethClient.eth.get_block(block_number)
    
    base_fee_of_next_block = calc_base_fee_of_next_block(block)
    print(f"Base fee for block {block_number + 1} will be {base_fee_of_next_block}")

This code connects to an Ethereum node, retrieves the latest block information, and calculates what the base fee will be for the subsequent block.

Determining Priority Fees for Transaction Speed

While base fees are algorithmically determined, priority fees (tips) remain user-defined. To estimate appropriate priority fees, you need to analyze historical data to understand what tips others are paying for timely transaction inclusion.

Using the eth_feeHistory API

The eth_feeHistory API provides essential data for estimating priority fees. This method returns historical gas information that helps determine appropriate tip levels:

from web3 import Web3

eth_json_rpc_endpoint = "YOUR_ETHEREUM_NODE_ENDPOINT"
ethClient = Web3(Web3.HTTPProvider(eth_json_rpc_endpoint))

fee_history = ethClient.eth.fee_history(4, "pending", [25, 50, 75])
print(fee_history)

This call requests fee history starting from the pending block and looks backward 4 blocks, returning the 25th, 50th, and 75th percentiles of priority fees for each block.

Processing the Fee History Data

The raw data from eth_feeHistory requires processing to become truly useful. Here's a formatting function that structures the data for easier analysis:

def format_fee_history(result, include_pending):
    block_num = result['oldestBlock']
    index = 0
    blocks = []
    historical_blocks = len(result['reward'])
    
    while block_num < result['oldestBlock'] + historical_blocks:
        blocks.append({
            'number': block_num,
            'baseFeePerGas': float(result['baseFeePerGas'][index]),
            'gasUsedRatio': float(result['gasUsedRatio'][index]),
            'priorityFeePerGas': [float(x) for x in result['reward'][index]],
        })
        block_num += 1
        index += 1
        
    if include_pending:
        blocks.append({
            'number': 'pending',
            'baseFeePerGas': float(result['baseFeePerGas'][historical_blocks]),
            'gasUsedRatio': float('nan'),
            'priorityFeePerGas': [],
        })
        
    return blocks

This formatter organizes the raw data into a more readable structure, grouping information by block number with corresponding base fees, gas usage ratios, and priority fee percentiles.

Calculating Priority Fee Estimates

With properly formatted data, you can now calculate average priority fees for different speed options:

blocks = format_fee_history(ethClient.eth.fee_history(4, "latest", [25, 50, 75]), False)

low_priority_fees = [b['priorityFeePerGas'][0] for b in blocks]
mid_priority_fees = [b['priorityFeePerGas'][1] for b in blocks]
high_priority_fees = [b['priorityFeePerGas'][2] for b in blocks]

low_average = sum(low_priority_fees) / len(low_priority_fees)
mid_average = sum(mid_priority_fees) / len(mid_priority_fees)
high_average = sum(high_priority_fees) / len(high_priority_fees)

print(f"Slow: {low_average} gwei | Average: {mid_average} gwei | Fast: {high_average} gwei")

This approach uses a simple averaging method across four historical blocks, which provides a reasonable estimate for current network conditions. The three averages correspond to different confirmation speed expectations:

Customizing Your Gas Estimation Strategy

While the averaging method described above works well for many applications, you might want to customize your estimation strategy based on specific requirements:

👉 Explore more strategies for optimizing your gas fee estimation algorithms and providing the best user experience.

Implementing Gas Options in Your Application

When implementing gas options in your dApp or wallet, consider these best practices:

  1. Clear labeling: Use intuitive terms like "Slow," "Standard," and "Fast" that users understand
  2. Time estimates: Provide expected confirmation times for each option when possible
  3. Cost transparency: Show the total estimated cost in ETH and USD equivalents
  4. Custom options: Allow advanced users to manually adjust fees
  5. Network status indicators: Show when the network is congested and fees are generally higher

Frequently Asked Questions

What is EIP-1559 and how did it change Ethereum's fee structure?
EIP-1559 introduced a base fee mechanism that automatically adjusts based on network demand, replacing the previous auction system. This created more predictable gas fees and introduced a token burning mechanism that reduces ETH supply over time.

How often does the base fee change?
The base fee adjusts with every block, which occurs approximately every 12 seconds on Ethereum. The change depends on how full the previous block was compared to the network's target capacity.

What's the difference between base fee and priority fee?
The base fee is burned and required for all transactions, while the priority fee (tip) goes to miners and incentivizes them to include your transaction. Users can set the priority fee to zero, but this may result in slower confirmation times.

How many historical blocks should I use for fee estimation?
Most implementations use 4-10 historical blocks for estimation, which represents about 1-2 minutes of network activity. Using more blocks provides a broader perspective but may not reflect recent congestion changes.

Can I set a zero priority fee?
Technically yes, but transactions with zero priority fee may take much longer to confirm, especially during network congestion. Miners typically prioritize transactions with higher tips.

How accurate are these gas estimation methods?
While these methods provide good estimates, they can't guarantee precise predictions due to the dynamic nature of the Ethereum network. Sudden spikes in demand can quickly change fee requirements.

Conclusion

Building effective gas fee options with EIP-1559 API requires understanding both the base fee mechanism and priority fee estimation. By implementing the techniques described in this guide, you can provide users with clear choices that balance cost and confirmation speed according to their needs.

The key components include calculating the predictable base fee, analyzing historical priority fee data, and creating averaged estimates for different confirmation speeds. Remember that these are estimation techniques—actual network conditions may vary, so providing users with clear information about potential variance is important for a good user experience.

As Ethereum continues to evolve, staying updated on network changes and optimizing your fee estimation algorithms will ensure your application provides the best possible experience for users managing their transaction costs.