Skip to main content
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';

const erc20Abi = [
  {
    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: '' }],
  },
] as const;

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: [...],
});

Performance Comparison

ApproachRPC CallsBlock Consistency
Sequential readsNNo guarantee
Promise.all readsN (parallel)No guarantee
Multicall1Same 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:
ChainChain IDBlock Deployed
Ethereum114353601
Polygon13725770160
Arbitrum One421617654707
Optimism104286263
Base84535022
BSC5615921452
Avalanche4311411907934
Gnosis10021022491
… 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

ErrorDescription
MulticallEncodingErrorFailed to encode a contract call
MulticallDecodingErrorFailed to decode result
MulticallContractErrorContract call reverted
MulticallZeroDataErrorCall returned empty data (0x)
MulticallRpcErrorRPC call to Multicall3 failed
MulticallResultsMismatchErrorResults 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>;