Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.biconomy.io/llms.txt

Use this file to discover all available pages before exploring further.

ERC-8211 is an Ethereum standard for composable execution on smart accounts: a batch encoding where each parameter declares how to obtain its value at execution time, and what conditions that value must satisfy. The audited reference implementation was developed by the Ethereum Foundation and Biconomy and ships with the MEE v2.2.2 contract release. Two SDKs work together end-to-end:

@biconomy/smart-batching

Builds the ERC-8211 calldata. Type-safe, viem-style API for runtime values, constraints, and storage captures. No infrastructure dependencies — produces standalone ComposableCall[] payloads.

@biconomy/abstractjs

Executes the calldata through Biconomy’s Modular Execution Environment (MEE) — handles gas abstraction, sponsorship, cross-chain orchestration, and signing. The recommended execution layer for any ERC-8211 batch.
You can build with @biconomy/smart-batching and hand the resulting calls to any smart account that can dispatch an executeComposable call — ERC-7579 executor modules, ERC-6900 plugins, ERC-7702-delegated EOAs, or native smart-account implementations all work. Pairing with @biconomy/abstractjs gives you sponsored gas, multichain dispatch, and a single signed payload out of the box.

What ERC-8211 solves

Traditional batched transactions freeze every parameter at signing time. If anything changes between signing and execution — a swap returns fewer tokens than expected, a balance shifts, a bridge delivers late — the batch reverts. Until ERC-8211, the only workaround was deploying a custom contract for every flow. ERC-8211 fixes this with three primitives baked into a single signed batch:
  • Runtime values — a parameter can be a placeholder that resolves on-chain at execution time. The live ERC-20 balance, allowance, or any view function output flows into the next call automatically.
  • On-chain constraintseq, gte, lte, gteSigned, lteSigned, or. Attach bounds to a resolved value and the entire batch reverts atomically if any constraint fails. Acts as a slippage guard, balance floor, or exact-match check.
  • Pre- and post-conditions — plain check calls placed before or after a write assert the world is in the expected state. If a pre-condition fails the write never happens; if a post-condition fails the whole batch reverts and the user pays no gas.
The composability module is audited by Pashov Audit Group (May 2026 report) and deployed deterministically at 0x0000821108B5C9F3fe17E40811bE5b66DaF8f0e7 (module) and 0x00008211dea1Aca67ac55fc44AE3bF88CF41281d (storage) on every supported chain.

Install

# npm
npm install @biconomy/smart-batching @biconomy/abstractjs viem

# bun
bun add @biconomy/smart-batching @biconomy/abstractjs viem
Composable batching works on any MEE version with native composability (v2.2.0 and above) — the basic primitives (eq, gte, lte, runtime values, output capture) are available across the line. MEE v2.2.2 adds the audited reference implementation plus the new constraint types shipped with that audit: gteSigned, lteSigned, or. Pass version: getMEEVersion(MEEVersion.V2_2_2) to @biconomy/abstractjs to opt in to the full feature set; older v2.2.x versions stay supported for accounts that haven’t migrated.

Quickstart — sweep an ERC-20 balance with a constraint

A common pattern: transfer the live USDC balance of an address to a recipient at execution time, but only if it’s at least some minimum. The amount is unknown at signing time — only the constraint is.
import { createComposableBatch } from "@biconomy/smart-batching";
import {
  createMeeClient,
  getDefaultMEENetworkApiKey,
  getDefaultMEENetworkUrl,
  getMEEVersion,
  MEEVersion,
  toMultichainNexusAccount,
} from "@biconomy/abstractjs";
import { createPublicClient, http, parseUnits } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { baseSepolia } from "viem/chains";

const USDC = "0x036CbD53842c5426634e7929541eC2318f3dCF7e"; // Base Sepolia USDC
const recipient = "0xRecipientAddress";

// 1. Smart account on MEE v2.2.2
const signer = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const account = await toMultichainNexusAccount({
  signer,
  chainConfigurations: [
    {
      chain: baseSepolia,
      transport: http(),
      version: getMEEVersion(MEEVersion.V2_2_2),
    },
  ],
});

const scaAddress = account.addressOn(baseSepolia.id, true);
const publicClient = createPublicClient({ chain: baseSepolia, transport: http() });

// 2. Build the ERC-8211 batch with @biconomy/smart-batching
const batch = createComposableBatch(publicClient, scaAddress);
const usdc = batch.erc20Token(USDC);
const minExpected = parseUnits("1", 6); // 1 USDC

batch.add([
  // Pre-condition: revert before doing anything if the SCA doesn't have at least 1 USDC.
  usdc.check({
    functionName: "balanceOf",
    args: [scaAddress],
    constraint: { gte: minExpected },
  }),

  // Action: transfer the SCA's live USDC balance to the recipient.
  // The amount is resolved on-chain at execution time — no need to know it now.
  usdc.write({
    functionName: "transfer",
    args: [recipient, usdc.runtimeBalance()],
  }),

  // Post-condition: assert the recipient actually received at least 1 USDC.
  usdc.check({
    functionName: "balanceOf",
    args: [recipient],
    constraint: { gte: minExpected },
  }),
]);

// 3. Execute through @biconomy/abstractjs
const isStaging = true; // staging MEE env for testnets
const meeClient = await createMeeClient({
  account,
  url: getDefaultMEENetworkUrl(isStaging),
  apiKey: getDefaultMEENetworkApiKey(isStaging),
});

const quote = await meeClient.getQuote({
  instructions: [
    {
      calls: await batch.toCalls(),
      chainId: baseSepolia.id,
      isComposable: true, // tells MEE to dispatch through the ERC-8211 module
    },
  ],
  feeToken: { address: USDC, chainId: baseSepolia.id },
});

const { hash } = await meeClient.executeQuote({ quote });
const receipt = await meeClient.waitForSupertransactionReceipt({ hash });
console.log("Done:", receipt.transactionHash);
What just happened:
  1. @biconomy/smart-batching built three ComposableCall entries: a pre-condition check, a write with a runtime-resolved balance, and a post-condition check.
  2. batch.toCalls() serialised them into the ABI shape the audited composability module decodes on-chain.
  3. @biconomy/abstractjs packaged the calls into a single MEE supertransaction (paying fee in USDC, optionally sponsored), and the MEE network simulated, signed, and executed it.
If the pre-condition fails, the transfer never happens. If the post-condition fails, the whole batch reverts atomically. The user pays no gas for a partial outcome.
Want to try it without writing code? Clone bcnmy/erc8211-quickstart and run bun install && bun run index.ts. It generates a fresh smart account, submits a sponsored ERC-8211 batch to Base Sepolia, and prints a MEEScan link — zero setup, no funding required. Tweak the constraint in index.ts to see the audited composability module actually gate execution.

Core primitives

Three building blocks cover most flows. Full reference and edge cases are in the SDK README.

Runtime values

A placeholder argument resolved on-chain at execution time. Three sources are supported:
const batch = createComposableBatch(publicClient, scaAddress);
const usdc = batch.erc20Token(USDC);
const eth = batch.nativeToken();
const oracle = batch.contract("0xOracleAddress", ORACLE_ABI);

// ERC-20 balance of any address at execution time
usdc.runtimeBalance();
usdc.runtimeBalance({ owner: "0xSomePoolAddress" });

// Native ETH balance
eth.runtimeBalance();

// Any view function — read a live oracle price, contract state, etc.
oracle.runtimeValue({ functionName: "latestPrice", args: [] });

// ERC-20 allowance — "consume exactly what was approved"
usdc.runtimeAllowance({ spender: "0xDexAddress" });
Pass any of these as an argument to a write call and the composability module fills in the actual value at execution time.

On-chain constraints

Attach a constraint to a runtime value or a check call. The module evaluates it before using the value, and reverts the whole batch if it fails.
ConstraintComparisonDescription
{ eq: value }unsignedResolved value must equal value exactly
{ gte: value }unsignedResolved value must be ≥ value
{ lte: value }unsignedResolved value must be ≤ value
{ gteSigned: value }signed (int256)Two’s-complement comparison; supports negative values
{ lteSigned: value }signed (int256)Two’s-complement comparison; supports negative values
{ or: [...] }Passes if any one child constraint passes
// Slippage guard: only proceed if the runtime balance is at least 5 USDC
usdc.runtimeBalance({ constraint: { gte: parseUnits("5", 6) } });

// Signed bound — for values that may be negative (e.g. a price delta from an oracle)
oracle.check({ functionName: "priceDelta", args: [], constraint: { gteSigned: -500n } });

// OR — pool must be either fully drained OR replenished above 100 USDC
usdc.check({
  functionName: "balanceOf",
  args: [poolAddress],
  constraint: { or: [{ eq: 0n }, { gte: parseUnits("100", 6) }] },
});

Output capture — chain one call’s output into the next

Capture the return value of a call into namespace storage, then read it as a runtime value later in the same batch.
const storage = batch.storage();
const storageKey = await storage.getStorageKey();

batch.add([
  // Call that produces a value — capture its return into storage automatically
  swapContract.write({
    functionName: "swapExactIn",
    args: [tokenIn, tokenOut, amountIn],
    capture: { type: "execResult", storageKey },
  }),

  // Use the captured value as the amount for the next call
  vault.write({
    functionName: "deposit",
    args: [await storage.runtimeValue({ storageKey })],
  }),
]);

Where to go next

Quickstart repo

Clone-and-run sponsored testnet example — no funding, no env vars, MEEScan link in seconds.

Examples gallery

12 real-world DeFi flows: leverage loops, MEV-protected swaps, cross-chain deposits, stop-loss/take-profit, output captures, and more.

SDK reference

Full @biconomy/smart-batching API documentation — every method, parameter, and edge case.

ERC-8211 standard

The standard’s project page, with the audited reference implementation and EIP discussion.

Supported chains

MEE v2.2.2 (the ERC-8211 release) deployment list — 16 mainnets and 13 testnets.