@0xsequence/multicall
An Ethereum provider wrapper that aggregates multiple operations in one, reducing the network load on clients and servers. The project aims to be plug-and-play with existing ether.js integrations.
For more info see 0xsequence project page.
Inspired by MakerDAO Multicall.js.
Installation
yarn add @0xsequence/multicall
or
npm install --save @0xsequence/multicall
Usage
Sequence Multicall works by implementing ethers.Provider
and wrapping an existing ethers.Provider
; this wrapped provider can transparently aggregate supported JSON-RPC calls.
import { providers } from '@0xsequence/multicall'
import { providers as ethersProviders } from 'ethers'
const provider = new providers.MulticallProvider(new ethersProviders.JsonRpcProvider("https://cloudflare-eth.com/"))
Making aggregated calls
Multicall leverages RPC calls' asynchronous nature to perform the aggregation; it implements a buffer with a configurable 50ms delay and aggregates all operations received within that window.
Explicit usage of the functionality can be forced by making multiple calls using Promise.all
.
const [balance, supply] = await Promise.all([
provider.getBalance("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
dai.totalSupply()
])
Methods can also be aggregated without using Promise.all
, as long as there are no await
in between calls.
const balance = await provider.getBalance("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
const supply = await dai.totalSupply()
const balancePromise = provider.getBalance("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
const supplyPromise = dai.totalSupply()
const balance = await balancePromise
const supply = await supplyPromise
Using the provider
The MulticallProvider
instance can be used in any context where an ethers.Provider is expected, including contract interfaces, middlewares, or libraries; all calls to the same provider are candidates for aggregation.
const abi = [
"function balanceOf(address owner) view returns (uint256)",
"function totalSupply() view returns (uint256)",
"function symbol() view returns (string)",
]
const uni = new ethers.Contract("0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", abi, provider)
const dai = new ethers.Contract("0x6B175474E89094C44Da98b954EedeAC495271d0F", abi, provider)
const uniTotalSupplyPromise = uni.totalSupply()
const [totalSupply, balance, daiSymbol, uniSymbol] = await Promise.all([
dai.totalSupply(),
dai.balanceOf("0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B"),
dai.symbol(),
uni.symbol()
])
const uniTotalSupply = await uniTotalSupplyPromise
Supported methods
The following JSON-RPC methods are supported for call aggregation:
Method | Supported | Implemented | Notes |
---|
eth_call | Yes | Yes | Requests containing from , gasPrice or value aren't aggregated. |
eth_getBalance | Yes | Yes | |
eth_getCode | Yes | Yes | |
eth_blockNumber | Yes | No | |
All other RPC methods that are part of the standard are forwarded to the parent provider without any modifications.
⚠️ Using mixed blocktags will make some calls skip aggregation.
Error handling
The multicall wrapper is designed to work with any exiting ether.js integration transparently; this includes error handling for cases when multicall fails, is wrongly configured, or the contract does not support it.
JSON-RPC Calls are forwarded to the parent provider on any of the following cases:
- Multicall contract is not deployed on the given network
- Individual call fails (only failed calls are forwarded)
- Batch call fails (all calls are forwarded)
- Invalid RPC Call (invalid address, etc.)
- Mixed blocktags within a batch
- Unsupported special parameters (see supported methods)
- Unsupported method
Configuration
The MulticallProvider comes with a pre-defined configuration; it's ready to work out-of-the-box on the networks: Mainnet, Ropsten, Kovan, Rinkeby, Görli, and Matic (Mainnet).
DEFAULT_CONF = {
batchSize: 50,
timeWindow: 50,
contract: "0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80"
}
Supported networks
The utility contract is 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80
, it has been deployed using an Universal Deployer and it uses the same address on all networks. It can be used on any of these chains without configuration changes.
Network | Address | Deployed |
---|
Mainnet | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
Ropsten | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
Rinkeby | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
Kovan | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
Görli | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
Matic | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
Mumbai (Matic testnet) | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
Arbitrum testnet | 0xCa731e0f33Afbcfa9363d6F7449d1f5447d10C80 | Yes |
It can be deployed on any network that supports the CREATE2
opcode.