> ## 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.

# Multicall

> Batch multiple contract read calls into a single RPC request

<Info>
  **Skill** — Copyable reference implementation. Use as-is or customize. See [Skills Philosophy](/concepts/skills).
</Info>

# Multicall

Batch multiple contract read calls into a single RPC request using the [Multicall3](https://github.com/mds1/multicall) 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

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
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:

```typescript theme={null}
// 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:

```typescript theme={null}
// 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:

```typescript theme={null}
const results = await multicall(provider, {
  contracts: [...],
  multicallAddress: '0x1234...', // Custom address
});
```

## Deployless Mode

For chains without Multicall3, deploy inline:

```typescript theme={null}
const results = await multicall(provider, {
  contracts: [...],
  deployless: true, // Deploy Multicall3 bytecode inline
});
```

## Create Bound Function

Create a multicall function with default options:

```typescript theme={null}
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

| 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:

```typescript theme={null}
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)

```typescript theme={null}
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?)

```typescript theme={null}
function createMulticall(
  provider: TypedProvider,
  options?: {
    multicallAddress?: `0x${string}`;
    batchSize?: number;
  }
): (params: MulticallParameters) => Promise<MulticallReturnType>;
```
