Skip to main content
Batch multiple contract calls into a single RPC request using Multicall3.
import { ABI } from '@tevm/voltaire/ABI';
import { Hex } from '@tevm/voltaire/Hex';
import { Address } from '@tevm/voltaire/Address';
import * as Abi from '@tevm/voltaire/Abi';

// ERC20 balanceOf ABI
const erc20Abi = [
  {
    type: "function",
    name: "balanceOf",
    stateMutability: "view",
    inputs: [{ type: "address", name: "account" }],
    outputs: [{ type: "uint256", name: "balance" }]
  }
] as const;

// Multicall3 aggregate ABI
const multicall3Abi = [
  {
    type: "function",
    name: "aggregate3",
    stateMutability: "payable",
    inputs: [{
      type: "tuple[]",
      name: "calls",
      components: [
        { type: "address", name: "target" },
        { type: "bool", name: "allowFailure" },
        { type: "bytes", name: "callData" }
      ]
    }],
    outputs: [{
      type: "tuple[]",
      name: "results",
      components: [
        { type: "bool", name: "success" },
        { type: "bytes", name: "returnData" }
      ]
    }]
  }
] as const;

// Token addresses to query (example mainnet tokens)
const tokenAddresses = [
  "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC
  "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT
  "0x6B175474E89094C44Da98b954EescdeCB5BFF4dC", // DAI
  "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", // WBTC
  "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH
  "0x514910771AF9Ca656af840dff83E8264EcF986CA", // LINK
  "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", // UNI
  "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9", // AAVE
  "0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2", // MKR
  "0xc00e94Cb662C3520282E6f5717214004A7f26888"  // COMP
];

// Account to check balances for
const account = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";

// Multicall3 address (same on all chains)
const multicall3Address = "0xcA11bde05977b3631167028862bE2a173976CA11";

// Encode each balanceOf call
const calls = tokenAddresses.map(tokenAddress => {
  const callData = Abi.Function.encodeParams(
    erc20Abi[0],
    [account]
  );

  return {
    target: tokenAddress,
    allowFailure: true, // Don't revert if one call fails
    callData: Hex.fromBytes(callData)
  };
});

// Encode the multicall aggregate3 call
const multicallData = Abi.Function.encodeParams(
  multicall3Abi[0],
  [calls]
);

// This single call replaces 10 individual RPC calls
const multicallHex = Hex.fromBytes(multicallData);

// Decode results after execution
function decodeMulticallResults(resultData: Uint8Array) {
  const results = Abi.Function.decodeResult(multicall3Abi[0], resultData);

  const balances = results[0].map((result: { success: boolean; returnData: string }, i: number) => {
    if (!result.success) {
      return { token: tokenAddresses[i], balance: null, error: true };
    }

    const decoded = Abi.Function.decodeResult(
      erc20Abi[0],
      Hex.toBytes(result.returnData)
    );

    return {
      token: tokenAddresses[i],
      balance: decoded[0] as bigint,
      error: false
    };
  });

  return balances;
}

// Gas savings calculation
const gasPerCall = 21000n + 2600n; // Base + cold SLOAD
const singleCallsGas = gasPerCall * BigInt(tokenAddresses.length);
const multicallOverhead = 21000n + 700n; // Base + multicall logic
const multicallGas = multicallOverhead + (2600n * BigInt(tokenAddresses.length));
const gasSaved = singleCallsGas - multicallGas;

console.log(`Single calls: ${singleCallsGas} gas`);
console.log(`Multicall: ${multicallGas} gas`);
console.log(`Saved: ${gasSaved} gas (${(gasSaved * 100n) / singleCallsGas}%)`);
Multicall3 is deployed at the same address on all EVM chains. It’s the standard way to batch read operations and reduce RPC calls in dApps.

Benefits

ApproachRPC CallsLatencyGas (if on-chain)
Individual calls1010x round trips~236,000
Multicall batch11 round trip~47,000

Common Use Cases

  1. Portfolio dashboards - Fetch all token balances in one call
  2. DEX interfaces - Get reserves for multiple pairs
  3. NFT galleries - Batch tokenURI calls
  4. Governance UIs - Fetch voting power across protocols