ERC-8211

Smart Batching — AI Agent 链上 DeFi 执行标准
整理自 Biconomy + Ethereum Foundation 规范 · 发布于 2026 年 4 月 6 日 · Fellowship 讨论 ↗ · 官方站点 ↗
静态批量(旧) 签名时固定所有参数 执行时状态已变 → 失败 ⚠ 滑点 / 余额不足 ERC-8211 Smart Batch(新) Fetchers 实时读取链上状态 Constraints 校验,不满足则整批 revert 原子执行,无需多次签名 单笔原子交易 ↓ 89% gas 消耗   ↓ 零额外签名
图1 — Smart Batching 将多步 DeFi 操作从"参数在签名时冻结"升级为"执行时实时解析"

ERC-8211 是由 BiconomyEthereum Foundation 联合提出的链上执行标准,发布于 2026 年 4 月 6 日。它不修改以太坊协议层,而是在合约编码层引入一种新的批处理原语:Smart Batching(智能批量)

它的核心洞见只有一句话:

— ERC-8211 核心命题

批量执行的参数,不应该在签名时就被冻结——它应该在链上执行时才知道自己的值。

这句话看似简单,却把多步 DeFi 流从"时间快照的拼接"变成了"实时协议的执行"。对于需要在链上自主操作的 AI Agent 来说,这是它们真正进入 DeFi 的通行证。

目录

静态批量的困境 ↑ top

"批处理不是新东西,ERC-4337 早就支持 UserOperation 批量提交。"这是大多数开发者在第一次听到 ERC-8211 时的反应。这个判断并没有错——但它描述的是一种不同的批量。

ERC-4337 的 UserOperation 批量是静态批量:所有参数——token 数量、滑点上限、目标合约地址——在用户签名时就固定下来。从签名到链上执行,这中间可能有数分钟的延迟,而 DeFi 的链上状态是实时变动的。

静态批量失败场景
操作签名时的参数执行时的实际状态
Uniswap Swap amountIn: 1000 USDC 滑点超出容忍范围 → revert,整批失败
Aave 存款 amount: 0.5 WETH 上一步 swap 实际输出 0.48 WETH → 余额不足 → 失败
跨链桥接 amount: fixed 目标链流动性已变化 → 金额超限 → 失败

对于手动操作的人类用户,失败了重发就好。但对于需要全天候自主运行的 AI Agent,静态批量的高失败率意味着:要么保守地串行执行每一步(多次签名、多次 gas),要么接受频繁失败带来的资金损耗。

Note

ERC-8211 的目标用户不仅是 AI Agent——任何需要在链上执行多步策略的场景(DeFi 套利、跨链操作、组合收益策略)都适用。但 Agent 是最迫切的受益者,因为它们不能依赖人类的实时干预。

ComposableExecution 架构 ↑ top

ERC-8211 的核心数据结构是 ComposableExecution[]——一个批次入口的数组。每个入口描述一次链上调用,但与静态批量不同,它的参数不是直接写死的值,而是一组参数声明:告诉执行引擎"在执行时去哪里取这个值,以及这个值必须满足什么条件"。

每个 ComposableExecution 由三个维度控制:

Fetchers

参数的值从哪来。在执行时实时读取,而非签名时写死。

Constraints

参数必须满足什么条件。不满足则整批 revert,保护执行安全。

Predicates

纯链上断言门控target = address(0),无调用,只做条件检查。

Fetchers — 参数来源

Fetcher 是 ERC-8211 最关键的设计。它让每一个参数都能在执行时从链上"现取",而不是从签名时的快照里读取。

Fetcher 类型
类型取值方式典型用途
Literal 内联字节,直接写死 目标合约地址、固定金额、已知常量
StaticCall 执行时调用指定合约的 view 函数,取返回值 读取预言机价格、链上余额、池子储量、swap 输出估算
Balance 执行时读取指定账户的 ERC-20 余额 上一步 swap 实际输出多少,传递给下一步存款

Balance Fetcher 是解决"下一步的输入 = 上一步的实际输出"这个经典 DeFi 问题的关键。AI Agent 不需要猜测 swap 之后会得到多少 token,执行引擎在那一刻直接读账户余额,传给下一步。

Tip

StaticCall Fetcher 可以调用任意 view 函数——包括 Chainlink 预言机的 latestAnswer()、Uniswap pool 的 slot0()、或者任意自定义的链上计算逻辑。这让 ERC-8211 批次可以在执行时做到"完全知情"。

Constraints — 执行条件

Constraint 是与 Fetcher 配套的校验器:当一个参数被 Fetcher 解析出来之后,Constraint 验证它是否在可接受范围内。如果不满足,整个批次立即 revert。

Constraint 操作符
操作符含义典型用途
None无约束,直接使用取到的值透传上一步输出给下一步
Gte≥ 指定值滑点保护:swap 输出必须 ≥ 最低可接受金额
Lte≤ 指定值价格上限:预言机价格不能超过某阈值
Eq= 指定值精确匹配:确认合约状态符合预期
Neq≠ 指定值防止某状态发生:如 pool 未被暂停

Constraint 与 Fetcher 的组合覆盖了 DeFi 中绝大多数的"前提条件"需求。过去需要在链下预估然后硬编码的参数,现在可以在链上执行时动态验证——更实时,也更安全。

Predicates — 纯断言门控

Predicate 是一种特殊的 ComposableExecution 入口:它的 target 字段被设为 address(0),表示这个步骤不发起任何合约调用,只做链上条件断言。

如果 Predicate 的断言失败,整个批次 revert。Predicate 通常被插入两个操作步骤之间,充当"阶段门":

1

操作:Swap WETH → USDC

Uniswap Router.exactInputSingle(),amountIn 由 Balance Fetcher 实时读取

P

Predicate:USDC 余额 ≥ 最低存款门槛(如 900 USDC)

target = address(0),StaticCall 读取 USDC.balanceOf(account),Gte constraint 校验

2

操作:Supply USDC to Aave

Aave Pool.supply(),amount 由 Balance Fetcher 读取上一步实际输出

设计哲学

Predicate 使用与普通操作相同的 Fetcher + Constraint 解析路径,没有特殊的运行时分支。address(0) 就是信号——执行引擎识别后跳过调用,只做断言。这让整个标准保持极简。

Solidity 接口参考 ↑ top

ERC-8211 定义在合约编码层,不修改任何 EVM 协议。接口由核心数据结构和一个执行函数组成:

enum FetcherType — 参数值的来源
enum FetcherType {
    Literal,      // 内联字节,签名时直接写死
    StaticCall,   // 执行时 staticcall view 函数,取返回值
    Balance       // 执行时读取 ERC-20 balanceOf(account)
}
取值方式典型用途
Literalinline bytes已知常量:合约地址、固定金额
StaticCallview call预言机价格、池子储量、swap 输出估算
BalancebalanceOf()上一步 swap 实际输出,传给下一步存款
enum ConstraintOp — 参数必须满足的条件
enum ConstraintOp {
    None,   // 无约束,直接透传
    Gte,    // >=  滑点保护:输出 ≥ 最低可接受金额
    Lte,    // <=  价格上限:不超过某阈值
    Eq,     // ==  精确匹配合约状态
    Neq     // !=  防止某状态出现(如 pool 被暂停)
}
struct ParamEntry — 单个参数声明
struct ParamEntry {
    FetcherType  fetcherType;
    address      fetchTarget;   // StaticCall / Balance 的目标地址
    bytes        fetchData;     // StaticCall 的 calldata;Balance 时忽略
    ConstraintOp constraintOp;
    uint256      constraintVal; // 约束右值
    bytes32      storeSlot;     // 可选:存入临时 slot,供后续步骤引用
}
字段类型说明
fetcherTypeFetcherType值从哪来(Literal / StaticCall / Balance)
fetchTargetaddressStaticCall 的合约地址,或 Balance 查询的 token 地址
fetchDatabytesStaticCall 的 calldata(含 selector + 参数)
constraintOpConstraintOp校验操作符;None 表示直接透传
constraintValuint256约束右值,如最低滑点金额
storeSlotbytes32可选,将解析值缓存供后续步骤读取;bytes32(0) 表示不存储
struct ComposableExecution — 单个批次入口
struct ComposableExecution {
    address      target;   // address(0) = Predicate(纯断言,不发起调用)
    bytes4       selector;
    ParamEntry[] params;
}
字段类型说明
targetaddressaddress(0) 表示 Predicate,只断言不调用;其他为目标合约
selectorbytes4函数选择器,Predicate 时忽略
paramsParamEntry[]参数声明列表,每个元素独立解析 + 校验
function IERC8211.execute — 执行入口
function execute(
    ComposableExecution[] calldata batch
) external returns (bytes[] memory results);
参数类型说明
batchComposableExecution[]按顺序执行的批次入口数组;任意步骤失败则整批 revert
returnsbytes[]每个 Call 入口的原始返回值;Predicate 入口对应空 bytes
谁需要实现这套接口?

ERC-8211 是 wire format 标准,定义编码格式,不是开箱即用的合约库。按账户类型分层:

上述接口为概念参考版,以规范草案为准。

执行流程 ↑ top

execute(batch) 被调用时,执行引擎按以下步骤处理每个 ComposableExecution 入口:

① 遍历 batch[] 取出当前入口 ② Predicate 检测 target == address(0)? yes 断言校验 失败 → revert(batch) no ③ 解析每个参数 Fetcher → 取值 ④ Constraint 校验 不满足 → revert(batch) ⑤ 组装并调用 call(target, calldata) 返回 results[] 下一个 入口
图2 — execute() 对 batch[] 中每个入口的处理逻辑;任意步骤失败则整批原子 revert

关键特性:原子性。整个 batch[] 要么全部成功,要么在任意失败点 revert,不存在部分执行的中间状态。这与传统多笔独立交易的最大区别——过去如果第 3 笔失败,前两笔已经上链,资金状态进入一个"半截"的中间状态。

实战:4 步 DeFi 流 ↑ top

以下是 ERC-8211 官方规范中给出的典型场景:将 WETH 通过 Uniswap 换成 USDC,校验余额,存入 Aave,最后质押凭证 token。整个流程打包进一笔原子交易。

4 步 DeFi 批次 — ComposableExecution[]
#类型操作Fetcher / Constraint
1 Call UniswapRouter.exactInputSingle(WETH → USDC)
amountIn: 账户当前 WETH 余额
Balance Fetcher 读取 WETH 余额;Gte constraint 保证最低输入量
P Predicate 断言:账户 USDC 余额 ≥ 最低存款门槛(如 900 USDC) Balance Fetcher + Gte constraint;target = address(0)
2 Call AavePool.supply(USDC, amount, account)
amount: 上一步实际换得的 USDC 余额
Balance Fetcher 读取 USDC 当前余额(包含 swap 实际输出)
3 Call AaveStaking.stake(aUSDC, amount)
amount: 账户持有的 aUSDC 凭证数量
Balance Fetcher 读取 aUSDC 余额;Gte constraint 防止 dust 操作
关键点

步骤 2 的 amount 是 Uniswap swap 实际输出,而非签名时预估的输出。Balance Fetcher 在步骤 2 执行前实时读取账户 USDC 余额,传入 Aave supply。这是静态批量永远无法做到的。

用静态批量完成同样的操作需要:4 笔独立交易 × 4 次用户签名,中间状态暴露在链上,任一步骤失败都需要手动恢复。ERC-8211 将这个流程压缩为 1 笔原子交易,gas 消耗降低约 89%。

TypeScript SDK ↑ top

ERC-8211 配套了一套 TypeScript SDK,开发者在高层描述意图,SDK 自动编译为 ComposableExecution[] 的链上编码。下面是与 4 步 DeFi 流对应的完整写法:

import 依赖引入 & 初始化
import { SmartBatch, FetcherType, ConstraintOp } from "@erc8211/sdk";
import { UNISWAP_ROUTER, AAVE_POOL, AAVE_STAKING, WETH, USDC, aUSDC } from "./addresses";

const batch = new SmartBatch();
1
addCall — Swap WETH → USDC
Balance Fetcher Gte
batch.addCall({
  target:        UNISWAP_ROUTER,
  selector:      "exactInputSingle",
  params: [{
    fetcherType:   FetcherType.Balance,   // 执行时读取账户实时 WETH 余额
    fetchTarget:   WETH,
    constraintOp:  ConstraintOp.Gte,
    constraintVal: 1n,                   // 余额必须 ≥ 1 wei,防空仓
  }],
});
P
addPredicate — 断言 USDC 余额 ≥ 900
Balance Fetcher Gte
batch.addPredicate({
  fetcherType:   FetcherType.Balance,
  fetchTarget:   USDC,
  constraintOp:  ConstraintOp.Gte,
  constraintVal: parseUnits("900", 6), // 不足 900 USDC → 整批 revert
});
2
addCall — Supply USDC to Aave
Balance Fetcher None — 透传
batch.addCall({
  target:   AAVE_POOL,
  selector: "supply",
  params: [{
    fetcherType:  FetcherType.Balance,  // 取 swap 后实际到账的 USDC 余额
    fetchTarget:  USDC,
    constraintOp: ConstraintOp.None,   // 无额外约束,全额存入
  }],
});
3
addCall — Stake aUSDC 凭证
Balance Fetcher Gte
batch.addCall({
  target:   AAVE_STAKING,
  selector: "stake",
  params: [{
    fetcherType:   FetcherType.Balance,
    fetchTarget:   aUSDC,             // Aave 存款后到账的 aUSDC 凭证
    constraintOp:  ConstraintOp.Gte,
    constraintVal: parseUnits("1", 6), // < 1 USDC 的 dust 不质押
  }],
});
encode() + execute() — 编译并单笔提交
const encoded = batch.encode();        // 编译为 ComposableExecution[] ABI 编码
await smartAccount.execute(encoded); // 单笔原子提交,1 签名
SDK 编译逻辑

batch.encode() 在本地将上述高层描述编译为 ComposableExecution[] ABI,编码中不包含任何当前链上状态——它只描述"执行时应该怎么取、怎么验"。这与静态批量的根本区别:静态批量编码的是当前值,Smart Batching 编码的是取值规则。

兼容标准 ↑ top

ERC-8211 被设计为账户无关的编码标准,兼容现有所有主流账户抽象框架:

ERC-4337 EIP-7702 ERC-7579 ERC-7683 ERC-8004 ERC-8183
兼容关系说明
标准关系说明
ERC-4337 运行时 ComposableExecution[] 可封装进 UserOperation.callData,通过 EntryPoint 提交
EIP-7702 运行时 委托账户可将 execute() 实现为 ERC-8211 接口,EOA 直接获得 Smart Batching 能力
ERC-7579 模块 模块化账户可将 ERC-8211 打包为 Execution 模块,按需挂载
ERC-7683 跨链 跨链 Intent 标准;ERC-8211 批次可在目标链等待桥接完成后继续执行,Predicate 做跨链状态门控
ERC-8004 身份层 StaticCall Fetcher 可调用 ERC-8004 ReputationRegistry,将 Agent 声誉分作为批次前提条件
ERC-8183 商业层 ERC-8183 的 Provider 在执行 Job 时,可将多步链上操作打包为 ERC-8211 批次,降低 gas 并保证原子交付

与 ERC-8004 的协作

ERC-8004 解决"Agent 可不可信",ERC-8211 解决"Agent 能不能执行"。两者分工明确,组合使用时,一个完整的 Agent DeFi 操作路径如下:

  1. 调用方通过 ERC-8004 ReputationRegistry 查询执行 Agent 的声誉分
  2. 声誉校验通过后,构造 ComposableExecution[] 批次
  3. 将声誉分 StaticCall 查询作为批次第一个 Predicate,链上实时确认身份信任
  4. 后续步骤原子执行 DeFi 操作
实际意义

这意味着一个智能合约可以实现"只有声誉分 ≥ 80 的 Agent 才能执行此策略"——不需要链下白名单,不需要中心化管理员,链上一次调用完成身份验证 + 策略执行。

与 ERC-8183 的协作

ERC-8183 定义了 Agent 之间的任务市场——Client 发布任务,Provider 执行并获得报酬。ERC-8211 让 Provider 在链上交付时更高效:

安全考量 ↑ top

安全风险与缓解措施
风险场景缓解
重放攻击 相同的 ComposableExecution[] 在不同区块被重复提交 依赖账户层的 nonce 机制(ERC-4337 / EIP-7702 均内置 nonce)
预言机操控 StaticCall Fetcher 引用的预言机被操控,导致 Constraint 被绕过 使用多来源聚合预言机(Chainlink、Pyth);对高价值操作增加 TWAP 保护
Constraint 设置过宽 Gte(0) 等同于无约束,失去滑点保护 SDK 对零值约束抛出警告;审计时重点检查约束值
Fetcher gas 耗尽 StaticCall Fetcher 指向恶意合约,消耗大量 gas 在 execute() 实现中对每个 Fetcher 设置 gas 上限(推荐 50,000 gas)
storeSlot 污染 多个步骤写入相同 storeSlot 导致意外覆盖 SDK 编译时做 storeSlot 唯一性检查;避免手动指定相同 slot
重要提醒

ERC-8211 批次的安全边界由 Constraint 设置决定。过于宽松的约束(如 Gte(1))在技术上合法,但等同于放弃了风险保护。在高价值 DeFi 策略中,每个参数的 Constraint 都应该经过独立审计。

总结速查 ↑ top

维度内容
提案编号ERC-8211
提案方Biconomy + Ethereum Foundation
发布时间2026 年 4 月 6 日
核心概念Smart Batching — 参数延迟到执行时解析
核心数据结构ComposableExecution[]
三大机制Fetchers(取值)/ Constraints(校验)/ Predicates(门控)
Fetcher 类型Literal / StaticCall / Balance
Constraint 操作符None / Gte / Lte / Eq / Neq
Predicate 标识target = address(0)
原子性是——任意步骤失败则整批 revert
协议层修改无——合约编码层标准,不需要 EVM 分叉
账户兼容性ERC-4337 / EIP-7702 / ERC-7579,账户无关
Gas 优化相比串行执行降低约 89%
开发工具TypeScript SDK,高层 DSL 编译为 ComposableExecution[]

测验 ↑ top

5 道题,测试你对 ERC-8211 的理解:

1. ERC-8211 解决的核心问题是什么?

2. 你要实现"下一步存款金额 = 上一步 swap 的实际输出",应该使用哪种 Fetcher?

3. 一个 ComposableExecution 入口的 target 字段被设为 address(0),这意味着?

4. 在一个 4 步批次中,第 2 步的 Constraint 校验失败。结果是?

5. ERC-8211 与 ERC-4337 的关系是?

0 / 5