ERC-8211 Standards Track Draft

ERC-8211: Smart Batching

From transactions to programs.

A batch encoding where each parameter declares how to obtain its value at execution time and what conditions that value must satisfy. No Solidity required. No contract deployment. No audit cycles for new flows.

Biconomy × Ethereum Foundation

See it in action

Interactive demo of Smart Batching — runtime-resolved parameters and predicate-gated execution, live on-chain.

The problem with static batching

An Ethereum transaction is a single function call. ERC-4337 and EIP-5792 extended this with batch execution — multiple calls under one signature — but every parameter is static: frozen at signing time, blind to on-chain state at execution.

Real-world DeFi flows produce dynamic, unpredictable outputs. Static batching forces two bad choices: hardcode optimistic amounts (risking reverts) or underestimate conservatively (leaving value stranded).

SWAP → SUPPLY
1
swap(100 USDC → WETH)
Returns 0.0495 WETH
OK
2
supply(0.05 WETH)
Hardcoded 0.05 but only 0.0495 available
REVERT
Swap output depends on price impact, slippage, and MEV. The hardcoded amount is a guess.
VAULT WITHDRAW → BRIDGE
1
withdraw(500 shares)
Returns 487.3 USDC (share rate shifted)
OK
2
bridge(500 USDC)
Hardcoded 500 but only 487.3 received
REVERT
Lending vault share-to-asset conversion is variable. The withdrawal amount can’t be predicted.
BRIDGE → DEFI
1
bridge(100 USDC → Optimism)
Delivers 99.7 USDC after variable fees
OK
2
deposit(100 USDC)
Hardcoded 100 but bridge took variable fees
REVERT
Bridge delivery has unpredictable delay and variable fees. No way to know the exact arrival amount.
REBALANCE FLOW
1
repay(debt on Aave)
Interest accrued since signing — OK
OK
2
borrow(exact collateral ratio)
Oracle price moved — ratio stale, position unsafe
REVERT
Liquidation thresholds and oracle prices change block-to-block. Hardcoded ratios are immediately stale.
SEND FULL BALANCE
1
transfer(1.0 ETH)
Hardcoded 1.0 but gas costs unknown at signing
OK
account balance
0.000031 ETH dust remains — stranded forever
DUST
Gas cost is unpredictable. You can’t hardcode "send everything" — dust always remains.
SLIPPAGE PROTECTION
1
swap(1 ETH → USDC)
Sandwiched — returns only $1,800 instead of $2,500
OK
2
supply($1,800 USDC)
No way to assert "output ≥ min" — bad trade executes silently
NO GUARD
Static batches can’t assert conditions on intermediate outputs. MEV extracts value with no protection.
Frozen at signing

Parameters are locked when you sign. If on-chain state changes before execution, too bad.

Blind to state

No awareness of balances, oracle prices, or any live on-chain data at execution time.

Atomic revert

One stale parameter in a multi-step flow causes the entire batch to revert. All or nothing.

What Smart Batching unlocks

Three primitives that turn a static batch into a programmable execution plan.

01

Runtime parameter injection

Each parameter declares a fetcher that resolves its value on-chain at execution — reading balances, oracles, or any contract state.

Instead of
supply(0.05 WETH)
You write
supply(BALANCE(WETH))
02

Pre-and-post assertions

Every resolved value runs through inline constraints. Entries with target = address(0) act as pure predicate checks that revert the batch if conditions fail.

Constraints
amount GTE min
price LTE maxPrice
token EQ expected
03

Multi-transaction context

A shared Storage contract lets entries write output values that later entries — or even the next UserOperation — can read without custom contracts.

Data flow
swap() Storage supply()
Persists across entries and transactions.

Runtime parameter injection

Swap USDC → USDT on Uniswap, then supply to Aave. The swap output is variable — smart batching resolves the exact amount at execution time.

1
SwapRouter.exactInputSingle(USDC, USDT, 500, 1000e6, 0)
2
AavePool.supply(USDT, runtime injected
Resolved at execution time
Fetcher type
BALANCE
Resolves via
IERC20(USDT).balanceOf(self)
Returns
997_420_000(997.42 USDT)
Constraint
GTE(1)✓ pass
, self, 0)
Hover an interactive parameter to see how it resolves

How it works

Every parameter in a smart batch declares two orthogonal concerns: how the value is obtained (fetcher type) and where it goes (parameter routing). After resolution, inline constraints validate the value before it’s routed.

Fetcher types — how values are obtained

Each parameter specifies its resolution strategy independently.

RAW_BYTES Literal value, known at encoding
STATIC_CALL Arbitrary on-chain state read
BALANCE ERC-20 or native balance query

Parameter routing — where values go

Each resolved value is directed to one of three destinations.

TARGET Call address
VALUE ETH to forward
CALL_DATA Appended to calldata

Inline constraints — validate before routing

Each resolved value is checked against predicates. If any fails, the batch reverts.

EQ GTE LTE IN Exact match, bounds, range

Execution pipeline

For each entry in the composable batch:
  1. 1
    Resolve inputs
    Each InputParam fetcher fires: RAW_BYTES, STATIC_CALL, or BALANCE
  2. 2
    Validate constraints
    Inline predicates check each resolved value — any failure reverts the batch
  3. 3
    Route & assemble calldata
    Values direct to TARGET, VALUE, or CALL_DATA; calldata built from selector + parameters
  4. 4
    Execute call
    target.call{value} (calldata) — skipped if target is address(0) (predicate entry)
  5. 5
    Capture outputs
    Return data optionally written to Storage contract for use by later entries

The pipeline is normative — all conforming implementations must follow this exact sequence. See the full specification for details.

Cross-chain orchestration

Rebalance a lending position from Morpho on Base to Aave on Ethereum — eight steps across two chains, signed once. The Ethereum batch is predicate-gated: a relayer simulates via eth_call and submits only when the bridged WETH has arrived.

User signs ONE Merkle root → covers both chains
bridge
Key property: Constraints observe state, not mechanism. The bridge could be any provider — native bridge, Across, ERC-7683, LayerZero — the Ethereum batch just waits for the WETH balance to appear. The predicate model is credibly neutral with respect to the interoperability layer.

From transactions to programs

Developers author multi-step, multi-chain programs in TypeScript. The SDK compiles to ComposableExecution[] entries with fetchers, constraints, and storage instructions. The user signs once. Relayers execute on-chain.

batch.ts
const batch = smartBatch([
  swap({
    from: WETH,
    to: USDC,
    amount: fullBalance()
  }),

  predicate({
    balance: gte(USDC, account, 2500e6)
  }),

  supply({
    protocol: "aave",
    token: USDC,
    amount: fullBalance()
  }),

  stake({
    token: aUSDC,
    amount: fullBalance()
  }),
]);

This is a complete four-step DeFi flow: swap WETH for USDC, verify the balance meets a minimum, supply to Aave, stake the receipt tokens. Every amount resolves dynamically via fullBalance() — no hardcoded values, no leftover dust.

No Solidity required

Write flows in TypeScript. The SDK compiles to standard on-chain encoding.

No contract deployment

No new attack surface. No audit cycles when flows change.

Sign once, execute anywhere

One signature over a Merkle root. Relayers handle submission.

Universal compatibility

The standard is defined at the encoding and interface level — not as a specific module. The same ComposableExecution[] encoding works with every smart account architecture. One wire format, one interface, thin adapters.

ERC-7579 Executor Module

Wraps IComposableExecution as a standard executor module. Installs through the ERC-7579 module lifecycle.

ERC-6900 Plugin

Registers executeComposable as an execution function via the ERC-6900 manifest.

Native Direct Inheritance

Smart accounts implement IComposableExecution directly. No module wrapper, no cross-contract overhead.

ERC-7702 Delegation Target

EOAs delegate to an implementation contract. Composable execution without a smart account deployment.

Also compatible with
ERC-4337 Smart Accounts
EIP-5792 wallet_sendCalls
EIP-8141 Frame Transactions
ERC-7683 / 7786 Interop layers

No existing smart account requires migration. The encoding format is self-contained and the interface is a single function — adapters for any account standard are minimal.