用x402串起智能体发现、购买与回执
x402-agent-shopper 演示智能体发现服务、选择卖家、通过 HTTP 402 和签名支付,再取得结果与交易记录的流程。文章指出,工具调用和支付协议之外,还需解决购买内容确认与卖家身份识别。
AI 深度解读
Agent 能调用工具,也能签名付款,但付完钱之后,它如何确认买到了什么、卖家是谁、结果能不能验?x402-agent-shopper 用一个买家、一个卖家和一条交易流水,把这些问题串成了可运行的演示。
作者准备在 Devcon 8 Mumbai 展示它。最有价值的不是几美分完成一次采购,而是那些真实接口暴露的边界:请求格式可能错,支付凭证可能被重放,返回的证书可以验签,却未必能证明卖家的身份。对准备给 AI 开放收费 API 的程序员,这些细节比接上支付更值得看。
能付款,为什么还不算会买东西
作者把问题分成了三层:MCP 让 Agent 发现并调用工具,x402 通过 HTTP 402、机器可读报价和 EIP-3009 签名提供支付路径,剩下的一层是交易契约。
这里的契约不是法律文本,而是程序能执行的约定:传什么参数、买什么结果、怎样检查、失败后怎么办。价格只能回答「多少钱」,回答不了这些问题。
演示中的一次任务先搜索「timezone」,找到 8 个候选,再选择 geospatial.openverbs.com。流水展示它和 agentsvc.io 的报价都是 $0.0020,2s.io 是 $0.0025;选中的调用显示 settled,耗时 230ms,结果为 Asia/Kolkata +05:30。整段任务接触 3 个卖家,其中两个是陌生服务,总花费 $0.057,耗时 2.6s。
这些数字是作者展示的一次运行记录,没有多轮统计,也没有成功率或性能对照。它证明流程可以串起来,不能据此估算生产环境的稳定性。
项目刻意保持简单:Node 22、ESM,唯一依赖是 viem,没有构建步骤和框架。run.mjs 串起发现、检查、付款、验证和记录;lib/discovery.mjs 搜索 Bazaar,lib/x402.mjs 处理支付挑战,lib/guard.mjs 控制预算。自建卖家运行在 :8787,/buy/certificate 的标价是 50000 μUSDC。

作者强调文件要短到观众在会议室后排也能读懂。这个约束适合教学演示,也让关键决策容易检查;它并不能替代生产服务需要的状态管理和故障处理。
最容易出错的,是报价之外的接口细节
作者接入的是真实 x402 Bazaar,而不是只照着规范实现。踩坑集中在三个地方:协议方言、请求参数和失败状态。
首先,402 报价没有统一的承载方式。有的放在响应体,有的放在 payment-required 头;网络可能写成 base、base-mainnet 或 eip155:8453,金额字段可能叫 amount,也可能叫 maxAmountRequired。客户端必须先归一化,再选择报价。
支付凭证也有兼容问题:v1 卖家读取 X-PAYMENT,v2 读取 PAYMENT-SIGNATURE,MPP 使用 Authorization: Payment。geospatial.openverbs.com 还同时返回 x402 USDC 报价和 WWW-Authenticate: Payment,指出第二种支付通道「evm」。客户端需要选择支付通道,不能只挑最低价格。
下面这段代码处理凭证头的尝试顺序,并在服务端明确报出歧义时回退:
const attempts = [
{ 'payment-signature': header, 'x-payment': header },
{ 'payment-signature': header },
];
if (challenge.mpp) attempts.push({ authorization: `Payment ${header}` });
let second = null;
for (const credentials of attempts) {
second = await send(credentials);
if (second.status !== 400) break;
const probe = await second.clone().text();
if (!/ambiguous/i.test(probe)) break;
}
要看清最后两行:它只针对状态码 400 且响应含 ambiguous 的情况继续尝试,不是遇到支付失败就随意重试。原文中的一个真实卖家会拒绝同时携带 x402 和 MPP 凭证的请求,返回 ambiguous_payment。
其次,报价不包含完整的调用说明。Bazaar 扩展声明接口接受 POST,JSON 请求体是 {lat, lon},作者最初却发送了带查询参数的 GET,结果付费调用拿到参数校验错误。对做过前后端联调的人,这个坑并不陌生,只是现在错误请求可能先花掉钱。
最后,签名正确不代表支付能完成。空钱包提交合法签名,作者遇到的是一个空的 402 响应,没有解释性消息。另一个漏洞更直接:缺少已使用 nonce 集合时,同一份授权可以再次铸造证书。他们后来让两个卖家都拒绝重放,返回 authorisation already used。
支付成功、请求有效和结果合格,是三个需要分别检查的状态。
预算要挡在签名前,收据要能离线验证
买家使用 EIP-3009 TransferWithAuthorization 授权转账。按作者描述的支付机制,facilitator 承担 gas;客户端调用 signAuthorization(accept, account) 生成凭证。
作者把预算检查放在签名之前,而不是付款后统计。Budget.approve() 依次检查停止文件、网络白名单、单次上限 maxPerCall 和会话上限 maxTotal,金额使用 BigInt。执行 touch STOP 后,后续购买都会被拒绝。
这个顺序很关键:签名是授权动作,预算规则必须先决定这次授权能否发生。不过,原文代码在批准时就增加 spent,因此这里更接近「累计批准金额」。原文没有说明失败后的额度回补,也没有展示并发采购如何预留额度,不能直接把它理解成完整账本。
交易末端出售的是证书。卖家将 JSON 规范化、计算哈希,再用 ed25519 签名;买家只拿公钥就能离线检查:
const { certificate, publicKey } = mint({ buyer: account.address, item, result });
const verification = check(certificate, publicKey); // → { valid: true, digest }
要注意 check(certificate, publicKey):验证不依赖卖家继续在线。但如果公钥也是卖家随响应提供的,它只能证明证书与这把钥匙匹配。卖家身份如何与可信公钥绑定,原文没有说清;验签也无法自动证明业务结果正确。
测试则围绕失败路径展开。test/e2e.mjs 有 12 项检查,用临时端口和一次性钱包跑通 402、签名、验证、铸证及离线验签,不需要充值或联网。test/contract.mjs 有 27 项检查,覆盖坏签名、重放、垃圾凭证、少付、错误网络、停止开关和缺少密钥,并断言状态码及机器可读原因。它实际抓出了重放漏洞,以及检查器强制把 localhost 改成 https 的问题。
test/offline.mjs 的 3 项检查更干脆:替换 globalThis.fetch,只要触网就记录 NETWORK-CALLED 并抛错,以此证明 fixtures 模式确实离线。
我的判断
这篇最值得借鉴的是把协议兼容、消费约束和失败原因当成主流程。常见的 API 演示往往止于一次 200 响应,作者把拒绝购买也做成可观察、可测试的行为,这对自动执行任务的客户端更有意义。
但「真实结算」需要打个问号。前文强调真实签名、真实结算头和 settled 状态,后文又明确说自建部分没有 facilitator,只验证结算、不广播交易。原文没有完整划清外部服务与本地卖家的结算边界,不能把所有演示路径都理解为已经完成链上资金转移。
作者也承认缺少退款、幂等键和完整多支付通道支持。lib/facilitator.mjs 留出了接入点,真实 facilitator 结算被描述为一次 POST /settle,但完整的生产故障处理并未展开。
项目没有 LLM,Agent 是确定性程序。这让协议调试更清楚,却没有验证模型是否会选错服务、误读报价或错误重试。未来通过 MCP 暴露同一引擎、保留同一预算守卫,是作者提出的方向,尚不是已验证结果。
相比已有服务目录和统一鉴权的内部 API,陌生卖家市场要处理更多协议差异与信任问题。原文没有对照数据证明这套方式更便宜或更可靠;它提供的是一份有实测支撑的踩坑记录,以及作者对交易契约的判断。
能拿来做什么
- 如果你正在用 Node 开发收费 API,可以先整理 llms.txt、/.well-known/x402、类型化 schema 和 agent card。项目还展示了 /.well-known/schema.json;把请求方法、参数形状和返回结构写清楚,是减少付费误调用的起点。
- 如果你的 AI 编程工具准备接入付费服务,把网络、单次金额、会话预算和停止开关放在签名入口统一检查。模型发起的调用也应经过同一入口。
- 如果你要复现项目,先运行
node run.mjs --fixtures查看无需联网和资金的完整流水,再运行node test/contract.mjs。优先理解重放、少付和错误网络如何被拒绝,比只看成功截图更有帮助。 - 如果你负责交易结果页或调用日志,可以借鉴 receipt tape:同时呈现候选报价、选择结果、支付通道、结算状态和验证结果。遇到「扣钱但结果不可用」时,这些信息才足以帮助定位问题。