npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@juglipaff/multicall4

v1.0.1

Published

Stack-machine multicall contract. Batches arbitrary calls with control flow, arithmetic, and result piping in a single transaction.

Readme

Multicall4

release npm coverage license

[!CAUTION] This code has not been audited. Use at your own risk. No warranty is provided, express or implied. Do not deploy to production without an independent security review.

Never approve ERC-20 tokens to Multicall4 directly. See Dangers and mitigations.

A Turing-complete call batcher for atomic execution of dynamic user workflows onchain.

Why

Existing multicall primitives (e.g. multicall3, protocol-specific batchers) share the same shape: a hardcoded list of (target, calldata) pairs, executed sequentially with results returned as an array. That covers the easy case - independent reads or independent writes fanned out to save round trips, but it breaks down the moment the actions become dependent on each other.

Concrete examples where the flat-list model fails:

  • Approve exactly the amount a router quoted, then swap using that same amount, in one transaction.
  • Read a pool's reserves, compute the optimal input, and execute the swap without a second RPC round trip or a stale-quote race.
  • Deploy a contract via CREATE2 and immediately initialize it at its own predicted address in the same call.
  • Try one execution path; if it reverts, fall back to another without leaking the failure to the outer caller.

The current workarounds either split calls into multiple transactions, which loses atomicity, exposes an MEV surface, and introduces stale-data races, or require a bespoke Solidity contract per workflow. The underlying gap is expressiveness. On-chain execution primitives don't offer the atomicity guarantees dependent workflows need. The natural evolution of multicall, the one that closes the gap end-to-end, is to make the flow Turing-complete: give the program branching, arithmetic, and inline data flow between calls, all resolved on-chain at execution time, atomically.

What this is

Multicall4 is a Turing-complete call batcher for atomic execution of dynamic user workflows onchain. Under the hood it is a small bytecode interpreter: a single external entrypoint, multicall(bytes calldata program), reads program as a sequence of opcodes over a byte stack and executes it inside one transaction.

The execution model

program is a linear stream of opcode bytes and their inline operands. Each opcode reads its arguments from the top of the stack (and, for PUSH, from the program itself), performs a bounded amount of work, and pushes any results back onto the stack. Multicall4 gives the caller a scriptable environment where each call can:

  • Branch on the outcome of the previous call,
  • Feed the return data of one call into the calldata of the next,
  • Compute values inline instead of pre-baking them into calldata (arithmetic, bitwise, hashing),
  • Deploy contracts and immediately use their address (CREATE, CREATE2),
  • Decide at runtime whether to RETURN a chosen slice of the stack or REVERT with a chosen reason.

The stack is untyped: everything is bytes. Operands are big-endian and their widths are fixed per-opcode. Program indexes and stack offsets are uint32, so a program cannot exceed ~4 GiB. Transaction calldata itself hits this limit long before.

Opcode set (V1)

Byte counts in Inline / Pops / Pushes cells: the first line is the total; subsequent lines number each field with its width in bytes.

| Byte | Opcode | Inline (bytes) | Pops (bytes) | Pushes (bytes) | Description | |---|---|---|---|---|---| | 0x00 | PUSH | 4 + length1. length (4)2. data (length) | 0 | length1. data (length) | Copies length bytes of inline program data onto the top of the stack. | | 0x01 | JUMPI | 0 | 51. condition (1)2. jumpIndex (4) | 0 | If condition != 0, sets the program counter to jumpIndex. Otherwise falls through. | | 0x02 | RETURN | 0 | 81. offset (4)2. size (4) | 0 | Halts and returns size bytes of the current stack starting at offset. | | 0x03 | REVERT | 0 | 81. offset (4)2. size (4) | 0 | Reverts with size bytes of the current stack starting at offset as the revert reason. | | 0x04 | PC | 0 | 0 | 41. current program counter (4) | Pushes the current program counter. | | 0x05 | CALL | 0 | 56 + length1. calldata (length)2. to (20)3. value (32)4. length (4) | 5 + return-data length1. return data (return-data length)2. return-data length (4)3. success flag (1) | External call to to with value wei and calldata. Downstream msg.sender is Multicall4 itself. | | 0x06 | CREATE | 0 | 36 + length1. initcode (length)2. value (32)3. length (4) | 211. deployed address (20, zero on failure)2. success flag (1) | Deploys a new contract via CREATE with value wei and initcode. | | 0x07 | CREATE2 | 0 | 68 + length1. initcode (length)2. value (32)3. salt (32)4. length (4) | 211. deployed address (20, zero on failure)2. success flag (1) | Deploys a new contract via CREATE2 with value wei, salt, and initcode. | | 0x08 | DUP | 0 | 81. offset (4)2. size (4) | size1. bytes of the resulting stack starting at offset (size) | Duplicates size bytes at offset onto the top of the stack. | | 0x09 | SWAP | 0 | 81. offset (4)2. size (4) | 0 | Swaps the size bytes at offset with the top size bytes of the stack. Reverts if the two regions overlap. | | 0x0a | POP | 0 | 81. offset (4)2. size (4) | 0 | Removes size bytes at offset from the stack. | | 0x0b | BLOCKHASH | 0 | 321. blockNumber (32) | 321. blockhash(blockNumber) (32) | Pushes blockhash(blockNumber) (zero for blocks outside the last 256). | | 0x0c | COINBASE | 0 | 0 | 201. block.coinbase (20) | Pushes block.coinbase. | | 0x0d | BASEFEE | 0 | 0 | 321. block.basefee (32) | Pushes block.basefee. | | 0x0e | PREVRANDAO | 0 | 0 | 321. block.prevrandao (32) | Pushes block.prevrandao. | | 0x0f | CHAINID | 0 | 0 | 321. block.chainid (32) | Pushes block.chainid. | | 0x10 | GASLIMIT | 0 | 0 | 321. block.gaslimit (32) | Pushes block.gaslimit. | | 0x11 | NUMBER | 0 | 0 | 321. block.number (32) | Pushes block.number. | | 0x12 | TIMESTAMP | 0 | 0 | 321. block.timestamp (32) | Pushes block.timestamp. | | 0x13 | GAS | 0 | 0 | 321. gasleft() (32) | Pushes gasleft(). | | 0x14 | ADDRESS | 0 | 0 | 201. address(this) (20) | Pushes address(this) (i.e. Multicall4). | | 0x15 | CALLER | 0 | 0 | 201. msg.sender (20) | Pushes msg.sender of the enclosing multicall(...) call. | | 0x16 | CALLVALUE | 0 | 0 | 321. msg.value (32) | Pushes msg.value of the enclosing multicall(...) call. | | 0x17 | GASPRICE | 0 | 0 | 321. tx.gasprice (32) | Pushes tx.gasprice. | | 0x18 | ORIGIN | 0 | 0 | 201. tx.origin (20) | Pushes tx.origin. | | 0x19 | BALANCE | 0 | 201. address (20) | 321. address.balance (32) | Pushes the native balance of address. | | 0x1a | EXTCODESIZE | 0 | 201. address (20) | 321. address.code.length (32) | Pushes the deployed code size of address. | | 0x1b | EXTCODECOPY | 0 | 281. address (20)2. offset (4)3. length (4) | length1. address.code[offset : offset + length] (length) | Pushes length bytes of address's deployed code starting at offset. Out-of-range bytes read as zero (EVM semantics). | | 0x1c | EXTCODEHASH | 0 | 201. address (20) | 321. keccak256(address.code) (32) | Pushes keccak256(address.code) (zero if account has no code, per EVM semantics). | | 0x1d | SHA3 | 0 | 81. offset (4)2. size (4) | 321. keccak256(stack[offset : offset + size]) (32) | Computes keccak256 over size bytes of the current stack starting at offset. | | 0x1e | ADD | 0 | 641. a (32)2. b (32) | 321. a + b (32) | Wrapping unsigned addition. | | 0x1f | SUB | 0 | 641. a (32)2. b (32) | 321. a - b (32) | Wrapping unsigned subtraction. | | 0x20 | MUL | 0 | 641. a (32)2. b (32) | 321. a * b (32) | Wrapping unsigned multiplication. | | 0x21 | DIV | 0 | 641. a (32)2. b (32) | 321. a / b (32) | Unsigned division. | | 0x22 | SDIV | 0 | 641. a (32)2. b (32) | 321. a / b (32) | Signed division. | | 0x23 | MOD | 0 | 641. a (32)2. b (32) | 321. a % b (32) | Unsigned modulo. | | 0x24 | SMOD | 0 | 641. a (32)2. b (32) | 321. a % b (32) | Signed modulo. | | 0x25 | EXP | 0 | 641. a (32)2. b (32) | 321. a ** b (32) | Wrapping unsigned exponentiation. | | 0x26 | ADDMOD | 0 | 961. a (32)2. b (32)3. n (32) | 321. (a + b) % n (32) | (a + b) % n with arbitrary-precision intermediate (unsigned). | | 0x27 | MULMOD | 0 | 961. a (32)2. b (32)3. n (32) | 321. (a * b) % n (32) | (a * b) % n with arbitrary-precision intermediate (unsigned). | | 0x28 | EQ | 0 | 641. a (32)2. b (32) | 11. a == b flag (1) | Equality. | | 0x29 | GT | 0 | 641. a (32)2. b (32) | 11. a > b flag (1) | Unsigned greater-than. | | 0x2a | SGT | 0 | 641. a (32)2. b (32) | 11. a > b flag (1) | Signed greater-than. | | 0x2b | LT | 0 | 641. a (32)2. b (32) | 11. a < b flag (1) | Unsigned less-than. | | 0x2c | SLT | 0 | 641. a (32)2. b (32) | 11. a < b flag (1) | Signed less-than. | | 0x2d | NOT | 0 | 321. value (32) | 321. ~value (32) | Bitwise negation. | | 0x2e | AND | 0 | 641. a (32)2. b (32) | 321. a & b (32) | Bitwise AND. | | 0x2f | OR | 0 | 641. a (32)2. b (32) | 321. a | b (32) | Bitwise OR. | | 0x30 | XOR | 0 | 641. a (32)2. b (32) | 321. a ^ b (32) | Bitwise XOR. | | 0x31 | SHL | 0 | 641. a (32)2. b (32) | 321. a << b (32) | Left shift. | | 0x32 | SHR | 0 | 641. a (32)2. b (32) | 321. a >> b (32) | Logical right shift. | | 0x33 | SAR | 0 | 641. a (32)2. b (32) | 321. a >> b (32) | Arithmetic right shift. |

Design: monolith over plugin architecture

Multicall4 is a monolith: every opcode is implemented inline in one contract. A plugin architecture was considered, where each opcode lives in its own contract and Multicall4 acts as a dispatcher that delegatecalls into the user-supplied opcode contract per instruction. That shape would make new opcodes hot-pluggable: any Solidity version could ship a new EVM instruction (say, a future MCOPY2, TSTORE2, or a curve precompile wrapper) and users could adopt it by pointing at a freshly-deployed opcode contract without waiting for a Multicall4 redeploy.

Ultimately the monolith was chosen for gas. The plugin variant costs additional ~700 warm / ~2600 cold gas per opcode for the delegatecall alone. Programs run tens to hundreds of opcodes per invocation, so the overhead compounds into tens of thousands of gas per multicall. That is a permanent tax paid on every call, in exchange for flexibility.

Versioning strategy

Multicall4 is the family name for the underlying stack-machine architecture. Individual opcode-set revisions are versioned as Multicall4V1, Multicall4V2, and so on. When new revisions land, the current revision is not upgraded in place: a new contract file (Multicall4V2.sol) is deployed at a new deterministic address. Consumers pin whichever revision has the opcodes they need.

  • multicall(bytes) ABI stays byte-compatible across revisions. Opcode set and byte assignments are not guaranteed stable across revisions: opcodes may be added, removed, renumbered, or have their semantics changed. Consumers pin the revision they target and only migrate deliberately.
  • Each revision's opcode set and per-chain address are listed in this repo's README under the revision's git tag.
  • The npm package tracks the same numbering: the major version of @juglipaff/multicall4 matches the revision suffix (1.x.yMulticall4V1, 2.x.yMulticall4V2, …).

This trades a small amount of ecosystem bookkeeping (docs + registry updates on new releases) for permanent gas savings on every call.

Dangers and mitigations

The contract holds no persistent storage. Every invocation starts from a clean slate and the stack lives only for the duration of the call, so the blast radius of any error is bounded by the single transaction that triggers it. This does not make misuse safe though.

Never approve ERC-20 tokens to Multicall4 directly

Multicall4 is permissionless - any address can send it any program. An approve(multicall4, N) from Alice is not scoped to Alice. Any transaction from any address can craft a program that calls token.transferFrom(Alice, attacker, N) and drain her balance. This is the same pitfall as any router-style contract that shares an allowance across users, magnified because Multicall4 executes user-supplied bytecode. Do not approve tokens directly to Multicall4 on any chain.

Intended safe usage is a per-user execution proxy (WIP). The proxy holds the ERC-20 allowances instead of Multicall4, accepts programs only from its owner, validates them, and only then forwards to Multicall4.multicall(...). Under this pattern Multicall4 remains the shared execution engine but never holds an allowance from any user.

Dependencies

Runtime (Solidity):

Development / testing:

  • forge-std - Foundry standard library (Test, console, cheatcodes).
  • foundry - build, test, coverage, formatter. Install via foundryup.

Foundry pulls Solidity dependencies as git submodules under lib/.

Usage

Install

npm:

npm install @juglipaff/multicall4

ABI import

Pre-generated ABIs ship in the abi/ directory of the npm package for off-chain clients (ethers.js, viem, wagmi, web3.js, etc.):

import Multicall4V1 from "@juglipaff/multicall4/abi/Multicall4V1.json";
import IMulticall4V1 from "@juglipaff/multicall4/abi/IMulticall4V1.json";

TypeScript projects can import the JSON directly with resolveJsonModule enabled in tsconfig.json. ABI files are regenerated on every release, so no post-install compilation is required.

Program construction

Programs are built off-chain. A program is a raw bytes blob (a concatenation of opcode bytes and their inline operands) passed to multicall(bytes). The example below uses viem to batch two ERC-20 transferFrom calls into one transaction.

[!WARNING] This example approves Multicall4V1 to spend an ERC-20 balance for illustration only. Do not do this on any live chain. Approving tokens to Multicall4V1 directly is unsafe. See Dangers and mitigations.

import {
  createWalletClient,
  encodeFunctionData,
  encodePacked,
  erc20Abi,
  http,
  parseAbi,
  type Address,
  type Hex,
} from "viem";
import { mainnet } from "viem/chains";
import { privateKeyToAccount } from "viem/accounts";
import Multicall4V1Abi from "@juglipaff/multicall4/abi/Multicall4V1.json";

// Opcode byte values. Order matches the Opcode enum in IMulticall4V1.sol.
const PUSH = 0x00;
const CALL = 0x05;

// Encodes: PUSH <calldata>, PUSH <to>, PUSH <value>, PUSH <length>, CALL.
// CALL pop order (bottom-to-top of stack): calldata, to, value, length.
// PUSH header layout: opcode (1 byte) + length (uint32, 4 bytes) + data (`length` bytes).
function encodeCall(to: Address, value: bigint, data: Hex): Hex {
  const dataLen = (data.length - 2) / 2; // strip 0x, 2 hex chars per byte
  return encodePacked(
    ["uint8", "uint32", "bytes",     // PUSH calldata
     "uint8", "uint32", "address",   // PUSH to (20 bytes)
     "uint8", "uint32", "uint256",   // PUSH value (32 bytes)
     "uint8", "uint32", "uint32",    // PUSH length (4 bytes)
     "uint8"],                       // CALL
    [PUSH, dataLen, data,
     PUSH, 20,      to,
     PUSH, 32,      value,
     PUSH, 4,       dataLen,
     CALL],
  );
}

const MULTICALL4_V1: Address = "0x0000000000000000000000000000000000000000"; // fill in per-chain
const TOKEN:         Address = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // e.g. USDC
const ALICE:         Address = "0x1111111111111111111111111111111111111111";
const BOB:           Address = "0x2222222222222222222222222222222222222222";

const account = privateKeyToAccount(process.env.PRIVATE_KEY as Hex);
const client = createWalletClient({ account, chain: mainnet, transport: http() });

const transferA = encodeFunctionData({
  abi: erc20Abi,
  functionName: "transferFrom",
  args: [account.address, ALICE, 1_000_000n],
});
const transferB = encodeFunctionData({
  abi: erc20Abi,
  functionName: "transferFrom",
  args: [account.address, BOB,   2_000_000n],
});

const program = encodePacked(
  ["bytes", "bytes"],
  [encodeCall(TOKEN, 0n, transferA), encodeCall(TOKEN, 0n, transferB)],
);

await client.writeContract({
  address: MULTICALL4_V1,
  abi: Multicall4V1Abi,
  functionName: "multicall",
  args: [program],
});

The same shape works from ethers.js (ethers.solidityPacked) or web3.js (web3.eth.abi.encodePacked). Only the packing helper changes.

Development

This repo uses Foundry for development and testing and git submodules for dependency management.

git clone https://github.com/Juglipaff/multicall4.git
cd multicall4
forge install

### Make changes

forge test # Test and regenerate gas snapshots
forge coverage # Collect coverage - CI fails if < 100% coverage
scripts/extract-abi.sh src abi # Regenerate abis

Deployment

Multicall4 is designed for deterministic cross-chain deployment via the Arachnid CREATE2 factory at 0x4e59b44847b379578588920cA78FbF26c0B4956C.

forge script scripts/Deploy.s.sol --rpc-url $RPC --broadcast

The salt lives in scripts/Deploy.s.sol. Placeholder is bytes32(0). Replace with a vanity salt before mainnet deployment if wanted.