How to Calculate Ethereum Miner Rewards

Tutorial on how to calculate miner rewards for a single Ethereum block

Have you ever wondered how much a miner earns for mining a block on Ethereum? Maybe you’re curious about what miners are earning or have a practical application (i.e. blockchain explorer, miner profit calculator, etc) for calculating a block reward. You could achieve this by checking Etherscan’s “block reward” field if you believe their calculations are accurate. Another alternative would be to check the miner’s wallet balance via eth_getBalance before and after a given block is mined. The issue with this solution is that it will only be accurate when the miner does not receive ether from a transaction. For example, if a miner receives 1ETH in the block they mined, your reward calculation would be offset. In this tutorial, we will explore a different approach that does require trusting a 3rd party or miner balance. To accurately calculate a block reward, the following parts are needed:

  • Block reward: A fixed inflationary reward that only changes at forks. The block reward is currently set to 2ETH and was last set by EIP-1234 at the Constantinople fork. Consequently, each Ethereum block excluding burned fees adds 2ETH to the total currency supply.

  • Transaction gas fees: Each transaction on Ethereum requires a certain amount of gas units to execute Opcode commands on the EVM which change the state of the network. Although each operation requires a fixed amount of gas, the rate at which a user pays for those gas units changes based on a block’s baseFeePerGas and the user specified maxPriorityFeePerGas (miner tip). The baseFeePerGas is the minimum rate a user can pay to include their transaction in the next block and is determined by the previous block’s total gas usage. If the previous block uses less than 50% of it’s gas capacity (30 million units) the base fee decreases. If the previous block’s gas usage is equal to 50% the base fee stays the same. Otherwise, if the gas usage is above 50% the base fee will increase in the next block. Because, the base fee is burned, adding a miner tip gives some incentive to a miner to include your transaction in the next block. The basic formula is as follows:

    gas units x (baseFeePerGas + maxPriorityFeePerGas) = transaction fee

    An example fee could look like this: 21,000 x (15 gwei + 1.5gwei) = 346500gwei or 0.0003465ETH

  • Burned fees: The baseFeePerGas is burned/removed from the Ethereum protocol altogether. In order to calculate the total amount of burned fees in a block you can use the following formula:
    baseFeePerGas x gasUsed = burned fees
    Once you know the total amount burned, you can subtract this from the total block reward.

  • Uncle and nephew block rewards: The final part of our block reward calculation is to add additional rewards for mining an Uncle block (uncle reward) or including it in the latest block (nephew reward). An uncle block occurs when two miners create blocks at almost the same time. While both blocks are valid, the network can only accept one block at a time. Therefore one block is rejected and labeled as an uncle block. Instead of letting this block go stale, a nephew reward equal to 1/32 of a block reward is issued to any miner willing to later include this uncle block inside a block they are mining. Additionally, an uncle reward is issued to the miner of the uncle block. The size of the uncle reward is determined by the following formula:

    (Uncle blockNumber + 8 - Block Number) x block reward / 8 = uncle reward.

    Here’s a few examples:
    - (100 + 8 - 101) x 2 / 8 = 1.75ETH
    - (100 + 8 - 102) x 2 / 8 = 1.5ETH
    - (100 + 8 - 103) x 2 / 8 = 1.25ETH

    The formula ensures that the more blocks that pass between the uncle and when it’s added to the network, the
    smaller the reward.
    This is not the case for the nephew block, which is fixed at 1/32 of a block reward. Currently, the nephew reward is:

    2ETH/32 = 0.0652ETH

Awesome, now we have all the necessary ingredients for calculating the block reward! Lets put it all together into a
recipe:

block reward + transaction fees sum + nephew reward - burned fees = miner reward

📘

NOTE:

You could add the uncle fees too; however, because they are going to a separate miner for a different block we will include them separately.

In the next section, we will set up our project environment and calculate the miner reward using Alchemy’s API and ethers

Prerequisites

Before you begin this tutorial, please ensure you have the following:

Connect to Alchemy

We will create a unique Alchemy API key to connect to Ethereum through Alchemy:

Navigate to your Alchemy dashboard and follow the steps below:

  1. From the Alchemy Dashboard, hover over Apps, then click +Create App.
  2. Name your app: BlockRewardDemo.
  3. Select the Ethereum and mainnet for your network.
  4. Click Create app.

Setup project environment

Open VS Code (or your preferred IDE) and enter the following in terminal:

mkdir minerRewardDemo
cd minerRewardDemo

Next, initialize npm (node package manager) with the following command:

npm init

Press enter and answer the project prompt as follows:

package name: (minerRewardDemo)
version: (1.0.0)
description: 
entry point: (index.js)
test command: 
git repository: 
keywords: 
author: 
license: (ISC)

Press enter again to complete the prompt. If successful, a package.json file will have been created in your directory.

Install environment tools

The tools you will need to complete this tutorial are:

  • ethers (A library that makes it easy to interact with Ethereum. However, we will really only use it for it’s conversion functions)
  • axios (to make jsonrpc requests to Alchemy’s API)
  • dotenv (so that you can store your private key and API key safely)

To install the above tools, ensure you are still inside your root folder and type the following commands in terminal:

Ethers

npm install --save ethers

Axios

npm install axios

Dotenv

npm install dotenv --save

Above, we have imported the libraries that we installed and all of the necessary variables to interact with .env

Create a Dotenv File

Create an .env file in your root folder. The file must be named .env or it will not be recognized.

In the .env file, we will store all of our sensitive information (i.e., our Alchemy API key and MetaMask private key).

Copy the following into your .env:

MAINNET_API_URL = "https://eth-mainnet.g.alchemy.com/v2/{YOUR_ALCHEMY_API_KEY}"
  • Replace {YOUR_ALCHEMY_API_KEY} with the respective Alchemy API keys found on Alchemy's dashboard, under VIEW KEY

Call Alchemy methods with axios

In your root folder, create a file named getBlockReward.js and add the following lines of code:

const { default: axios } = require("axios");
const { ethers } = require("ethers");
require("dotenv").config();

const ALCHEMY_API_URL = process.env.MAINNET_API_URL

const getBlockReward = async blockNum => {
// Alchemy requests will go here
}

Above, we have imported axios, ethers, and our dotenv config. We are storing our Alchemy API URL in a variable so that we can use it to make requests to Alchemy’s Ethereum methods. Additionally, we’ve created an async function and passed a block number.

Next, let’s create an async function inside getBlockReward to make a request using eth_getBlockByNumber with our API key. Since we need to provide a hexadecimal value to the method params, let’s also convert the passed blockNum variable to a hex value:

const getBlock = async num => {
    const blockNumHex = ethers.utils.hexlify(num);
    const blockRes = await axios.post(
      ALCHEMY_API_URL,
      {
        jsonrpc: "2.0",
        method: "eth_getBlockByNumber",
        params: [blockNumHex, true],
        id: 0,
      }
    );
    return blockRes.data.result;
  }

📘

NOTE:

We’ve also passed a true Boolean in the params to get full transaction details which include the effective gas price of each transaction in the block.

We’ll also need to create functions for fetching gas usage from transactions and uncle block data via a block hash. Below the blockRes request, create an async function called getGasUsage and pass a transaction hash variable. Then, use axios to make a call to Alchemy’s eth_getTransactionReceipt and pass the hash variable inside the request params:

const getGasUsage = async hash => {
      const txRes = await axios.post(
        ALCHEMY_API_URL,
        {
          jsonrpc: "2.0",
          method: "eth_getTransactionReceipt",
          params: [`${hash}`],
          id: 0,
        }
      );
      return txRes.data.result.gasUsed;
    };

Now, let’s create a similar async function to get uncle block data. We’ll pass a block hash variable and call eth_getBlockByHash with a false Boolean as we do not need the full transaction details for uncle blocks:

const getUncle = async hash => {
      const uncleRes = await axios.post(
        ALCHEMY_API_URL,
        {
          jsonrpc: "2.0",
          method: "eth_getBlockByHash",
          params: [`${hash}`, false],
          id: 0,
        }
      );
      return uncleRes.data.result;
    };

Your entire getBlockReward.js file should look like this:

const { default: axios } = require("axios");
const { ethers } = require("ethers");
require("dotenv").config();

const ALCHEMY_API_URL = process.env.MAINNET_API_URL

const getBlockReward = async blockNum => {

  const getBlock = async num => {
    const blockNumHex = ethers.utils.hexlify(num);
    const blockRes = await axios.post(
      ALCHEMY_API_URL,
      {
        jsonrpc: "2.0",
        method: "eth_getBlockByNumber",
        params: [blockNumHex, true],
        id: 0,
      }
    );
    return blockRes.data.result;
  }

  const getGasUsage = async hash => {
    const txRes = await axios.post(
      ALCHEMY_API_URL,
      {
        jsonrpc: "2.0",
        method: "eth_getTransactionReceipt",
        params: [`${hash}`],
        id: 0,
      }
    );
    return txRes.data.result.gasUsed;
  };

  const getUncle = async hash => {
    const uncleRes = await axios.post(
      ALCHEMY_API_URL,
      {
        jsonrpc: "2.0",
        method: "eth_getBlockByHash",
        params: [`${hash}`, false],
        id: 0,
      }
    );
    return uncleRes.data.result;
  };

};

Nice! That should be all we need in terms of making requests to Ethereum. In the next section let’s calculate a miner reward using the data from the functions we just created.

Calculate a miner reward

Now that we have functions to call all the necessary Alchemy methods, let’s construct the miner reward formula by calculating the sum of all transactions in a block, the sum of burned fees, and the nephew block rewards. Before we jump into it, let’s create a try-catch statement and define a few key variables.

At the very bottom of the getBlockReward function, add the following:

try {
    console.log("fetching block rewards...")
    const block = await getBlock(blockNum);
    const blockNumber = parseInt(block.number)
    const transactions = block.transactions;
    const baseFeePerGas = block.baseFeePerGas;
    const gasUsed = block.gasUsed;

  } catch (error) {
    console.log(error);
  }

Above, we call our getBlock function and store the response in the block variable. From the response, we can extract the block number, transaction array, transaction base fee, and the sum of gas used within the entire block.

Cost of all transactions in a block

First, let’s start with getting the sum of transaction fees for the given block. In order to do this, we can iterate over the transactions in the transaction array to get the gas price. We will also need the amount of gas units to calculate the total fee. To get the amount of gas used for each transaction, we can call our getGasUsage function and pass each transaction hash to the function. Finally, we can multiply the transaction gas usage by the gas price to get the total fee.

Since we later want to sum these transaction fees, let’s also move them to an array.

Ensure you’re still inside the try-catch. Then, create an empty minerTips array and set a sum variable to zero:

let minerTips = [];
let sumMinerTips = 0;

Next, let’s use a for loop to iterate over the transactions in our block and calculate the total fee:

for (const tx of transactions) {
      const txGasUseage = await getGasUsage(tx.hash);
      const totalFee = ethers.utils.formatEther(
        ethers.BigNumber.from(txGasUseage).mul(tx.gasPrice).toString()
      );
      minerTips.push(Number(totalFee));
    }

Above, we use the transaction hash to return the gas usage for each transaction. Then we convert our gasUsage variable using bignumber so that we can multiply it by the gasPrice and format the result into an Ether value as it is currently in wei. Finally, we push the total fee value to an array which we can sum to get the total for all transaction fees in our block:

if (transactions.length > 0) {
      sumMinerTips = minerTips.reduce(
        (prevTip, currentTip) => prevTip + currentTip
      );
    }

As long as there is at least one transaction, we will add the items in the minerTips array and set the total equal to sumMinerTips. Otherwise, sumMinerTips will stay equal to zero.

Sum the burned fees in a block

Next, we’ll need to get the sum of burned fees in our block so that we can subtract it from the total reward. To do this, we need to multiply the total gasUsed by the baseFeePerGas:

const burnedFee = ethers.utils.formatEther(
      ethers.BigNumber.from(gasUsed).mul(baseFeePerGas).toString()
    );

Again, we use bignumber to multiply the wei values and format the result in Ether.

Uncle and nephew rewards

nephew rewards

Let’s start with the nephew reward. Because the nephew reward is 1/32 of the block reward, the reward should be fixed to 0.0625ETH/uncle block. To calculate this add the following lines of code:

const baseBlockReward = 2;
const nephewReward = baseBlockReward / 32;
const uncleCount = block.uncles.length;
const totalNephewReward = uncleCount * nephewReward;

Uncle rewards

In order to calculate the uncle rewards, we’ll need to iterate over each of the block hashes found in the block.uncles property of our block variable. Then, we’ll pass each hash to our getUncle function to extract both the uncle block number and miner. Finally, we’ll push the block number and miner to an uncle rewards array:

let uncleRewardsArr = [];
    for (const hash of block.uncles) {
      const uncle = await getUncle(hash)
      const uncleNum = parseInt(uncle.number)
      const uncleMiner = uncle.miner
      const uncleReward = (uncleNum + 8 - blockNumber) * baseBlockReward / 8;
      uncleRewardsArr.push({
        reward: `${uncleReward}ETH`,
        miner: uncleMiner
      })
    }

Final miner reward calculation

Now that we have the sum of transaction fees and sum of burned fees let’s calculate the miner reward in a scenario where there is no uncle block included:

const blockReward = baseBlockReward + (sumMinerTips - Number(burnedFee));

This will give us the basic block reward, however let’s also account for nephew and uncle rewards by adding the totalNephewReward when the given block contains at least one uncle hash:

if (uncleCount > 0) {
      console.log("Block reward:", blockReward + totalNephewReward + "ETH");
      console.log("miner:", block.miner);
      console.log("Uncle rewards:");
      console.log(uncleRewardsArr);
    } else {
      console.log("Block reward:", blockReward + "ETH");
      console.log("miner:", block.miner);
    }

Above we are printing the total block reward plus the total nephew reward when the block contains uncles. Otherwise, we simply print the block block reward and miner.

Your entire getBlockReward.js file should appear as follows:

const { default: axios } = require("axios");
const { ethers } = require("ethers");
require("dotenv").config();

const ALCHEMY_API_URL = process.env.MAINNET_API_URL;

const getBlockReward = async blockNum => {
  const getBlock = async num => {
    const blockNumHex = ethers.utils.hexlify(num);
    const blockRes = await axios.post(ALCHEMY_API_URL, {
      jsonrpc: "2.0",
      method: "eth_getBlockByNumber",
      params: [blockNumHex, true],
      id: 0,
    });
    return blockRes.data.result;
  };

  const getGasUsage = async hash => {
    const txRes = await axios.post(ALCHEMY_API_URL, {
      jsonrpc: "2.0",
      method: "eth_getTransactionReceipt",
      params: [`${hash}`],
      id: 0,
    });
    return txRes.data.result.gasUsed;
  };

  const getUncle = async hash => {
    const uncleRes = await axios.post(ALCHEMY_API_URL, {
      jsonrpc: "2.0",
      method: "eth_getBlockByHash",
      params: [`${hash}`, false],
      id: 0,
    });
    return uncleRes.data.result;
  };

  try {
    console.log("fetching block rewards...");
    const block = await getBlock(blockNum);
    const blockNumber = parseInt(block.number);
    const transactions = block.transactions;
    const baseFeePerGas = block.baseFeePerGas;
    const gasUsed = block.gasUsed;

    let minerTips = [];
    let sumMinerTips = 0;
    for (const tx of transactions) {
      const txGasUseage = await getGasUsage(tx.hash);
      const totalFee = ethers.utils.formatEther(
        ethers.BigNumber.from(txGasUseage).mul(tx.gasPrice).toString()
      );
      minerTips.push(Number(totalFee));
    }

    if (transactions.length > 0) {
      sumMinerTips = minerTips.reduce(
        (prevTip, currentTip) => prevTip + currentTip
      );
    }

    const burnedFee = ethers.utils.formatEther(
      ethers.BigNumber.from(gasUsed).mul(baseFeePerGas).toString()
    );

    const baseBlockReward = 2;
    const nephewReward = baseBlockReward / 32;
    const uncleCount = block.uncles.length;
    const totalNephewReward = uncleCount * nephewReward;

    let uncleRewardsArr = [];
    for (const hash of block.uncles) {
      const uncle = await getUncle(hash);
      const uncleNum = parseInt(uncle.number);
      const uncleMiner = uncle.miner;
      const uncleReward = ((uncleNum + 8 - blockNumber) * baseBlockReward) / 8;
      uncleRewardsArr.push({
        reward: `${uncleReward}ETH`,
        miner: uncleMiner,
      });
    }

    const blockReward = baseBlockReward + (sumMinerTips - Number(burnedFee));

    if (uncleCount > 0) {
      console.log("Block reward:", blockReward + totalNephewReward + "ETH");
      console.log("miner:", block.miner);
      console.log("Uncle rewards:");
      console.log(uncleRewardsArr);
    } else {
      console.log("Block reward:", blockReward + "ETH");
      console.log("miner:", block.miner);
    }
  } catch (error) {
    console.log(error);
  }
};

Okay we’re almost there! At the bottom of your file call the getBlockReward function and pass any block number inside:

getBlockReward(15349734);

Finally, ensure you’re inside your root folder, then run the following command:

node getBlockReward.js

If successful, you should see these results in console:

fetching block rewards...
Block reward: 2.066205744642072ETH
miner: 0xea674fdde714fd979de3edf0f56aa9716b898ec8
Uncle rewards:
[
  {
    reward: '1.75ETH',
    miner: '0x8b4de256180cfec54c436a470af50f9ee2813dbb'
  }
]

🥳 Woohoo! Nice work, you’ve calculated a block reward and completed the tutorial! 🥳

If you enjoyed this tutorial on calculating a miner reward, give us a tweet @AlchemyPlatform! And don't forget to join our Discord server to meet other blockchain devs, builders, and entrepreneurs!


ReadMe