Multicall3 is a utility contract that enables batching multiple contract calls into a single transaction. This provides gas savings, atomic execution, and simplified interaction patterns for complex multi-step operations. Contract Address: 0xcA11bde05977b3631167028862bE2a173976CA11 Deployment Status: Default preinstall Repository: github.com/mds1/multicall

Key Features

  • Gas Efficiency: Save on base transaction costs by batching calls
  • Atomic Execution: All calls succeed or all revert together (optional)
  • State Consistency: Read multiple values from the same block
  • Flexible Error Handling: Choose between strict or permissive execution
  • Value Forwarding: Send ETH/native tokens with calls

Core Methods

aggregate

Execute multiple calls and revert if any fail.
function aggregate(Call[] calldata calls)
    returns (uint256 blockNumber, bytes[] memory returnData)

tryAggregate

Execute multiple calls, returning success status for each.
function tryAggregate(bool requireSuccess, Call[] calldata calls)
    returns (Result[] memory returnData)

aggregate3

Most flexible method with per-call configuration.
function aggregate3(Call3[] calldata calls)
    returns (Result[] memory returnData)

aggregate3Value

Like aggregate3 but allows sending value with each call.
function aggregate3Value(Call3Value[] calldata calls)
    payable returns (Result[] memory returnData)

Usage Examples

Basic Batch Calls

import { ethers } from "ethers";

const provider = new ethers.JsonRpcProvider("YOUR_RPC_URL");
const MULTICALL3 = "0xcA11bde05977b3631167028862bE2a173976CA11";

// Multicall3 ABI
const multicallAbi = [
  "function aggregate3(tuple(address target, bool allowFailure, bytes callData)[] calls) returns (tuple(bool success, bytes returnData)[] returnData)"
];

const multicall = new ethers.Contract(MULTICALL3, multicallAbi, provider);

// Example: Read multiple token balances
async function getMultipleBalances(tokenAddress, accounts) {
  const erc20Abi = ["function balanceOf(address) view returns (uint256)"];
  const iface = new ethers.Interface(erc20Abi);

  // Prepare calls
  const calls = accounts.map(account => ({
    target: tokenAddress,
    allowFailure: false,
    callData: iface.encodeFunctionData("balanceOf", [account])
  }));

  // Execute multicall
  const results = await multicall.aggregate3(calls);

  // Decode results
  const balances = results.map((result, i) => {
    if (result.success) {
      return {
        account: accounts[i],
        balance: iface.decodeFunctionResult("balanceOf", result.returnData)[0]
      };
    }
    return { account: accounts[i], balance: null };
  });

  return balances;
}

Complex DeFi Operations

Combine multiple DeFi operations atomically:
// Swap and stake in one transaction
async function swapAndStake(tokenIn, tokenOut, amountIn, poolAddress) {
  const calls = [
    // 1. Approve router
    {
      target: tokenIn,
      allowFailure: false,
      callData: iface.encodeFunctionData("approve", [router, amountIn])
    },
    // 2. Perform swap
    {
      target: router,
      allowFailure: false,
      callData: iface.encodeFunctionData("swap", [tokenIn, tokenOut, amountIn])
    },
    // 3. Approve staking
    {
      target: tokenOut,
      allowFailure: false,
      callData: iface.encodeFunctionData("approve", [poolAddress, amountOut])
    },
    // 4. Stake tokens
    {
      target: poolAddress,
      allowFailure: false,
      callData: iface.encodeFunctionData("stake", [amountOut])
    }
  ];

  return await multicall.aggregate3(calls);
}

Reading Protocol State

Get consistent protocol state in one call:
async function getProtocolState(protocol) {
  const calls = [
    { target: protocol, callData: "0x18160ddd", allowFailure: false }, // totalSupply()
    { target: protocol, callData: "0x313ce567", allowFailure: false }, // decimals()
    { target: protocol, callData: "0x06fdde03", allowFailure: false }, // name()
    { target: protocol, callData: "0x95d89b41", allowFailure: false }, // symbol()
  ];

  const results = await multicall.aggregate3(calls);
  // All values from the same block
  return decodeResults(results);
}

Advanced Features

Value Forwarding

Send native tokens with calls using aggregate3Value:
const calls = [
  {
    target: weth,
    allowFailure: false,
    value: ethers.parseEther("1.0"),
    callData: iface.encodeFunctionData("deposit")
  },
  {
    target: contract2,
    allowFailure: false,
    value: ethers.parseEther("0.5"),
    callData: iface.encodeFunctionData("buyTokens")
  }
];

await multicall.aggregate3Value(calls, {
  value: ethers.parseEther("1.5") // Total ETH to send
});

Error Handling Strategies

// Strict mode - revert if any call fails
const strictCalls = calls.map(call => ({
  ...call,
  allowFailure: false
}));

// Permissive mode - continue even if some fail
const permissiveCalls = calls.map(call => ({
  ...call,
  allowFailure: true
}));

// Mixed mode - critical calls must succeed
const mixedCalls = [
  { ...criticalCall, allowFailure: false },
  { ...optionalCall, allowFailure: true }
];

Common Use Cases

Query multiple token balances for multiple users efficiently.
Fetch prices from multiple DEXs in a single call for best execution.
Cast votes on multiple proposals in one transaction.
Claim rewards, compound, and rebalance positions atomically.
Mint, transfer, or approve multiple NFTs efficiently.

Gas Optimization

Multicall3 saves gas through:
  1. Base Fee Savings: Pay the 21,000 gas base fee only once
  2. Storage Optimization: Warm storage slots across calls
  3. Reduced State Changes: Fewer transaction state transitions
Typical savings: 20-40% for batching 5+ calls

Best Practices

  • Batch Size: Optimal batch size is 10-50 calls depending on complexity
  • Gas Limits: Set appropriate gas limits for complex batches
  • Error Handling: Use allowFailure wisely based on criticality
  • Return Data: Decode return data carefully, checking success flags
  • Reentrancy: Be aware of potential reentrancy in batched calls

Comparison with Alternatives

FeatureMulticall3Multicall2Manual Batching
Gas EfficiencyExcellentGoodPoor
Error HandlingFlexibleBasicN/A
Value SupportYesNoYes
DeploymentStandard addressVariesN/A
Block ConsistencyYesYesNo

Integration Libraries

Popular libraries with Multicall3 support:
  • ethers-rs: Rust implementation
  • web3.py: Python Web3 library
  • viem: TypeScript alternative to ethers
  • wagmi: React hooks for Ethereum

Troubleshooting

IssueSolution
”Multicall3: call failed”Check individual call success flags
Gas estimation failureIncrease gas limit or reduce batch size
Unexpected revertOne of the calls with allowFailure: false failed
Value mismatchEnsure total value sent matches sum of individual values

Further Reading