Multicall
Batch multiple contract calls into a single transaction
Multicall
Aggregate multiple contract calls into a single transaction, reducing RPC overhead and ensuring atomic state reads.
Contracts
| Contract | Features |
|---|---|
| Multicall | Basic aggregation, reverts on failure |
| Multicall2 | Try/catch support, optional failure handling |
Multicall
Basic batching that reverts if any call fails.
aggregate
struct Call {
address target;
bytes callData;
}
function aggregate(Call[] memory calls)
public
returns (uint256 blockNumber, bytes[] memory returnData);Helper Functions
function getEthBalance(address addr) public view returns (uint256 balance);
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash);
function getLastBlockHash() public view returns (bytes32 blockHash);
function getCurrentBlockTimestamp() public view returns (uint256 timestamp);
function getCurrentBlockDifficulty() public view returns (uint256 difficulty);
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit);
function getCurrentBlockCoinbase() public view returns (address coinbase);Multicall2
Enhanced version with try/catch and block info.
tryAggregate
Execute calls with optional failure tolerance.
struct Result {
bool success;
bytes returnData;
}
function tryAggregate(bool requireSuccess, Call[] memory calls)
public
returns (Result[] memory returnData);Parameters:
requireSuccess: If true, reverts on any failure; if false, returns failure info
blockAndAggregate
Aggregate with block information.
function blockAndAggregate(Call[] memory calls)
public
returns (
uint256 blockNumber,
bytes32 blockHash,
Result[] memory returnData
);tryBlockAndAggregate
Combined try/catch with block info.
function tryBlockAndAggregate(bool requireSuccess, Call[] memory calls)
public
returns (
uint256 blockNumber,
bytes32 blockHash,
Result[] memory returnData
);Usage Examples
Fetch Multiple Balances
Multicall2 multicall = Multicall2(MULTICALL_ADDRESS);
// Build calls
Multicall2.Call[] memory calls = new Multicall2.Call[](3);
calls[0] = Multicall2.Call({
target: WLUX,
callData: abi.encodeWithSignature("balanceOf(address)", user)
});
calls[1] = Multicall2.Call({
target: LUXD,
callData: abi.encodeWithSignature("balanceOf(address)", user)
});
calls[2] = Multicall2.Call({
target: LETH,
callData: abi.encodeWithSignature("balanceOf(address)", user)
});
// Execute
(, bytes[] memory results) = multicall.aggregate(calls);
// Decode
uint256 wluxBalance = abi.decode(results[0], (uint256));
uint256 luxdBalance = abi.decode(results[1], (uint256));
uint256 lethBalance = abi.decode(results[2], (uint256));Query AMM Pools
// Get reserves from multiple pairs
Multicall2.Call[] memory calls = new Multicall2.Call[](2);
calls[0] = Multicall2.Call({
target: wluxLuxdPair,
callData: abi.encodeWithSignature("getReserves()")
});
calls[1] = Multicall2.Call({
target: wluxLethPair,
callData: abi.encodeWithSignature("getReserves()")
});
(uint256 blockNumber, bytes[] memory results) = multicall.aggregate(calls);
// Decode reserves
(uint112 reserve0_1, uint112 reserve1_1,) = abi.decode(
results[0], (uint112, uint112, uint32)
);
(uint112 reserve0_2, uint112 reserve1_2,) = abi.decode(
results[1], (uint112, uint112, uint32)
);With Failure Tolerance
// Some calls might fail (e.g., tokens without balanceOf)
Multicall2.Call[] memory calls = new Multicall2.Call[](5);
// ... build calls
// Allow failures
Multicall2.Result[] memory results = multicall.tryAggregate(false, calls);
for (uint i = 0; i < results.length; i++) {
if (results[i].success) {
uint256 balance = abi.decode(results[i].returnData, (uint256));
// Process balance
} else {
// Handle failure
}
}Lending Pool State
// Get user account data across lending protocols
Multicall2.Call[] memory calls = new Multicall2.Call[](3);
calls[0] = Multicall2.Call({
target: lendingPool,
callData: abi.encodeWithSignature("getUserAccountData(address)", user)
});
calls[1] = Multicall2.Call({
target: lendingPool,
callData: abi.encodeWithSignature("getReserveData(address)", WLUX)
});
calls[2] = Multicall2.Call({
target: lendingPool,
callData: abi.encodeWithSignature("getReserveData(address)", LUXD)
});
(
uint256 blockNumber,
bytes32 blockHash,
Multicall2.Result[] memory results
) = multicall.blockAndAggregate(calls);
// All data is from the same blockTypeScript/ethers.js
import { Contract } from 'ethers';
const multicall = new Contract(MULTICALL_ADDRESS, MULTICALL_ABI, provider);
// Build calls
const calls = [
{
target: tokenAddress,
callData: tokenInterface.encodeFunctionData('balanceOf', [user])
},
{
target: tokenAddress,
callData: tokenInterface.encodeFunctionData('totalSupply')
}
];
// Execute
const [blockNumber, results] = await multicall.callStatic.aggregate(calls);
// Decode
const balance = tokenInterface.decodeFunctionResult('balanceOf', results[0]);
const supply = tokenInterface.decodeFunctionResult('totalSupply', results[1]);Gas Optimization
Multicall saves gas by:
- Single RPC round-trip instead of N
- Shared transaction overhead
- Atomic block state reads
| Calls | Individual RPC | Multicall | Savings |
|---|---|---|---|
| 5 | ~100ms | ~25ms | 75% |
| 10 | ~200ms | ~30ms | 85% |
| 50 | ~1000ms | ~50ms | 95% |
Best Practices
- Batch Related Calls: Group calls that need consistent state
- Use Multicall2: Prefer tryAggregate for resilience
- Limit Batch Size: Stay under block gas limit (~500 calls max)
- Cache Results: Store decoded results to avoid re-fetching
- Handle Failures: Use tryAggregate when some calls might fail
Related
- AMM - Pool queries
- Lending - Account data queries
- Smart Accounts - Batch execution