Agent 经济的讨论热闹了大半年,我身边不少人都在做链上自动交易、治理投票助手、自动领取空投这类脚本。但聊到最后几乎都会卡在同一个地方:一个 AI Agent 到底凭什么、以什么名义、花谁的钱去执行链上操作?ERC-8183 这个提案就是冲着这个问题来的,圈子里称它是“给 Agent 经济补上最后一块拼图”。它解决的不是让 Agent 变聪明的问题,而是让 Agent 在链上有权限、有预算、有刹车机制地替人干活。这篇文章我会按自己的理解拆解这个提案的设计逻辑,并附上一套可以在本地跑起来的最小实现,适合正在做 Agent 基建、DeFi 自动化工具,或者想把自己业务自动化起来的开发者参考。
1. Agent 经济的缺口到底在哪
如果你做过链上自动化,一定熟悉这么一套老办法:用户给合约授权一个额度,然后你的脚本调用transferFrom把币转走。这套逻辑服务的是“单笔资产转移”,但 Agent 经济里需要的根本不是一次性转账,而是“替我连续完成一项任务”。
举个例子。我想让一个 Agent 帮我把钱包里的资金分散到五个策略池,期间要往返多次:先从金库取币、兑换成对应资产、再分别转入五个池子、最后更新我的仓位记录。用传统授权模型,我得为每一步单独授权,要么就得把手里的私钥整个交给 Agent。前者烦琐到不现实,后者等于把家门钥匙直接塞给陌生人,安全上完全站不住。
更深层的问题在于代理权的定义缺失。链上合约有明确的权限边界,但“一个 Agent 在什么条件下、花多少钱、做哪些操作、多久之后失效”这件事,没有任何标准可循。每个团队都在自己造轮子,有的用链下服务器硬攒签名,有的把用户私钥托管在云端,有的干脆让用户授权一个超大的approve然后自求多福。这些方案在 demo 里都能跑,一旦放进真实资金环境,审计第一关就过不去。
ERC-8183 想做的,就是把“用户授权 Agent 代替自己执行链上操作”这整件事标准化:明确 Agent 能调哪些方法、最多花费多少、什么时候过期、用户如何事中撤销,以及每一笔调用如何被验证和记账。你可以把它理解成一个介于“普通授权”和“完全托管”之间的可控代理层。没有这层基础设施,Agent 经济就始终停留在“替人看行情的玩具”层面,落不到真实资产管理上。
2. ERC-8183 的核心设计思路
2.1 从“资产转移授权”升级为“操作授权”
传统授权模型只关心“谁可以动我的钱”,ERC-8183 把问题升级成了“谁可以以我的身份做哪些操作”。这个区别非常关键。
approve授权的是一个金额上限,而 ERC-8183 授权的是一个执行边界。边界里既有资金上限,也有方法白名单。Agent 不能拿着授权去调用任意合约的任意函数,它只能调用用户预先允许的合约和方法。比如我允许 Agent 调用某个聚合器的swap函数,那么它就不能顺手调用同一个合约里其他的管理函数。粒度上做到了“函数级”,比“额度级”精确一个量级。
在实现上,这需要一个可验证的授权凭证。ERC-8183 的做法是让用户创建一条授权记录(grant),记录里包含了被授权的 Agent 地址、预算上限、允许调用的目标合约、允许调用的函数选择器、有效期和单次调用上限。所有调用都必须携带这条记录的编号,合约在收到调用请求时先校验 Agent 身份和记录是否匹配,再校验预算是否充足。整个流程在链上完成验证,用户不需要把私钥给 Agent。
2.2 会话凭证与上下文:Agent 如何证明“我被授权了”
有了授权记录,Agent 在调用时必须证明“我就是那个被授权的 Agent”。这一步依赖链下签名。
用户创建授权记录后,Agent 手里会拿到一个 grant ID,同时自己保留一把独立的签名私钥。每次执行任务时,Agent 构造一条调用消息(包含目标地址、payload、预算消耗、nonce 和过期时间),用这把私钥签名后提交到链上合约。合约校验签名与授权记录中的 Agent 地址一致后,才继续执行。
这里有个细节值得多说一句:签名私钥和资产私钥是分离的。用户的资产私钥永远留在自己手里,Agent 只有一把功能受限的“操作钥匙”。哪怕这把钥匙泄露,攻击者也只能在授权边界内操作,而且用户随时可以撤销。这比托管私钥的方案安全一个级别。
nonce 和过期时间的作用是防重放。每一条调用消息都有唯一的 nonce,已经被执行过的 nonce 不能再次使用。过期时间则限制签名有效期,避免一条老签名在几个月后被重新提交。这两点在实际使用中极其重要,后面讲踩坑的时候我会专门展开。
2.3 可撤销、可续期、可审计的标准三件套
任何代理权设计,如果缺了“撤销”,就不能算完整。ERC-8183 把撤销做成了标准动作:用户随时可以调用接口终止一条授权记录,撤销后 Agent 的所有待执行签名立刻失效。设计上还把撤销设计成 calldata 级,也就是说不需要额外链下同步,Agent 下一笔调用会直接被合约拒绝。
可续期是另一个刚需。很多任务不是一次性完成,而是长期运行的,比如治理代理人每周为用户投票,或者定投策略按月执行。ERC-8183 允许用户在授权过期前续期,并不需要重新走一遍完整授权流程。续期时可以同时调整预算上限和有效期,相当于把授权记录升级了。
可审计则体现在事件日志上。每一次 Agent 调用都会发出事件,包含 grant ID、目标合约、消耗金额、调用结果。用户随时可以在区块浏览器里翻出自己名下所有 Agent 的行为记录,每一笔钱花在哪里、调了什么方法、结果如何,一目了然。链上行为天然透明,但前提是标准先定义好日志格式,否则每个协议打出来的日志都不一样,查起来非常痛苦。
3. 一个最小实现:接口、数据结构和核心逻辑
纸上谈兵没有意义,我按自己的理解写了一个最小可用版本。代码量不大,核心逻辑集中在授权创建、调用校验和预算扣减三个动作。
3.1 接口约定
我先把接口画出来。这不是标准原文,而是我对 ERC-8183 的个人复刻,用来跑通流程。
interface IERC8183 { struct Grant { address agent; // 被授权的 Agent 地址 address owner; // 授权人 uint96 budget; // 总预算,单位 wei uint96 spent; // 已花费 uint48 expiresAt; // 过期时间戳 uint16 callLimit; // 最大调用次数 uint16 callCount; // 已调用次数 address target; // 唯一允许调用的目标合约 bytes4[] selectors; // 允许调用的函数选择器列表 bool active; // 是否有效 } event GrantCreated(uint256 indexed grantId, address indexed owner, address indexed agent); event AgentCall(uint256 indexed grantId, address target, bytes4 selector, uint256 cost); event GrantRevoked(uint256 indexed grantId); function createGrant( address agent, uint96 budget, uint48 duration, uint16 callLimit, address target, bytes4[] calldata selectors ) external returns (uint256 grantId); function execute( uint256 grantId, bytes calldata payload, uint96 cost, bytes calldata signature ) external returns (bytes memory result); function revoke(uint256 grantId) external; }这个接口把授权记录设计成“单目标合约”模式:一条 grant 只能对应一个目标合约。这样在审计时思路特别清晰,用户也容易判断“我到底把这个 Agent 放进了哪一间房间”。
3.2 授权创建流程
用户调用createGrant,传入 Agent 地址、预算、时长、调用次数上限、目标合约和允许的 selector 列表。合约生成一个新的 grant ID,把信息存进grants映射,然后发出GrantCreated事件。
预算在这里以原生 ETH 为计量单位。实际使用中如果要扩展到 ERC-20,需要额外传 token 地址,我会在第四节展开这个问题。单次调用上限通过费用参数控制,也就是转账金额。整个授权创建不要求用户进行任何转账,用户只是“划出了一个许可范围”,真正的资金是在 Agent 执行调用时才流动。
3.3 调用执行与校验逻辑
执行函数是整套设计的核心,校验顺序我按审计逻辑排好了:先查授权是否被撤销、是否过期,再验签名、验方法、验额度,最后扣钱执行。每一步都能独立失败,方便定位问题。
function execute( uint256 grantId, bytes calldata payload, uint96 cost, bytes calldata signature ) external returns (bytes memory result) { Grant storage grant = grants[grantId]; require(grant.active, "grant not active"); require(block.timestamp < grant.expiresAt, "grant expired"); require(msg.sender == grant.agent, "not authorized agent"); bytes32 digest = keccak256(abi.encodePacked( "\x19Ethereum Signed Message:\n32", keccak256(abi.encode(grantId, payload, cost, nonces[grantId])) )); address signer = recover(digest, signature); require(signer == grant.owner, "bad signature"); bytes4 selector = bytes4(payload); require(isAllowed(grant, selector), "selector not allowed"); require(grant.spent + cost <= grant.budget, "budget exceeded"); require(grant.callCount < grant.callLimit, "call limit reached"); nonces[grantId] += 1; bool success; (success, result) = grant.target.call{value: cost}(payload); require(success, "target call failed"); grant.spent += cost; grant.callCount += 1; emit AgentCall(grantId, grant.target, selector, cost); }注意这里我把签名方设计成了 owner,也就是用户必须在链下为 Agent 的每次调用预先签名。这有一个好处:用户可以在签名前检查 payload 内容;但也有一个坏处:Agent 没法自主执行。实际生产里更灵活的方案是让 Agent 用自己的私钥签名,用户只签授权创建和 grant 配置,二者可以混用。我在简化实现里选了前者,逻辑线更短。
另一个细节:selector = bytes4(payload)的写法要求 payload 是标准的 4 字节选择器加参数编码。如果你的 Agent 调用带复杂参数,拆分参数做边界校验会复杂很多。为了保持最小实现可读性,这里只在函数级做了白名单校验,参数级校验可以在每个具体业务的 manager 合约里去做。
3.4 撤销与事件
revoke函数逻辑很简单:只有授权人自己能把 grant 置为不可用,同时发出事件。撤销之后,Agent 如果再尝试执行,第一步的active校验就把请求拦住了,整个调用天然失效,不需要额外向 Agent 下发“你已经被解除授权”的通知。这是链上状态机最省心的一个特性:状态变更即时生效,所有参与者共享同一份状态。
4. Agent 侧接入的真实工作
4.1 签名机制与上下文封装
我在实际写 Agent 集成的时候,发现最容易出乱子的是签名上下文。同一个 grant 下会有很多笔调用,每笔调用如果只有 payload 和签名,是防不了重放的:攻击者把旧调用重新提交一遍又一遍,预算会被偷花光。
所以签名消息里必须包含四个要素:grantId、payload、费用、nonce。其中 nonce 可以由合约维护,也可以由 Agent 维护,但建议由合约维护。合约在每次执行后自增 nonce,这样即使 Agent 那边状态不同步也不会出现重复签名被二次使用的情况。链上是唯一事实来源,不要依赖 Agent 的本地状态做安全判断,这是我踩过坑之后才有的体会。
另外,如果你的合约在多个链上部署,签名消息里还要塞chainId和合约地址。跨链重放攻击是真实存在的:Agent 在一条链上生成的有效签名,被攻击者在另一条链上原封不动地重放了一遍,照样能把预算花掉。加上链标识是最便宜、最有效的防御。
4.2 计价与限额:怎么定预算才有实操意义
预算字段我用了一个简单的uint96 budget,但生产环境里这不够用。面对现实世界,你至少会遇到三种计价需求:
一种是固定金额:Agent 每次调用消耗 0.1 ETH,预算上限 1 ETH,允许调用十次。这是我们实现里最基础的模型。
一种是可变成本:Agent 的执行路径不确定,比如兑换路径可能经过两个池子也可能经过三个池子,每次实际消耗都不同。这种场景单靠调用方预估费用不可靠,合约可以在执行后按实际回传成本进行扣减,或者让目标合约自己向授权合约报告消耗。两种方式各有利弊,前者逻辑简单但可能低估费用,后者准确但会把复杂度转嫁给业务合约。
还有一种是跨资产:Agent 要动用用户的多种 ERC-20 和 ETH。这种情况下授权记录里需要挂一个“资产清单”,每一种资产独立设置预算上限。我的建议是不要用单一货币折算所有资产,因为价格波动会把你的风控模型搅乱。分币种设限,超限即失败,简单又稳。
4.3 Agent 的钱包管理
前面提到过:Agent 不应该持有用户资产私钥。实际工程里,Agent 的签名私钥建议放在独立的 MPC 服务或硬件安全模块里,进程被入侵和签名私钥泄露这两件事要尽量隔离。
我在本地测试时用的是最简单的方式:给 Agent 生成一个独立的 EOA,专门用于 execute 调用和成员签名。但这个 EOA 本身不需要任何余额,因为价值转移是从授权人账户走的(或者由授权合约代为扣款)。这个设计挺反直觉的,但很安全:Agent 地址的私钥泄露,攻击者也只能在用户授权的目标和限额内调用,无法直接转走资产。
4.4 用户侧交互设计
用户侧不能直接把创建授权变成一笔让人看懵的交易。我在设计前端交互时推荐三步走:
第一步,展示权限边界:明确告诉用户这个 Agent 能调用哪个合约、哪些函数、预算多少、多长时间有效。这一步用自然语言展示,不要直接给人看 selector 十六进制。
第二步,让用户签名授权记录并提供预算。可以走 EIP-712 类型的结构化签名,用户用钱包确认时能清晰看到权限内容,不需要先支付 gas。
第三步,Agent 上线后实时展示执行流水。用户随时能看到每一次调用的成本和结果,并且界面上一直放着一个大大的“撤销授权”按钮。这个按钮一定要直接调用revoke,而不是先发请求给后端再等确认。
5. 我自己实测下来最容易踩的坑
5.1 重放攻击:最隐蔽的预算刺客
起初我以为加了 nonce 就万事大吉,但问题出在非ce 的生成位置上。有一版我图省事,直接把 nonce 塞在 Agent 本地数据库里,结果 Agent 服务重启后 nonce 回滚,旧签名又被提交了一遍。几笔重复的兑换调用在几分钟内把预算花了个干净。自那以后我老实了:nonce 必须由合约维护,或者至少由合约对账校验。本地状态在任何分布式系统里都只能当辅助,不能当安全边界。
5.2 只校验函数,不校验参数,照样出问题
有一版我想当然地认为,只要 Agent 只能调用swap函数就安全了。但swap的函数签名里有个参数是“接收代币的地址”。Agent 可以把收益转到任意地址,包括攻击者指定的地址。这是函数级授权的天然盲区。
补救办法有两个方向:要么在授权合约里实现“参数模板”,把关键参数改成只能取某几个白名单值;要么在目标合约侧加一个授权模块,只接受特定来源的调用并且校验内部参数。我的建议是后者,因为把参数规则写进通用授权合约会越写越复杂,最终变成没人敢碰的巨型代码库。
5.3 预算扣减与实际执行成本不一致
另一个常见问题是预估费用和实际费用不一致。Agent 构造调用时,Charge 用的是预估成本,但目标合约真正消耗的 gas 或转账金额可能会更高。如果合约在调用前就锁死费用,实际不够的时候就会失败;如果按最终消耗扣,又可能出现 Agent 恶意高报费用,把预算套走。
我的处理方式是双轨制:合约执行时锁定一个最大费用值(cost),目标合约执行完后,实际消耗如果小于锁定值,多出的部分立刻退回授权人的可消费余额。锁定时校验spent + cost <= budget,退钱时只调 spent。这样的体验比较好,Agent 不会因为预估值偏低而频繁失败,用户也不会因为预估值偏高而被过度扣款。
5.4 不要把业务逻辑和授权逻辑混在同一个合约里
我最早做的版本把授权校验、业务执行、资产结算全写在一个合约里,看起来效率很高,但每次换业务逻辑都要重新部署整个授权合约。后来我改成“授权合约只管权限和预算,业务合约只管具体执行”,也就是让授权合约以代理身份调用目标合约,二者通过接口解耦。这样目标合约可以独立迭代,授权模块稳定不动,安全性也好审计得多。
5.5 事件日志要早设计,别等上线后补
Agent 调用的可追溯性,完全取决于事件里存了什么。我一开始只记录grantId和target,排查问题的时候发现根本不知道某笔调用具体做了什么。后来我把事件扩展为目标合约、选择器、费用、结果状态、调用时间五要素,排查问题的效率高了很多。日志字段设计千万别省,上线之后补日志参数是要动合约的,成本极高。
6. 哪些场景适合拿 ERC-8183 打底
6.1 DeFi 自动交易与策略执行
DeFi 是 ERC-8183 最直接的落地场景。用户可以授权一个 Agent 在特定 DEX 上执行兑换,但限制它只能操作白名单里的池子,并设置每日预算上限。Agent 根据行情信号自行调仓,用户不需要盯盘,也不需要把资产转进某个保险箱合约,资产始终留存在自己的钱包体系内,只是代理权被标准化了。实际收益模型清晰,审计成本也低。
6.2 DAO 治理中的代理人机制
DAO 治理投票是个被严重低估的 Agent 场景。很多持币人根本没时间跟踪提案,更别说逐条分析投票。通过 ERC-8183 这类授权,用户可以委托 Agent 在特定范围内的提案上代为投票,同时设置单次投票的预算上限(比如给 Agent 的操作 gas 补贴上限)。它比传统的“投票委托”更精细:用户可以限制 Agent 只对其中的技术类提案投票,不碰治理参数调整类提案。参数级授权在这里变成了现实刚需。
6.3 内容分发与自动结算
如果你的业务里有大量“按效果付费”的分发场景,比如内容创作者通过 Agent 自动发布内容并领取分润,那么 ERC-8183 可以直接用于搭建自动结算层。内容的每次发布消耗一次调用额度,分润则由目标合约按规则打款给创作者,整条链路不需要人工干预,也不需要信任一个中心化第三方定期记账。
6.4 订阅型 Agent 服务
我还见过一种模式:用户以月费形式订阅一个分析 Agent,Agent 每周末替用户生成链上活动报告,并批量为用户执行定投计划。这里 ERC-8183 可以作为“订阅接入层”,用户首次授权一整年预算,后续由 Agent 自动执行;月费不足时授权人可以直接撤销授权,等于退订。整个订阅生命周期都记账上,用户对资金去向有完整的审计轨迹,解决了不少服务商和用户之间的信任问题。
我自己做了几轮实验之后,对这个提案的感受是:技术实现难度其实不大,难的是把权限模型想清楚。Agent 经济增长到今天,缺的不再是更好的模型或更聪明的提示词,而是可信的执行层。ERC-8183 的价值恰恰是让“我允许这个 Agent 花我的钱、做我限定的事、超过边界就停”这件事变成链上常识。你如果正在做 Agent 相关项目,不妨从最小授权模型开始跑通流程,然后再一点点加上预算维度、参数维度和时间维度。这套骨架搭好了,后面往里填什么业务都不会歪。