A lightweight TypeScript/JavaScript library built on ethers v6 for DeFi dashboards, on-chain analytics and gas-optimised dApps.
- @evmlord/multicall-sdk – Batch smart-contract calls on Ethereum & EVM networks
-
Gas & RPC Optimized
Combine dozens ofeth_call
into one multicall, slashing HTTP/WebSocket overhead and minimizing latency. -
Fully Typed
Written in TypeScript with built-in declarations—autocomplete your batch calls, interfaces, and return values. -
Failure-Tolerant
Gracefully handle individual call failures (allowFailure
) without aborting the entire batch. -
Rich Decoding
Automatically unpack tuples, structs, arrays and custom errors into plain JS objects & arrays—no manual unpacking. -
EVM & DeFi Focused
Supports Multicall3, Multicall2 and on-chain block helpers (getBlockNumber
,getEthBalance
, etc.) across 280+ networks.
# via yarn
yarn add @evmlord/multicall-sdk
# or npm
npm install @evmlord/multicall-sdk
285 EVM-compatible networks are supported by default, and custom networks can be supported by providing a deployed Multicall contract address.
👉 See the complete list in SUPPORTED_NETWORKS.md
.
import { JsonRpcProvider, WebSocketProvider } from "ethers";
import { Multicall } from "@evmlord/multicall-sdk";
// 1) HTTP RPC URL
const mc1 = new Multicall({
provider: "https://mainnet.infura.io/v3/…",
chainId: 1,
});
// 2) Browser/EIP-1193 (e.g. MetaMask)
const mc2 = new Multicall({
provider: window.ethereum, // auto-wrapped
multicallAddress: "0x…", // override default if deployed elsewhere
});
// 3) Already-constructed ethers Provider
const ws = new WebSocketProvider("wss://…");
const mc3 = new Multicall({ provider: ws, chainId: 5 });
// 4) Custom signer for writing txs
const signer = wallet.connect(provider);
const mc4 = new Multicall({
provider: provider,
signer: signer,
chainId: 56,
});
import erc20Abi from "./abi/ERC20.json";
import { Call } from "@evmlord/multicall-abi";
const token = new ethers.Contract("0x…ERC20", erc20Abi, provider);
// Prepare calls
const calls: Call[] = [
{ contract: token, functionFragment: "balanceOf", args: ["0xYourAddress1"] },
{ contract: token, functionFragment: "balanceOf", args: ["0xYourAddress2"] },
{ contract: token, functionFragment: "balanceOf", args: ["0xYourAddress3"] },
{ contract: token, functionFragment: "totalSupply", args: [] },
];
// Execute a single eth_call via Multicall
const { blockNumber, returnData } = await mc1.aggregate(calls);
// Decode your results
const [balance1] = token.interface.decodeFunctionResult(
"balanceOf",
returnData[0]
);
const [balance2] = token.interface.decodeFunctionResult(
"balanceOf",
returnData[1]
);
const [balance3] = token.interface.decodeFunctionResult(
"balanceOf",
returnData[2]
);
const [supply] = token.interface.decodeFunctionResult(
"totalSupply",
returnData[3]
);
console.log({
blockNumber,
balance1,
balance2,
balance3,
supply,
});
/*
console:
{
blockNumber: 55038412n,
balance1: 76950775000000000000000n,
balance2: 0n,
balance3: 1583902570428472973924450219389n,
supply: 10000000000000000000000000000000000000000000n
}
*/
Method | Description |
---|---|
aggregate(calls: Call[]) |
Reverts on any failing call. Returns { blockNumber, returnData: string[] } . |
tryAggregate(requireSuccess, calls: Call[]) |
Optionally continue on failed calls. Returns Array<{ success: boolean, returnData: string }> |
blockAndAggregate(calls: Call[]) |
Alias for tryBlockAndAggregate(true, calls) . Returns { blockNumber, blockHash, returnData } . |
tryBlockAndAggregate(requireSuccess, calls: Call[]) |
Same as tryAggregate but also provides full block info plus per-call success flags. |
aggregate3(calls: Call3[]) |
Multicall 3 style: each call has an allowFailure flag, and return values are auto-decoded to JS tuples/structs. |
aggregate3Value(calls: Call3Value[]) |
Like aggregate3 , but supports per-call native ETH value and automatically sums msg.value . |
sendAggregate3Value(calls: Call3Value[], overrides: Overrides) |
Like aggregate3Value , but for real on-chain writes, accepts optional Ethers overrides (gasLimit, gasPrice, etc) and returns TransactionResponse , which you can .wait() on. |
// ── aggregate3 example ─────────────────────────────────────────
// call two view methods in one batch, but let one of them fail
const calls3: Call3[] = [
{
contract: token,
functionFragment: "nonExistedProperty", // this will throw
args: [],
allowFailure: true,
},
{
contract: token,
functionFragment: "balanceOf",
args: ["0x5500..."], // wallet address here
allowFailure: true,
},
];
const results3 = await mc1.aggregate3(calls3);
console.log({ results3 });
/*
console:
{
results3: [
[ false, 'Revert: (unrecognized revert: 0x…)' ],
[ true, 76950775000000000000000n ]
]
}
*/
// ── aggregate3Value example ────────────────────────────────────
// imagine a payable helper that charges a small fee per call
const helper = new ethers.Contract(
"0x…EHelperContract", // your helper address
HelperABI, // your ABI
provider
)
// send 0.001 ETH with each call to fetch some on-chain data
const fee = 0.001n * 10n**18n // 0.001 ETH in wei as bigint
const calls3Value: Call3Value[] = [
{
contract: helper,
functionFragment:'getSomeData',
args: [ user ],
allowFailure: false,
value: fee
},
{
contract: helper,
functionFragment:'getOtherData',
args: [ user, 42 ],
allowFailure: true,
value: fee
}
]
const results3Value = await mc1.aggregate3Value(calls3Value)
// returns Array<[success: boolean, data]>
{
const [ok, getSomeData] = results3Value[0];
if (ok) {
console.log('getSomeData →', getSomeData)
}
}
{
const [ok, getOtherData] = results3Value[1];
if (ok) {
console.log('getOtherData →', getOtherData)
}
}
-
aggregate3
You passallowFailure: true
on any call that might revert—failed calls return[false, rawHex]
while successes decode normally. -
aggregate3Value
Each call can carry its own ETH payment (value
), and the SDK automatically sums them into onemsg.value
for the batch. Any call markedallowFailure: true
won’t abort the entire batch if it reverts.
Up until now we’ve only covered the “view” methods (aggregate
, tryAggregate
, aggregate3
, etc). If you need to batch together state-changing calls (optionally with ETH attached) into a single on-chain tx, you can use sendAggregate3Value
:
import { Multicall, Call3Value, ChainId } from "@evmlord/multicall-sdk";
import { ethers } from "ethers";
// 1) Create a signer-backed Multicall instance
const provider = new ethers.JsonRpcProvider("https://rpc.ankr.com/eth");
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
const multicall = new Multicall({
provider,
chainId: ChainId.MAINNET,
signer: wallet, // <- needed for txs
});
// 2) Prepare your payable calls
// each call3Value: { contract, functionFragment, args, allowFailure, value }
const fee = ethers.parseUnits("0.001", "ether"); // per-call ETH fee
const myHelper = new ethers.Contract("0xHelper…", HelperABI, provider);
const calls: Call3Value[] = [
{
contract: myHelper,
functionFragment: "depositAndFetch",
args: ["0xYourAddress"],
allowFailure: false,
value: fee,
},
{
contract: myHelper,
functionFragment: "updateRecord",
args: [42, "hello"],
allowFailure: true,
value: fee,
},
// …etc
];
// 3) Send them all in one tx
const tx = await mc4.sendAggregate3Value(calls, {
gasLimit: 1_200_000,
});
console.log("Multicall tx hash:", tx.hash);
// 4) Wait for it to be mined
const receipt = await tx.wait();
console.log("→ mined in block", receipt.blockNumber);
/**
* Batch-execute multiple non-view calls (each may carry its own ETH value)
* in a single on-chain TX via Multicall3.aggregate3Value.
*
* @param calls – Array of { contract, functionFragment, args, allowFailure, value }
* @param overrides – Ethers transaction overrides (gasLimit, maxPriorityFeePerGas, etc)
* @returns Promise<TransactionResponse>
* @throws if no Signer was provided in the constructor.
*/
sendAggregate3Value(
calls: Call3Value[],
overrides?: Overrides
): Promise<TransactionResponse>;
-
allowFailure: true
on anyCall3Value
lets that individual call revert without failing the entire batch. -
The SDK automatically sums up all
value
fields into onemsg.value
on the multicall.
All return either Promise<bigint>
or Promise<string>
:
-
getEthBalance
Gets the ETH balance of an addressconst ethBalance = await mc1.getEthBalance("address");
-
getBlockHash
Gets the block hashOnly works for 256 most recent, excluding current according to Solidity docs
const blockHash = await mc1.getBlockHash(blockNumber);
-
getLastBlockHash
Gets the last blocks hashconst lastBlockHash = await mc1.getLastBlockHash();
-
getCurrentBlockTimestamp
Gets the current block timestampconst currentBlockTimestamp = await mc1.getCurrentBlockTimestamp();
-
getCurrentBlockDifficulty
Gets the current block difficultyconst currentBlockDifficulty = await mc1.getCurrentBlockDifficulty();
-
getCurrentBlockGasLimit
Gets the current block gas limitconst currentBlockGasLimit = await mc1.getCurrentBlockGasLimit();
-
getCurrentBlockCoinbase
Gets the current block coinbaseconst currentBlockCoinbase = await mc1.getCurrentBlockCoinbase();
This SDK ships with a comprehensive Mocha + Chai + Sinon test suite.
# Run unit tests (Mocha + Chai + Sinon)
yarn test
- Fork & clone
yarn install
- Develop in
src/
, add tests intest/
- Run
yarn test
& Submit a PR
Released under the MIT License.
© 2025 EVMlord