A lightweight TypeScript/JavaScript library built on ethers v6 for DeFi dashboards, on-chain analytics and gas-optimised dApps.
npm @evmlord/multicall-sdk latest version CI Build Status MIT license for @evmlord/multicall-sdk Weekly npm downloads for @evmlord/multicall-sdk TypeScript types included Minified bundle size
- @evmlord/multicall-sdk β Batch smart-contract calls on Ethereum & EVM networks
-
Gas & RPC Optimized
Combine dozens ofeth_callinto 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) } }
-
aggregate3You passallowFailure: trueon any call that might revertβfailed calls return[false, rawHex]while successes decode normally. -
aggregate3ValueEach call can carry its own ETH payment (value), and the SDK automatically sums them into onemsg.valuefor the batch. Any call markedallowFailure: truewonβ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: trueon anyCall3Valuelets that individual call revert without failing the entire batch. -
The SDK automatically sums up all
valuefields into onemsg.valueon the multicall.
All return either Promise<bigint> or Promise<string>:
-
getEthBalanceGets the ETH balance of an addressconst ethBalance = await mc1.getEthBalance("address");
-
getBlockHashGets the block hashOnly works for 256 most recent, excluding current according to Solidity docs
const blockHash = await mc1.getBlockHash(blockNumber);
-
getLastBlockHashGets the last blocks hashconst lastBlockHash = await mc1.getLastBlockHash();
-
getCurrentBlockTimestampGets the current block timestampconst currentBlockTimestamp = await mc1.getCurrentBlockTimestamp();
-
getCurrentBlockDifficultyGets the current block difficultyconst currentBlockDifficulty = await mc1.getCurrentBlockDifficulty();
-
getCurrentBlockGasLimitGets the current block gas limitconst currentBlockGasLimit = await mc1.getCurrentBlockGasLimit();
-
getCurrentBlockCoinbaseGets 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