maxPriorityFeePerGas vs maxFeePerGas

This guide will walk you through the difference between two EIP-1559 methods: maxPriorityFeePerGas and maxFeePerGas and help you understand when to use each of them.

Sending a transaction on Ethereum post London fork uses these two new gas price fields: maxFeePerGas and maxPriorityFeePerGas. We won't go into detail on the incentive theory behind fee markets here. Instead, we will dive into the difference between these two fields and when you might want to use one vs the other (or both).

If you've gone through our tutorial on sending an EIP 1559 transaction then you've seen that we recommend using only the maxPriorityFeePerGas field. We did this for simplicity, but it's not always the better field to use.

To understand why, let's look at each of the concepts more in-depth.

What is the baseFeePerGas?

Let's start with understanding what a base fee is. The base fee is the bare minimum you will be charged to send a transaction on the network. The base fee is set by the network itself, not by miners. The base fee changes block by block, based on how full the previous block was.

Post London fork, each block has a baseFeePerGas associated with it. You can see the base fee for the pending (upcoming) block with:

web3.eth.getBlock("pending").then((block) => console.log("baseFee", Number(block.baseFeePerGas)));

The above snippet assumes you've set up your Alchemy Web3 client. If you haven't , check out the EIP 1559 tutorial for some sample code.

Base fees are determined by the network and are also burned when the block is mined. This means that the miner does not get the base fee as a reward for mining a block.

Miners are still compensated for the computational work by receiving a "tip". When you submit a transaction you will also provide this "tip" in the maxPriorityFeePerGas field.

The bare minimum you should tip the miner is 1 wei so that there is an incentive for the miner to execute the transaction. The higher your tip, the more likely your transaction will be included in the block.

What is maxFeePerGas?

Now that we know about the base fee and tip, maxFeePerGas is super simple. It's just the sum of the two:

maxFeePerGas = baseFeePerGas + maxPriorityFeePerGas

When to use maxPriorityFeePerGas vs. maxFeePerGas?

The most guaranteed way to have your transaction included in the block is to specify a maxPriorityFeePerGas field (which is a tip). In this case, Alchemy will look up the pending baseFee and then set the maxFeePerGas field accordingly (to the sum of the base fee and the tip). All you have to do is decide how much tip to provide, which you can get by simply calling the eth_maxPriorityFeePerGas method on Alchemy.

This method is the simplest but it may not be the cheapest. There are two dimensions to consider when submitting your transaction: speed and cost. If you bid high, you get mined earlier. If you bid low, you get mined later or not at all.

When you submit only the maxPriorityFeePerGas field, the defaults will fill in the base fee for you. The base fee depends on how full the previous blocks were. If many of the previous blocks were full, then the base fee can end up being quite high! Since the value is added automatically, you can end up paying a surprisingly high gas price.

To avoid this pitfall you can supply the maxFeePerGas field. If you supply only this field, then the tip will be filled in for you but there are no guarantees on when your transaction will be mined. You can also supply both the maxFeePerGas and the maxPriorityFeePerGas fields for full control.

Example using maxFeePerGas

Let's put all of the concepts together by submitting a transaction that is guaranteed to fail. By setting the maxFeePerGas to less than the sum of baseFeePerGas and maxPriorityFeePerGas. This code is an extension of the code in the EIP 1559 transaction sending tutorial.

web3.eth.estimateGas({
  to: toAddress,
  data: "0xc6888fa10000000000000000000000000000000000000000000000000000000000000003"
}).then((estimatedGas) => {
  web3.eth.getMaxPriorityFeePerGas().then((tip) => {
    web3.eth.getBlock("pending").then((block) => {
      const baseFee = Number(block.baseFeePerGas);
      const max = Number(tip) + baseFee - 1; // less than the sum

      sendTx(web3, {
        gas: estimatedGas,
        maxPriorityFeePerGas: Number(tip),
        maxFeePerGas: max,
        to: toAddress,
        value: 100,
      });
    });
  });
});

The output is just a constant stream of "waiting…"

Transaction sent! 0x7648502609b5617fc23fac9daf92a10e8c5ecdd8fe709132c2ce50899685072a
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...
Attempting to get transaction receipt...

You will now have to wait until the base fee of a block drops low enough that the sum of the base fee plus tip is less than your maxFeePerGas. Depending on network congestion, the wait time can be very long.

Conclusion

Choosing to use maxPriorityFeePerGas vs. maxFeePerGas for sending transactions is completely dependent on what your priorities are. If you care about speed and want to get your transaction into the earliest block possible, you should use the default base fee and just set (a high) maxPriorityFeePerGas.

If you care more about saving on gas, and less about transaction speed, you can set your own maxFeePerGas and potentially risk the transaction getting mined at a later block (or earlier block if you choose to set it to be higher than the base fee + tip).

If you're interested in learning more, or have feedback, suggestions, or questions, reach out to us in Discord! Get started with Alchemy today by signing up for free.