How to Build a Gas Fee Estimator for EIP-1559 using python
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.
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🎉🎉🎉