前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
返回 AI 情报前线
All News · 全部资讯9915
  • LocalRAG 开源本地文档问答完整链路
  • Copilot群聊支持在同一对话处理多个仓库
  • Agent 执行工具前需要哪些安全关卡
  • 让 Claude Code 参考后端而不误改代码
  • Opus 快速模式开放,提速成本需细算
  • 让编程 Agent 看懂仓库依赖关系
  • 分类与抽取任务可让模型直接选答案
  • 为付费 API Agent 强制设置支出上限
  • n8n 长任务先响应,避免超时重试
  • Meta 新开放权重模型支持苹果芯片本地运行
  • 英伟达推出百万上下文开放多模态 Agent 模型
  • 用Claude Code做手机声波联机游戏
  • Google OAuth 支持刷新令牌密钥绑定
  • 将低频流程移入 Claude Code 按需技能
  • Copilot 多模型编排需累计计算调用费用
  • Gemini CLI 消费者登录退役带来的迁移提醒
  • 编码智能体自动放行配置可能成为攻击入口
  • 用事件钩子识别编码智能体何时等待操作
  • 并行编程 Agent 背后的 Git 工作树机制
  • Copilot 部分模型将于11月16日退役
  • NoteBank 用片段回放补强长上下文记忆
  • Meta 推出8160亿参数开放权重多模态模型
  • 两台 M5 Ultra Mac 可运行 Muse 开放模型
  • SageMaker 支持部署 Muse Spark Open
  • 谷歌 ADK 配方库贯通 Agent 开发部署
  • 为多轮 Agent 建立持续评估流水线
  • ctxtrim 压缩日志,节省编程上下文
  • 技能触发不准,先改好 description
  • AI 生成页面上线前,补验 GA4 事件链路
  • 让客服机器人把处理结果反馈到检索排序
  • Muse Spark Open 发布,主打编程与 Agent
  • FLUX 3 Klein 开放权重,8GB 显存可运行
  • Gemini 4 Argon 整合机器人控制与仿真
  • 英伟达开放多模态模型,面向企业智能体
  • Vercel 扩展后端与 Agent 部署能力
  • 英伟达发布面向智能体的多模态开放模型
  • 英伟达扩充智能体模型,覆盖云端与本地
  • 日合并超三万次,Anthropic 重构版本控制
  • 已加载 38 / 9915
8.0
热点
AI SCORE
技术实践2026-10-16 05:42

为付费 API Agent 强制设置支出上限

dev.to · AI#Agent#支付安全
Editor brief · 编辑速览

AgentBadge 的支付客户端强制要求提供支付限制,缺失配置即抛错。示例展示了单次支付上限、网络白名单和会话预算如何在签名前拦截支付,避免重试循环持续消耗余额。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

本文最初发表于 AgentBadge 博客。

2026 年 10 月 10 日,我们用自家的 agentbadge pay CLI 请求自家的生产 API,结果它拒绝向我们付款。一共三次,原因各不相同:一次触发单次调用限额,一次触发网络白名单限制,一次触发会话预算限制。每次拒绝都明确指出了具体字段及其值,每次都发生在生成签名之前。钱包余额一分钱都没动。这不是 bug 报告,而是功能正常工作的证明。本文就来讲讲它是怎么做到的。

一个在循环中调用付费 API 的 Agent,就像一个拿着信用卡的小程序。如果 API 每次重试都返回 402,或者 Agent 误读了发生变化的价格,循环就会不断签署付款,直到钱包被掏空。问题通常不在支付通道,而在于没有设置支出上限。因此,我们在构建 @agentbadge/circle-payments 的买方功能时,确立了一个前提:没有限额的付款客户端是 bug,不该成为默认行为。这里没有不限额模式:只要省略 PaymentCaps,createPayingClient 就会立即抛出 TypeError,此时进程还没有发出任何请求。

Hero

为什么支出限额必须设置,而不是可选项?

因为只要限额可以忘记设置,你就总会忘记。付款客户端在构造时要求提供 {maxPaymentUsd, sessionBudgetUsd},例如 {maxPaymentUsd: "0.50", sessionBudgetUsd: "5"}。省略它们就会立即失败:TypeError: PaymentCaps required — refusing to create an uncapped paying client。限额使用表示美元金额的十进制字符串,通过正则表达式校验;所有金额计算都使用 BigInt,以 6 位小数对应的最小单位进行,绝不使用浮点数。可选的 allowedNetworks 数组用于限定签名方可以操作哪些链,默认允许 Arc mainnet(eip155:5042)和 Arc testnet(eip155:5042002),这项限制会在其他任何操作之前执行。

Caps tiles

严格的 accepts 校验会拒绝什么?

凡是不符合契约的内容,都会拒绝,而且会明确指出拒绝了什么。每个 accepts[] 条目都必须通过五项检查:x402Version === 2;scheme ∈ {exact, eip3009-client-broadcast, gateway-batch};network ∈ 你的白名单;asset === 对应链的 USDC 地址;amount ≤ 单次调用限额。每次拒绝都会返回 {ok:false, reason, field, value},让 Agent 可以记录究竟是哪个字段导致付款被拒绝。

其中最严格的一条规则针对 extra.decimals。如果付款要求声明的小数位数不是 6,客户端会直接拒绝,而不是猜测。金额精度弄错,比如把 6 位小数当成 18 位,是代币支付中一个典型的坑。安全的处理方式是拒绝,而不是推断。

这些不是演示性质的测试。10 月 10 日对生产环境的实测产生了三次真实拒绝,每次都发生在签名之前:

refused field=amount value="300000": amount 300000 exceeds per-call cap 10000 (atomic units)
refused field=network value="eip155:84532": network "eip155:84532" not in allowedNetworks
refused: Session budget exhausted: spent 0 + requested 300000 > budget 10000 (atomic units)

Accepts checklist

会话预算如何阻止失控的循环?

客户端通过 SpendTracker 累计跟踪当前会话的支出,并且会在签名之前预留预算:validateAccepts → tracker.tryReserve(amount) → 只有完成这两步,签名方才会收到 typed-data payload。超过单次限额,就抛出 CapExceededError {cap, requested};超过会话预算,就抛出 BudgetExhaustedError {spent, budget, requested}。这两种情况下都不会调用 signTypedData。我们的限额拒付测试断言的正是这一点:钱包 mock 的签名调用次数为零。

支出会在资金实际提交时记录:通过 facilitator 的支付通道在 settle 时记录,自行结算则在 broadcast 时记录。onSpend(entry) hook 会将每个事件持久化到你自己的存储中。

同一条执行路径上还有两道叠加的保护:防循环机制会在携带 payment-signature 的重试请求再次收到 402 时抛出 PaymentNotAcceptedError,确保同一个请求绝不付款两次;paginateAll 则将每一页都视为一次付款,计入同一份预算。

Agent 签署什么,资金又在什么时候转移?

签名方式取决于 scheme,而且签名不等于支出。exact → EIP-3009 transferWithAuthorization 签名;facilitator 稍后才会结算,因此仅有签名不会转移任何资金。eip3009-client-broadcast(Arc 自行结算)→ 客户端自行广播 USDC 转账;如果声明了 extra.payerBinding,重试请求会携带 X-Wallet/X-Sig/X-Timestamp(EIP-191,agentbadge-pay:v1)。gateway-batch → BatchEvmScheme payload,可选择启用 gateway.autoDepositUsd,为 Gateway 钱包补充余额,避免在 settle 时因余额不足而失败。

10 月 10 日的实测用数字展示了签名与支出的区别:三次付费尝试都在 payment-signature 被接受后遇到了服务器 500 错误,facilitator 始终没有广播交易,付款方余额只在另外一次成功结算时发生了变化。失败的尝试产生了三次签名,链上结算次数却是零。即使服务器出了问题,支出上限依然有效。

如何运行,并在签名前完成验证?

const client = createPayingClient({
  caps: { maxPaymentUsd: "0.50", sessionBudgetUsd: "5" },
  signer: viemSigner(walletClient),
});
await client.get("https://agentbadge.xyz/api/total-scan");
// 402 → validateAccepts → cap-check → sign → retry → spend recorded

在 shell 中,agentbadge pay 同样强制执行限额规则。限额可以通过参数(--max-payment、--budget)或环境变量(AGENTBADGE_MAX_PAYMENT、AGENTBADGE_BUDGET)设置;私钥只能通过环境变量(AGENTBADGE_PAYER_KEY)提供,绝不能通过命令行参数传入,因为 ps 可以看到 argv。退出码:0 表示成功,2 表示拒付、触发限额或预算限制,3 表示传输错误。

--print-only 是买方的冒烟测试:它会解码 402、校验 accepts、打印付款详情,然后退出,全程不会接触签名方。以下是 10 月 10 日请求生产环境时的真实输出:

agentbadge pay https://agentbadge.xyz/api/total-scan \
  --max-payment 0.50 --budget 5 --network eip155:84532 --print-only
# {"scheme":"exact","network":"eip155:84532","asset":"0x036CbD53…",
#  "amountAtomic":"300000","priceUsd":"0.3","payTo":"0x9a66…95A5"}

print-only

客户端能在付款之前验证服务器的契约吗?

可以。客户端校验的 402 响应体,本身就是机器可读 API 契约的一部分。服务器提交到代码仓库中的 openapi.yaml 包含 x402 组件(X402PaymentRequired、X402PaymentRequirement、X402HonestRefusal),其中将 x402Version: 2、scheme 枚举、extra.decimals: 6 和 charged: false 固定为契约中的常量约束;如果规范与实际路由出现偏差,CI 的 drift-check 就会失败。通信双方验证的是同一份契约。

我们自己使用这套工具时,也被它抓到了问题。在同一次 10 月 10 日的实测中,我们的服务目录声明的是 eip155:5042002(Arc testnet),但实际的 402 响应只提供 Base Sepolia(eip155:84532)。默认只允许 Arc 的白名单拒绝了这项付款选项,直到我们显式传入 --network。我们自己的校验器发现了自家服务目录的偏差,这正是我们希望校验器触发的那类失败。

本文是 Arc Campaign 系列的 C18。上一篇:《拒绝就绝不收费——可由机器检查的拒付契约》。

相关链接:服务目录 · openapi.yaml · llms.txt · @agentbadge/circle-payments · AgentBadge

别只做认证,要实际测量。

如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
分类与抽取任务可让模型直接选答案
下一篇
n8n 长任务先响应,避免超时重试