Destiner's Notes

Multicall: How to Make Multiple Ethereum Calls in a Single Request

11 April 2022

Introduction

Fetching data from the blockchain is not the simplest feat. Usually, you have to query multiple smart contracts several times each, which leads to sending tens or even hundreds of JSON-RPC requests. This hurts the developer experience. Additionally, it also impacts your app performance.

Thankfully, there’s a better way. By using Multicall, you will be able to fetch any amount of data from the blockchain in a single HTTP request.

How does it work

First of all, Multicall is a smart contract deployed to Ethereum (and many other chains). It has a single function aggregate, which, well, aggregates multiple contract calls into a single call. Here’s the source code:

function aggregate(Call[] memory calls) public returns (uint256 blockNumber, bytes[] memory returnData) {
    blockNumber = block.number;
    returnData = new bytes[](calls.length);
    for(uint256 i = 0; i < calls.length; i++) {
        (bool success, bytes memory ret) = calls[i].target.call(calls[i].callData);
        require(success, "Multicall aggregate: call failed");
        returnData[i] = ret;
    }
}

As you can see, the code is pretty simple. Multicall works as a proxy for your calls, fetching the data, and then passing it back to you.

Usage

It’s all cool, but how to use it?

I’ll be using ethers, but the process is similar to other web3 libraries. First, you need to initialize the contract instance:

import { providers, Contract } from 'ethers';

import * as multicallAbi from './abi/multicall.json';

const key = 'YOUR_KEY_HERE';
const provider = new providers.AlchemyProvider(1, key);
const multicallAddress = '0x5e227AD1969Ea493B43F840cfF78d08a6fc17796';

const multicall = new Contract(multicallAddress, multicallAbi, provider);

Then, write the fetching function. We will fetch the balance of DAI inside the DAI/ETH Uniswap pool:

async function fetch() {
  const daiAddress = '0x6b175474e89094c44da98b954eedeac495271d0f';
  const balanceOfSelector = `70a08231`; // keccak256('balanceOf')
  const balanceOfParams = `0000000000000000000000002a1530c4c41db0b0b2bb646cb5eb1a67b7158667`; // Uniswap pool address padded to 64 characters

  const calls = [{
    target: daiAddress,
    callData: `0x${balanceOfSelector}${balanceOfParams}`,
  }];
  const result = await multicall.aggregate(calls);
  const data = result[1];

fetch();

If you run this code and print the value of the data variable, you’ll see that it indeed holds the token balance. By adding more calls to the calls array, you make multiple read queries that will be processed in a single JSON-RPC call.

Querying ETH balance

This works well for querying contract data, but Multicall also allows you to fetch the ETH balance of multiple wallets in a single request.

Let’s keep the Multicall contract initialization code, but modify the fetch function:

async function fetch() {
  const ethBalanceSelector = `4d2301cc`; // keccak256('getEthBalance')
  const walletAParams = `000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa96045`;
  const walletBParams = `000000000000000000000000279ee0f94b4f59198742c53650a103f0d329e0a2`;
  const walletCParams = `0000000000000000000000007a1979f8dc3c1e25f506c77c4c0dd0c2113b6cd8`;

  const calls = [
    {
      target: multicallAddress,
      callData: `0x${ethBalanceSelector}${walletAParams}`,
    },
    {
      target: multicallAddress,
      callData: `0x${ethBalanceSelector}${walletBParams}`,
    },
    {
      target: multicallAddress,
      callData: `0x${ethBalanceSelector}${walletCParams}`,
    },
  ];
  const result = await multicall.aggregate(calls);
  const [ethBalanceA, ethBalanceB, ethBalanceC] = result[1];
}

How does this work? First, the Mutlicall contract provides a few helper functions to query blockchain data, including current block parameters, block hash of a given block, and ETH balance of a given wallet.

Here, we use both Multicall’s aggregate and getEthBalance functions to query the balance of multiple wallets.

Historical calls

It is also possible to query historicall data using Multicall. Just add a blockTag when sending the calls:

const result = await multicall.aggregate(calls, {
  blockTag: '0xD59F80',
});

One caveat is that it’s impossible to query historical blocks before the Multicall was deployed. For example, Multicall was deployed to Ethereum on block 7929876. It is not possible to use Multicall to query any historical data before that block since the contract didn’t exist back then.

References

You can see the source code of the Multicall contract on GitHub.

You can also see the deployed contract on Ethereum, Polygon, and BSC.

To abstract the complexity of dealing with ABI encoding, you can use ethcall.