Multicall: How to Make Multiple Ethereum Calls in a Single Request
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 ↗.