> ## 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 Smart Batching

> Build composable transaction batches with on-chain constraints and runtime-resolved values

[ERC-8211](https://erc8211.com/) 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:

<CardGroup cols={2}>
  <Card title="@biconomy/smart-batching" icon="cubes" href="https://www.npmjs.com/package/@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.
  </Card>

  <Card title="@biconomy/abstractjs" icon="rocket" href="/overview/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.
  </Card>
</CardGroup>

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 constraints** — `eq`, `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](https://github.com/bcnmy/erc8211-contracts/blob/main/audits/2026-05-Composability-Nexus-Pashov-Review.pdf)) and deployed deterministically at `0x0000821108B5C9F3fe17E40811bE5b66DaF8f0e7` (module) and `0x00008211dea1Aca67ac55fc44AE3bF88CF41281d` (storage) on every supported chain.

***

## Install

```bash theme={null}
# 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.

```typescript theme={null}
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.

<Tip>
  **Want to try it without writing code?** Clone [`bcnmy/erc8211-quickstart`](https://github.com/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`](https://github.com/bcnmy/erc8211-quickstart/blob/main/index.ts) to see the audited composability module actually gate execution.
</Tip>

***

## Core primitives

Three building blocks cover most flows. Full reference and edge cases are in the [SDK README](https://github.com/bcnmy/smart-batching-sdk#readme).

### Runtime values

A placeholder argument resolved on-chain at execution time. Three sources are supported:

```typescript theme={null}
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.

| Constraint             | Comparison        | Description                                           |
| ---------------------- | ----------------- | ----------------------------------------------------- |
| `{ eq: value }`        | unsigned          | Resolved value must equal `value` exactly             |
| `{ gte: value }`       | unsigned          | Resolved value must be ≥ `value`                      |
| `{ lte: value }`       | unsigned          | Resolved 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         |

```typescript theme={null}
// 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.

```typescript theme={null}
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

<CardGroup cols={2}>
  <Card title="Quickstart repo" icon="rocket" href="https://github.com/bcnmy/erc8211-quickstart">
    Clone-and-run sponsored testnet example — no funding, no env vars, MEEScan link in seconds.
  </Card>

  <Card title="Examples gallery" icon="code" href="https://github.com/bcnmy/smart-batching-sdk/tree/main/examples">
    12 real-world DeFi flows: leverage loops, MEV-protected swaps, cross-chain deposits, stop-loss/take-profit, output captures, and more.
  </Card>

  <Card title="SDK reference" icon="book" href="https://github.com/bcnmy/smart-batching-sdk#readme">
    Full `@biconomy/smart-batching` API documentation — every method, parameter, and edge case.
  </Card>

  <Card title="ERC-8211 standard" icon="file-lines" href="https://erc8211.com/">
    The standard's project page, with the audited reference implementation and EIP discussion.
  </Card>

  <Card title="Supported chains" icon="globe" href="/contracts-and-audits/supported-chains">
    MEE v2.2.2 (the ERC-8211 release) deployment list — 16 mainnets and 13 testnets.
  </Card>
</CardGroup>
