Copy
Ask AI
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
| Approach | RPC Calls | Latency | Gas (if on-chain) |
|---|---|---|---|
| Individual calls | 10 | 10x round trips | ~236,000 |
| Multicall batch | 1 | 1 round trip | ~47,000 |
Common Use Cases
- Portfolio dashboards - Fetch all token balances in one call
- DEX interfaces - Get reserves for multiple pairs
- NFT galleries - Batch tokenURI calls
- Governance UIs - Fetch voting power across protocols

