How to Build a Gas Fee Estimator for EIP-1559 using python

Tokenview.io
4 min readSep 25, 2023

--

Before EIP-1559, the gas fee on ETH used a simple auction model, and the transactions of the highest bidder were verified first. Such a calculation model will cause gas fees to fluctuate wildly due to human factors (bidding). EIP-1559 is designed to solve the problem of unpredictable and volatile gas fees.

Many apps like to give users the option to set their own gas bids, including “slow,” “average,” and “fast” options. In this article, we’ll look at how to build these options using EIP-1559 API

1. The calculation formula of Gas Fee

units of gas used * (base fee + priority fee)

  • units of gas used The number of gas units spent on the transaction. A standard simple ETH transfer transaction requires 21,000 units of gas.
  • base fee Basic gas fee, unit is gwei (1gwei = 0.000000001 ETH)
  • priority fee The priority fee, or tip to miners, is in the same unit as the base fee, which is gwei.

2. base fee

First, the base fee is based on the base fee of the previous block. The calculation is automatically completed by the eth node, which is essentially different from the previous bidding mode. Roughly speaking, if the transaction volume of the previous block was larger, the base fee of the current block will increase, and vice versa. Such dynamic adjustments. In other words, this basic fee can be accurately calculated.

EIP-1559 source code

The above code is based on golang, we convert it into a simple python code:

```
from web3 import Web3

eth_json_rpc_endpoint = "https://services.tokenview.io/vipapi/nodeservice/eth?apikey=xxx"
ElasticityMultiplier = 2 # EIP-1559 The block size has been expanded, the maximum multiple is 2
BaseFeeChangeDenominator = 8 # The amount the base fee can change between blocks

def calc_base_fee_of_next_block(parent_block):
parent_gas_target = parent_block.gasLimit // ElasticityMultiplier
print('parent_gas_target',parent_gas_target)
print('parent_block.gasUsed',parent_block.gasUsed)
print('parent_block.baseFeePerGas',parent_block.baseFeePerGas)
if parent_block.gasUsed == parent_gas_target:
# parent block's gasUsed is the same as the target, baseFee remains unchanged
return parent_block.baseFeePerGas
if parent_block.gasUsed > parent_gas_target:
# parent block uses gas greater than the target value, baseFee increase
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:
# the gas used by the parent block is less than the target value, baseFee reduce
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}")
```

3.priority fee

Unlike base fees, priority fees are artificially set values. For transactions that need to be executed first in the same block, a higher tip is required. To predict priority fees, you have to scan blocks over time to see what fees others are using.

We will use this API:eth_feeHistory

```
from web3 import Web3

eth_json_rpc_endpoint = "https://services.tokenview.io/vipapi/nodeservice/eth?apikey=xxx"
ethClient = Web3(Web3.HTTPProvider(eth_json_rpc_endpoint))
print(ethClient.eth.fee_history(4,"pending", [25, 50, 75]))
```

The above call means, “Give me the fee history information starting from the pending block and looking backward 4 blocks. For each block also give me the 25th, 50th, and 75th percentiles of priority fees for transactions in the block”. The raw result looks like this:

```
{
"oldestBlock": 17913327,
"reward": [
[39519672,100000000,2000000000],
[100000000,1000000000,3000000000],
[100000000,365064718,1000000000],
100000000,570000000,3000000000]
],
"baseFeePerGas": [
21121728416,
21666906452,
20742307151,
19782866894,
17762883032
],
"gasUsedRatio": [
0.6032449666666667,
0.3293066333333333,
0.31497906666666664,
0.09156903333333333
]
}
```

To make our calculations easier, let’s write a formatting method that groups the above results into chunks:

```
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
```

Next we can get the data in this format:

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

->>
[
{
number: 17913335,
baseFeePerGas: 315777006840,
gasUsedRatio: 0.9922326809477219,
priorityFeePerGas: [ 34222993160, 34222993160, 63222993160 ]
},
{
number: 17913336,
baseFeePerGas: 354635947504,
gasUsedRatio: 0.22772779167798343,
priorityFeePerGas: [ 20000000000, 38044555767, 38364052496 ]
},
{
number: 17913337,
baseFeePerGas: 330496570085,
gasUsedRatio: 0.8876034775653597,
priorityFeePerGas: [ 9503429915, 19503429915, 36503429915 ]
},
{
number: 17913338,
baseFeePerGas: 362521975057,
gasUsedRatio: 0.9909446241177369,
priorityFeePerGas: [ 18478024943, 37478024943, 81478024943 ]
}
]
```

Readability is greatly improved! Among them, baseFeePerGas and gasUsedRatio are used to calculate the base fee. We only focus on priorityFeePerGas now.

Continuing with our requirements, to estimate the priority fee we need to determine two values:

i.How much historical data should be used to estimate

ii.How much priority do you want your transactions to have?

For i , we set the value to 4 blocks, a reasonable length of time of about a minute.

For ii, we set expectations as 25% 50% 75% corresponding to low, medium and high tips.

```
blocks = format_fee_history(ethClient.eth.fee_history(4,"latest", [25, 50, 75]),False)
low_percential_priority_fees = [b['priorityFeePerGas'][0] for b in blocks]
mid_percential_priority_fees = [b['priorityFeePerGas'][1] for b in blocks]
high_percential_priority_fees = [b['priorityFeePerGas'][2] for b in blocks]
low_average = sum(low_percential_priority_fees) / len(low_percential_priority_fees)
mid_average = sum(mid_percential_priority_fees) / len(mid_percential_priority_fees)
high_average = sum(high_percential_priority_fees) / len(high_percential_priority_fees)
print(low_average,' | ',mid_average,' | ',high_average)
```

The algorithm used by Tokenview is to simply average the priorityFeePerGas of the past 4 historical blocks. Of course, if you have a better estimation method, such as increasing the number of historical blocks to 20 blocks, or only considering the cheapest successful transaction. You can build your own gas cost estimator

Thank you for reading from start to finish🎉🎉🎉

--

--

Tokenview.io

Our mission is to build Freedom Safe Easy Web3/Crypto world. visit us at https://tokenview.io for General Multi-chain Explorer and Blockchain APIs entrance.