ERC-8211
ERC-8211 是由 Biconomy 与 Ethereum Foundation 联合提出的链上执行标准,发布于 2026 年 4 月 6 日。它不修改以太坊协议层,而是在合约编码层引入一种新的批处理原语:Smart Batching(智能批量)。
它的核心洞见只有一句话:
批量执行的参数,不应该在签名时就被冻结——它应该在链上执行时才知道自己的值。
这句话看似简单,却把多步 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),要么接受频繁失败带来的资金损耗。
ERC-8211 的目标用户不仅是 AI Agent——任何需要在链上执行多步策略的场景(DeFi 套利、跨链操作、组合收益策略)都适用。但 Agent 是最迫切的受益者,因为它们不能依赖人类的实时干预。
ComposableExecution 架构 ↑ top
ERC-8211 的核心数据结构是 ComposableExecution[]——一个批次入口的数组。每个入口描述一次链上调用,但与静态批量不同,它的参数不是直接写死的值,而是一组参数声明:告诉执行引擎"在执行时去哪里取这个值,以及这个值必须满足什么条件"。
每个 ComposableExecution 由三个维度控制:
参数的值从哪来。在执行时实时读取,而非签名时写死。
参数必须满足什么条件。不满足则整批 revert,保护执行安全。
纯链上断言门控。target = address(0),无调用,只做条件检查。
Fetchers — 参数来源
Fetcher 是 ERC-8211 最关键的设计。它让每一个参数都能在执行时从链上"现取",而不是从签名时的快照里读取。
| Fetcher 类型 | ||
|---|---|---|
| 类型 | 取值方式 | 典型用途 |
Literal |
内联字节,直接写死 | 目标合约地址、固定金额、已知常量 |
StaticCall |
执行时调用指定合约的 view 函数,取返回值 | 读取预言机价格、链上余额、池子储量、swap 输出估算 |
Balance |
执行时读取指定账户的 ERC-20 余额 | 上一步 swap 实际输出多少,传递给下一步存款 |
Balance Fetcher 是解决"下一步的输入 = 上一步的实际输出"这个经典 DeFi 问题的关键。AI Agent 不需要猜测 swap 之后会得到多少 token,执行引擎在那一刻直接读账户余额,传给下一步。
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 通常被插入两个操作步骤之间,充当"阶段门":
操作:Swap WETH → USDC
Uniswap Router.exactInputSingle(),amountIn 由 Balance Fetcher 实时读取
Predicate:USDC 余额 ≥ 最低存款门槛(如 900 USDC)
target = address(0),StaticCall 读取 USDC.balanceOf(account),Gte constraint 校验
操作:Supply USDC to Aave
Aave Pool.supply(),amount 由 Balance Fetcher 读取上一步实际输出
Predicate 使用与普通操作相同的 Fetcher + Constraint 解析路径,没有特殊的运行时分支。address(0) 就是信号——执行引擎识别后跳过调用,只做断言。这让整个标准保持极简。
Solidity 接口参考 ↑ top
ERC-8211 定义在合约编码层,不修改任何 EVM 协议。接口由核心数据结构和一个执行函数组成:
enum FetcherType { Literal, // 内联字节,签名时直接写死 StaticCall, // 执行时 staticcall view 函数,取返回值 Balance // 执行时读取 ERC-20 balanceOf(account) }
| 值 | 取值方式 | 典型用途 |
|---|---|---|
| Literal | inline bytes | 已知常量:合约地址、固定金额 |
| StaticCall | view call | 预言机价格、池子储量、swap 输出估算 |
| Balance | balanceOf() | 上一步 swap 实际输出,传给下一步存款 |
enum ConstraintOp { None, // 无约束,直接透传 Gte, // >= 滑点保护:输出 ≥ 最低可接受金额 Lte, // <= 价格上限:不超过某阈值 Eq, // == 精确匹配合约状态 Neq // != 防止某状态出现(如 pool 被暂停) }
struct ParamEntry { FetcherType fetcherType; address fetchTarget; // StaticCall / Balance 的目标地址 bytes fetchData; // StaticCall 的 calldata;Balance 时忽略 ConstraintOp constraintOp; uint256 constraintVal; // 约束右值 bytes32 storeSlot; // 可选:存入临时 slot,供后续步骤引用 }
| 字段 | 类型 | 说明 |
|---|---|---|
| fetcherType | FetcherType | 值从哪来(Literal / StaticCall / Balance) |
| fetchTarget | address | StaticCall 的合约地址,或 Balance 查询的 token 地址 |
| fetchData | bytes | StaticCall 的 calldata(含 selector + 参数) |
| constraintOp | ConstraintOp | 校验操作符;None 表示直接透传 |
| constraintVal | uint256 | 约束右值,如最低滑点金额 |
| storeSlot | bytes32 | 可选,将解析值缓存供后续步骤读取;bytes32(0) 表示不存储 |
struct ComposableExecution { address target; // address(0) = Predicate(纯断言,不发起调用) bytes4 selector; ParamEntry[] params; }
| 字段 | 类型 | 说明 |
|---|---|---|
| target | address | address(0) 表示 Predicate,只断言不调用;其他为目标合约 |
| selector | bytes4 | 函数选择器,Predicate 时忽略 |
| params | ParamEntry[] | 参数声明列表,每个元素独立解析 + 校验 |
function execute( ComposableExecution[] calldata batch ) external returns (bytes[] memory results);
| 参数 | 类型 | 说明 |
|---|---|---|
| batch | ComposableExecution[] | 按顺序执行的批次入口数组;任意步骤失败则整批 revert |
| returns | bytes[] | 每个 Call 入口的原始返回值;Predicate 入口对应空 bytes |
ERC-8211 是 wire format 标准,定义编码格式,不是开箱即用的合约库。按账户类型分层:
- 普通 EOA — 不能直接用。EOA 没有代码,无法运行
execute()内部的 Fetcher 解析与 Constraint 校验逻辑。 - EOA + EIP-7702 — 可以用。EOA 签一个 authorization 临时委托给 ERC-8211 executor 合约,该次交易期间 EOA 地址就具备了
execute()能力。 - 原生支持的智能账户(如 Biconomy Nexus)— 直接调用,无需额外工作。
- 已有自己执行接口的智能账户(Safe、Kernel 等)— 写一层薄适配器模块:接收
ComposableExecution[],调用共享解析引擎,再翻译成该账户原生的调用格式(如 Safe 的execTransaction())。核心解析逻辑来自 ERC-8211 共享库,适配器只负责"接入"。
上述接口为概念参考版,以规范草案为准。
执行流程 ↑ top
当 execute(batch) 被调用时,执行引擎按以下步骤处理每个 ComposableExecution 入口:
关键特性:原子性。整个 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 { SmartBatch, FetcherType, ConstraintOp } from "@erc8211/sdk"; import { UNISWAP_ROUTER, AAVE_POOL, AAVE_STAKING, WETH, USDC, aUSDC } from "./addresses"; const batch = new SmartBatch();
batch.addCall({ target: UNISWAP_ROUTER, selector: "exactInputSingle", params: [{ fetcherType: FetcherType.Balance, // 执行时读取账户实时 WETH 余额 fetchTarget: WETH, constraintOp: ConstraintOp.Gte, constraintVal: 1n, // 余额必须 ≥ 1 wei,防空仓 }], });
batch.addPredicate({ fetcherType: FetcherType.Balance, fetchTarget: USDC, constraintOp: ConstraintOp.Gte, constraintVal: parseUnits("900", 6), // 不足 900 USDC → 整批 revert });
batch.addCall({ target: AAVE_POOL, selector: "supply", params: [{ fetcherType: FetcherType.Balance, // 取 swap 后实际到账的 USDC 余额 fetchTarget: USDC, constraintOp: ConstraintOp.None, // 无额外约束,全额存入 }], });
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 不质押 }], });
const encoded = batch.encode(); // 编译为 ComposableExecution[] ABI 编码 await smartAccount.execute(encoded); // 单笔原子提交,1 签名
batch.encode() 在本地将上述高层描述编译为 ComposableExecution[] ABI,编码中不包含任何当前链上状态——它只描述"执行时应该怎么取、怎么验"。这与静态批量的根本区别:静态批量编码的是当前值,Smart Batching 编码的是取值规则。
兼容标准 ↑ top
ERC-8211 被设计为账户无关的编码标准,兼容现有所有主流账户抽象框架:
| 兼容关系说明 | ||
|---|---|---|
| 标准 | 关系 | 说明 |
| 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 操作路径如下:
- 调用方通过 ERC-8004 ReputationRegistry 查询执行 Agent 的声誉分
- 声誉校验通过后,构造
ComposableExecution[]批次 - 将声誉分 StaticCall 查询作为批次第一个 Predicate,链上实时确认身份信任
- 后续步骤原子执行 DeFi 操作
这意味着一个智能合约可以实现"只有声誉分 ≥ 80 的 Agent 才能执行此策略"——不需要链下白名单,不需要中心化管理员,链上一次调用完成身份验证 + 策略执行。
与 ERC-8183 的协作
ERC-8183 定义了 Agent 之间的任务市场——Client 发布任务,Provider 执行并获得报酬。ERC-8211 让 Provider 在链上交付时更高效:
- Provider 将任务所需的多步链上操作打包为 ERC-8211 批次
- 批次成功执行 = 交付完成,Evaluator 通过
deliverable_hash验证 - 相比串行执行,单笔原子交易降低失败概率,减少争议产生
安全考量 ↑ 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 的关系是?