Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
Skill — Copyable reference implementation. Use as-is or customize. See Skills Philosophy.
Multicall
Batch multiple contract read calls into a single RPC request using the Multicall3 contract.
What is Multicall3?
Multicall3 is a smart contract deployed on 70+ EVM chains that aggregates multiple contract calls into a single eth_call. Instead of making N separate RPC requests, you make 1 request that returns all results atomically at the same block.
Benefits:
- Performance: Reduce RPC calls from N to 1
- Consistency: All reads execute at the same block
- Cost: No gas for read operations (uses
eth_call)
- Reliability: Per-call failure handling
Quick Start
import { multicall } from './examples/multicall/index.js';
import * as S from 'effect/Schema';
import * as AbiSchema from 'voltaire-effect/primitives/Abi';
const erc20Abi = S.decodeUnknownSync(AbiSchema.fromArray)([
{
type: 'function',
name: 'name',
stateMutability: 'view',
inputs: [],
outputs: [{ type: 'string', name: '' }],
},
{
type: 'function',
name: 'balanceOf',
stateMutability: 'view',
inputs: [{ type: 'address', name: 'account' }],
outputs: [{ type: 'uint256', name: '' }],
},
]);
const results = await multicall(provider, {
contracts: [
{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
abi: erc20Abi,
functionName: 'name',
},
{
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
abi: erc20Abi,
functionName: 'balanceOf',
args: ['0x742d35Cc6634C0532925a3b844Bc454e4438f44e'],
},
],
});
// Results with allowFailure: true (default)
// [
// { status: 'success', result: 'USD Coin' },
// { status: 'success', result: 1000000n }
// ]
Multiple Contract Reads
Read from multiple contracts in a single call:
const tokens = [
'0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
'0xdAC17F958D2ee523a2206206994597C13D831ec7', // USDT
'0x6B175474E89094C44Da98b954EescdeCb5C27c6B', // DAI
];
const results = await multicall(provider, {
contracts: tokens.flatMap(token => [
{
address: token,
abi: erc20Abi,
functionName: 'name',
},
{
address: token,
abi: erc20Abi,
functionName: 'symbol',
},
{
address: token,
abi: erc20Abi,
functionName: 'decimals',
},
]),
});
// Process results in groups of 3
for (let i = 0; i < results.length; i += 3) {
const name = results[i].status === 'success' ? results[i].result : 'Unknown';
const symbol = results[i + 1].status === 'success' ? results[i + 1].result : '???';
const decimals = results[i + 2].status === 'success' ? results[i + 2].result : 18;
console.log(`${name} (${symbol}): ${decimals} decimals`);
}
Error Handling
With allowFailure: true (default)
Each result includes status and error information:
const results = await multicall(provider, {
contracts: [
{ address: validContract, abi, functionName: 'name' },
{ address: invalidAddress, abi, functionName: 'name' }, // Will fail
{ address: validContract, abi, functionName: 'symbol' },
],
allowFailure: true, // default
});
for (const result of results) {
if (result.status === 'success') {
console.log('Result:', result.result);
} else {
console.error('Failed:', result.error.message);
}
}
With allowFailure: false
Throws on first failure, returns raw values:
try {
const [name, balance, decimals] = await multicall(provider, {
contracts: [...],
allowFailure: false,
});
// Direct values, no status wrapper
console.log(name, balance, decimals);
} catch (error) {
// Thrown if any call fails
console.error('Multicall failed:', error);
}
Block Consistency
Query at a specific block for consistent reads:
// Read at specific block number
const results = await multicall(provider, {
contracts: [...],
blockNumber: 18000000n,
});
// Read at block tag
const results = await multicall(provider, {
contracts: [...],
blockTag: 'safe', // 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized'
});
Batching Control
Control how calls are batched for large multicalls:
// Custom batch size (bytes of calldata per batch)
const results = await multicall(provider, {
contracts: hundredsOfCalls,
batchSize: 2048, // default: 1024
});
// No batching - all in one call
const results = await multicall(provider, {
contracts: calls,
batchSize: 0,
});
Custom Multicall Address
Use a custom Multicall3 deployment:
const results = await multicall(provider, {
contracts: [...],
multicallAddress: '0x1234...', // Custom address
});
Deployless Mode
For chains without Multicall3, deploy inline:
const results = await multicall(provider, {
contracts: [...],
deployless: true, // Deploy Multicall3 bytecode inline
});
Create Bound Function
Create a multicall function with default options:
import { createMulticall } from './examples/multicall/index.js';
const batchCall = createMulticall(provider, {
batchSize: 2048,
multicallAddress: customAddress,
});
// Use without specifying provider each time
const results = await batchCall({
contracts: [...],
});
| Approach | RPC Calls | Block Consistency |
|---|
| Sequential reads | N | No guarantee |
| Promise.all reads | N (parallel) | No guarantee |
| Multicall | 1 | Same block |
For 10 contract reads:
- Sequential: 10 round trips, ~500ms at 50ms latency
- Promise.all: 10 parallel, ~50ms
- Multicall: 1 call, ~50ms + atomic consistency
Supported Chains
Multicall3 is deployed at 0xcA11bde05977b3631167028862bE2a173976CA11 on:
| Chain | Chain ID | Block Deployed |
|---|
| Ethereum | 1 | 14353601 |
| Polygon | 137 | 25770160 |
| Arbitrum One | 42161 | 7654707 |
| Optimism | 10 | 4286263 |
| Base | 8453 | 5022 |
| BSC | 56 | 15921452 |
| Avalanche | 43114 | 11907934 |
| Gnosis | 100 | 21022491 |
| … and 60+ more | | |
Check availability:
import { hasMulticall3, getMulticall3Contract } from './examples/multicall/index.js';
if (hasMulticall3(chainId)) {
const info = getMulticall3Contract(chainId);
console.log(`Available since block ${info.blockCreated}`);
}
Error Types
| Error | Description |
|---|
MulticallEncodingError | Failed to encode a contract call |
MulticallDecodingError | Failed to decode result |
MulticallContractError | Contract call reverted |
MulticallZeroDataError | Call returned empty data (0x) |
MulticallRpcError | RPC call to Multicall3 failed |
MulticallResultsMismatchError | Results count doesn’t match contracts |
API Reference
multicall(provider, parameters)
function multicall<TContracts, TAllowFailure = true>(
provider: TypedProvider,
parameters: {
contracts: TContracts;
allowFailure?: TAllowFailure;
blockNumber?: bigint;
blockTag?: 'latest' | 'earliest' | 'pending' | 'safe' | 'finalized';
multicallAddress?: `0x${string}`;
batchSize?: number;
deployless?: boolean;
}
): Promise<MulticallReturnType<TContracts, TAllowFailure>>;
createMulticall(provider, options?)
function createMulticall(
provider: TypedProvider,
options?: {
multicallAddress?: `0x${string}`;
batchSize?: number;
}
): (params: MulticallParameters) => Promise<MulticallReturnType>;