@ agentcash/路由器
X402和MPP上最快的API发货方式.
x402和MPP支付,相容的发现,和最小的锅炉板. 使用@ agentcash/routter, 代理在 代理卡许 从第一天起, 整个代理商的生态系统可以称为终点.
安装
pnpm add @agentcash/router
pnpm add zod # peer dependency
核心是框架不可知性(网络标准) Request编号 :Response)——Next.js是一个托管选项,而不是依赖. 见 托管。 。 。 .
环境
建议的切入点读取其特征: process.env复制盘 .env.example 住在雷波根头.
x402(当 CDP_API_KEY_ID + 键 CDP_API_KEY_SECRET 已设定)
至少必须配置一个支付协议: 设置 x402 的 CDP 密钥对 , MPP_SECRET_KEY 或两者兼而有之.
| 瓦尔 | 需求 | 目的 |
|---|---|---|
EVM_PAYEE_ADDRESS | 对 | 接收x402和MPP付款的EVM地址(0x…, 20字节). 切入小写 零地址被拒绝 . |
CDP_API_KEY_ID, (中文(简体) ), CDP_API_KEY_SECRET | 启用 x402 时 | Coinbase开发者平台为默认的EVM促进器提供证书. 存在切换 x402 上——在启动时从未检查有效性,所以占位符值为本地 dev 工作(见下文的点名) 。 在慷慨的 CDP 自由级创建 API 密钥 https://portal.cdp.coinbase.com/projects/api-keys 页面存档备份,存于互联网档案馆 (签名需要电话验证,因此提前提供CI/自动设置的密钥). T3 电话 @t3-oss/env-nextjs 用户必须在其环境图中声明这些内容. |
路由建筑公司给主持人打电话. 创建路由器可以启动一个背景获取器支持的付款类型,包括期间
next build。带有缺失或占位的 CDP 密钥,你会看到[x402] facilitator /supported failed, using hardcoded baseline: …在构建/dev日志中。 这是一个优雅的倒计时,而不是致命的错误;付费路线仍然服务于正确的402挑战. 占位符密钥是支持的本地- dev路径——真实的密钥仅用于支付验证和结算.
索拉纳
| 瓦尔 | 需求 | 目的 |
|---|---|---|
X402_BUILDER_CODE | 没有 | ERC-8021链上归属的基础构建者代码(^[a-z0-9_]{1,32}$) (中文(简体) ). 被宣布为每一次x402挑战的应用代码;促进者将其附加到和解调用数据中,将每个结算的付款计入您的应用 https://dashboard.base.org 页面存档备份,存于互联网档案馆。登记在 设置 → 构建器代码。 。 。 . |
SOLANA_PAYEE_ADDRESS | 没有 | 设定时添加一个索拉纳 exact 所以路由器要索拉纳付款. .upTo() 纯基并 .session() 仅为 MPP索拉纳客户只能支付静态价格 .paid() 路线. |
SOLANA_FACILITATOR_URL | 没有 | 取代索拉纳x402调解人. 默认为 DEFAULT_SOLANA_FACILITATOR_URL。 。 。 . |
MPP( 自动启用时 MPP_SECRET_KEY 设置)
| 瓦尔 | 需求 | 目的 |
|---|---|---|
MPP_SECRET_KEY | 启用 MPP 时 | 服务器端的 MPP 秘密 。 正在切换 MPP . |
MPP_CURRENCY | 启用 MPP 时 | 临时货币地址。 使用 TEMPO_USDC_ADDRESS (原始内容存档于2018-03-26) (英语). Tempo UDC. |
TEMPO_RPC_URL | 没有 | Tempo JSON-RPC 链上核电厂的终点。 对公众的默认 DEFAULT_TEMPO_RPC_URL (单位:千美元)https://rpc.tempo.xyz) (中文(简体) ). 只有在您有指定终点时才能覆盖 . |
MPP_OPERATOR_KEY | 没有 | 签名服务器端关闭/ 设置 。 设定时, 自动启用 MPP 会话模式( 需要 .session():流出和要求-模式/单位计费)。 地址必须等同于收款人. |
MPP_FEE_PAYER_KEY | 没有 | 赞助频道开放/TopUp的客户端气体. 必须下定决心去另一个地址 MPP_OPERATOR_KEY (Tempo拒绝付费的txs,如果 sender === feePayer) (中文(简体) ). |
MPP会话模式需要收款人的私钥. 与x402(仅地址)不同,
.session()路由结算服务器侧, 所以MPP_OPERATOR_KEY必须是私钥 联 合 国EVM_PAYEE_ADDRESS为了本地的发展, 薄荷薄荷 抛出键盘 在那里,两行:npm i -D viem # or pnpm add -D viem node -e "const {generatePrivateKey, privateKeyToAccount} = require('viem/accounts'); const pk = generatePrivateKey(); console.log('EVM_PAYEE_ADDRESS=' + privateKeyToAccount(pk).address); console.log('MPP_OPERATOR_KEY=' + pk);"
其它
| 瓦尔 | 需求 | 目的 |
|---|---|---|
BASE_URL | 在韦尔塞尔之外 | 来源 URL (https://api.example.com) (中文(简体) ). 负载:用作402域名,OpenAPI服务器URL,和MPP备忘录前缀. 必须匹配公有领域。 在韦尔塞尔, createRouterFromEnv 自动删除此选项 VERCEL_PROJECT_PRODUCTION_URL,则 VERCEL_URL。 。 。 . |
VERCEL_PROJECT_PRODUCTION_URL, (中文(简体) ), VERCEL_URL | 没有 | Vercel system env vars 用作后退 BASE_URL 未设定。 不要手动设置这些; Vercel 在构建和运行时提供它们 . |
KV_REST_API_URL, (中文(简体) ), KV_REST_API_TOKEN | 没有 | Upstash / Vercel KV (英语). Back SIWX nonce, SIWX 权限, 和 MPP 重放. 记忆回落在无服务器生产中不安全. 提供Kv Store是极好的建议. |
快开始
1. 创建路由器
有两种方式可以初始化. 选一.
备选案文A: createRouterFromEnv (建议. 读取 process.env,验证前面的每个值,并丢出一个单数 RouterConfigError 和所有的问题同时发生 自动可操作性 x402 当 CDP_API_KEY_ID编号 :CDP_API_KEY_SECRET 设定, 自动可操作的 MPP 当 MPP_SECRET_KEY 设置(至少需要两者之一),自动添加索拉纳在 SOLANA_PAYEE_ADDRESS 设置, 当 MPP_OPERATOR_KEY 已经设定.
// lib/router.ts
import { createRouterFromEnv } from '@agentcash/router';
export const router = createRouterFromEnv({
title: 'My API',
description: 'Pay-per-call search.',
guidance: 'POST /search with { q: string }. Returns top 10 results.',
});
没有密码吗? 设置占位符值
CDP_API_KEY_ID编号 :CDP_API_KEY_SECRET——路由器靴,记录倒置警告,付费路线服务于正确的402个挑战. 真实的密钥只需要用于核实和结算实际的付款(而Coinbase的注册需要电话验证,所以不能完全自动化).
备选案文B:建立 RouterConfig 并传递给 createRouter。 。 。 . 需要自定义网络、多个收款人、非标准资产或任何设置时使用此功能 createRouterFromEnv 没有暴露. createRouter 运行同样的校验 RouterConfig 形状.
// lib/router.ts
import { createRouter, BASE_MAINNET_NETWORK } from '@agentcash/router';
export const router = createRouter({
baseUrl: 'https://api.example.com',
payeeAddress: '0x…',
network: BASE_MAINNET_NETWORK,
protocols: ['x402'],
x402: {
accepts: [
/* … */
],
},
discovery: {
title: 'My API',
version: '1.0.0',
description: 'Pay-per-call search.',
guidance: 'POST /search with { q: string }. Returns top 10 results.',
},
});
network只使用 CAIP-2 标识符 (单位:千美元)eip155:8453, (中文(简体) ),solana:…)——友好名类base或者说base-sepolia失败类型检查并丢弃unsupported_x402_network在建筑。 导入导出常数( E)BASE_MAINNET_NETWORK, (中文(简体) ),SOLANA_MAINNET_NETWORK)而不是手写字符串. 这同样适用于x402.accepts[i].network。 。 。 .
2. 登记路线
一个副作用模块记录每个路径. 导入它将包含注册;主机(下一步)为注册的任何东西服务.
// lib/routes.ts
import { z } from 'zod';
import { router } from './router';
router
.route('search')
.paid('0.01')
.body(z.object({ q: z.string() }))
.handler(async ({ body }) => search(body.q));
router
.route({ path: 'inbox/status', method: 'GET' })
.siwx()
.handler(async ({ wallet }) => getStatus(wallet));
router
.route({ path: 'health', method: 'GET' })
.unprotected()
.handler(async () => ({ status: 'ok' }));
每个路线都选择一个认证模式 .paid(), (中文(简体) ), .upTo(), (中文(简体) ), .session(), (中文(简体) ), .siwx(), (中文(简体) ), .apiKey(),或 .unprotected() 看到了吗 Auth 模式路径可以声明 {param} 部分(drafts/{draftId}/commit → ctx.params.draftId) (中文(简体) ).
3. 为它服务
Next.js——一个包罗万象的文件 (建议):
// app/api/[[...route]]/route.ts
import '@/lib/routes'; // side-effect import: registers all routes
import { router } from '@/lib/router';
import { nextHandlers } from '@agentcash/router/next';
export const { GET, POST, PUT, PATCH, DELETE } = nextHandlers(router);
Hono / Bun / 节点 / 任何获取运行时间 :
import { serve } from '@hono/node-server';
import './routes';
import { router } from './router';
serve({ fetch: router.fetch, port: 3000 });
Ưμ㼯Aexport const POST = router.route(…)…handler(…)也工作——见 托管 用于所有三种模式,路径参数,以及 basePath。 。 。 .
4. 发现
router.fetch (因此,包罗万象) /{basePath}/openapi.json 财务报告和审定财务报表 /{basePath}/llms.txt 自动,加取出运行时的根别名。 在 Next.js 上,加入两条一行路由文件的常规根路径:
// app/openapi.json/route.ts — OpenAPI 3.1 at GET <origin>/openapi.json
import '@/lib/routes';
import { router } from '@/lib/router';
export const GET = router.openapi();
// app/llms.txt/route.ts — plain-text agent guidance at GET <origin>/llms.txt
import { router } from '@/lib/router';
export const GET = router.llmsTxt();
折旧:
router.wellKnown()(单位:千美元)/.well-known/x402资源清单)不再是推荐的发现表面. 处理器仍然工作,如果你已经为它服务——现有的x402-本土客户不会中断——但新的整合应该依赖/openapi.json财务报告和审定财务报表/llms.txt。 。 。 .
Bazaar 目录元数据
x402 主持人坚持从每次结算的付款中获取服务元数据 巴扎尔 发现目录。 路由器从发现配置到402的挑战 resource 块 :
createRouterFromEnv({
title: 'Quote API', // serviceName defaults to title when it fits (≤32 ASCII chars)
serviceName: 'Quote API', // explicit override
tags: ['quotes', 'wisdom'], // ≤5 tags, each ≤32 chars; defaults to the route's OpenAPI tag
iconUrl: 'https://example.com/icon.png', // HTTPS, ≤2048 chars
...
});
无效的值在以结构化配置问题启动时失败, 而不是被调解人默不作声地丢弃 .
5. 说吧
未支付请求获得 a 402 挑战存在于标题中(参见 阅读402项挑战:
curl -i -X POST https://api.example.com/api/search \
-H 'content-type: application/json' -d '{"q":"hello"}'
用任何 x402/ MPP 客户端支付 :
# AgentCash CLI — auto-negotiates x402 or MPP
npx agentcash fetch https://api.example.com/api/search -m POST -b '{"q":"hello"}'
# mppx CLI — MPP reference client
npx mppx https://api.example.com/api/search -J '{"q":"hello"}'
托管
路由器会讲网络标准 Request编号 :Response 通过嵌入式发送 浩诺 app,所以任何获取运行时间都会运行相同的路由定义. 三种托管模式:
Next.js 包罗万象(建议)
一个路由文件为每一个注册的路由服务——没有每个路由文件,没有发现桶:
// app/api/[[...route]]/route.ts
import '@/lib/routes'; // side-effect import: registers all routes
import { router } from '@/lib/router';
import { nextHandlers } from '@agentcash/router/next';
export const { GET, POST, PUT, PATCH, DELETE } = nextHandlers(router);
包罗万象 /{basePath}/* (默认) /api/*,包括: /api/openapi.json 财务报告和审定财务报表 /api/llms.txt。无比路径获得 notFound() 自动重新发现信封 。 根的发现别名( /openapi.json, (中文(简体) ), /llms.txt)仍然需要自己的路由文件(export const GET = router.openapi())或中相器重写为通相.
Ưμ㼯A
每个端点有一个路由文件——当您想要每个路由文件时仍然适合:
// app/api/search/route.ts
export const POST = router.route('search').paid('0.01').body(schema).handler(handler);
每个文件的三个细节 包罗万象的模式使得过时:
- 导出 const 名称对
.method()。 。 。 . 您导出的名称( E)GET编号 :POSTNext.js服务的动词控制;.method()(或route({ method })控制在发现中公布的动词。 路由器默认为POST(或GET何时.query()——任何其他GET路由必须明确声明,或者发现广告是错误的动词. - 发现需要一桶. 接下来,Js懒惰的路由文件 第一次击中,所以
app/openapi.json/route.ts必须import '@/lib/routes-barrel'(一个模块导入每个路由文件)或已卸载的路由不会出现在光谱中. ()llms.txt仅由静态配置而不需要) - 无与伦比的路线倒置. 挂载
router.notFound()包罗万象app/api/[[...path]]/route.ts,每个动词一个导出)所以 stale 代理调用得到一个JSON 404,带有再发现链接,而不是HTML错误页.
Hono / Bun / 节点 / 任何获取运行时间
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
const app = new Hono();
app.route('/', router.hono()); // or use router.fetch directly
serve({ fetch: app.fetch, port: 3000 });
router.fetch(request) 是一个标准的获取处理器; router.hono() 将内部的应用程序显示为更大的应用程序。 见 examples/hono 一个可运行的服务器.
路径参数和 basePath
路径可能声明 {param} 区段,在每个主机模式下均取:
router
.route('drafts/{draftId}/commit')
.unprotected()
.handler(async ({ params }) => commit(params.draftId));
RouterConfig.basePath (默认) 'api'控制挂载/广告前缀:线路服务于 {baseUrl}/{basePath}/{path}。通过一个空字符串来挂起源根.
Auth 模式
| 方法 | 目的 |
|---|---|
.paid(price) | 固定的,由参数衍生的,或分级支付前期(x402, MPP, 或两者兼有). |
.upTo(maxPrice) | 处理器计算帐单;处理器呼叫 charge(amount) 并且请求一次性解决运行中的全部问题. 仅出自x402. |
.session({ unitCost, maxPrice }) | 通过一个MPP支付渠道(MPP)向每个单位开具账单 session 意图. .handler() 准确的账单 unitCost· ; .stream() 电话 charge() 每个产量. 仅限MPP. 流动需要这一点。 (.metered({ tickCost, ... }) 仍为已贬值的别名。 ) |
.siwx() | 钱包的身份,没有报酬。 携带SIWX挑战返回402. |
.apiKey(resolver) | X-API-Key 或者说 Authorization: Bearer <key>组合 .paid() 编号 : .upTo() 编号 : .session()。 。 。 . |
.unprotected() | 无正觉. |
router
.route({ path: 'admin/users' })
.apiKey(async (key) => db.admin.findByKey(key)) // null => 401
.handler(async ({ account }) => db.user.findMany());
router
.route({ path: 'gated' })
.apiKey(resolver)
.paid('0.01') // key AND payment
.handler(fn);
.siwx() 在付费路线上。 支付一次, 身份重播
.paid() 财务报告和审定财务报表 .upTo() 编曲 .siwx() 一个免费的一次性重播模式 第一个请求通常解决(x402付款);一旦成功,钱包将记录在应享权益KV中。 随后为钱包提交有效SIWX签名的请求跳过支付并直接运行处理器. 打开 .upTo() 路线, charge(amount) 成为SIWX回放路径上的禁放. 处理者可以继续无条件拨打路线.
router
.route({ path: 'inbox' })
.paid('0.01')
.siwx() // first call pays $0.01, later calls present a SIWX sig instead
.handler(async ({ wallet }) => getInbox(wallet));
.session() 两者相互排斥 .siwx() ——每个单位的MPP计费没有权益模式——如果合并,建设者会投注注册.
发现: 无服务器/ 多事件部署必须提供真实
kvStore(原始内容存档于2017-09-29). Upstash / Vercel KV. 没有权利,就按程序处理Map,因此在A实例上支付的钱包在B实例上被视为未付,用户再次收费.
阅读402项挑战
未偿请求的返回取决于路由的认证模式:
- 仅付款路线 (单位:千美元)
.paid(), (中文(简体) ),.upTo(), (中文(简体) ),.session()无.siwx()返回 402 与 空正体. 整个挑战——接受、价格、计划——以基本64编码于PAYMENT-REQUIRED响应头 (x402 v2), MPP 在WWW-Authenticate别res.json()这些; 解码信头 . - SIWX路线 (单位:千美元)
.siwx(),单独或以定价模式组成)返回 402 并带有 JSON 躯体 倒计时PAYMENT-REQUIRED标题,包括sign-in-with-x扩展名。 头和身体总是一样.
每402个也带一个 X-Agent-Identity 响应头:可选 挑战 (notce,域名/路由绑定,过期). 持有 Dod 的客户端可以签名并寄回证明 X-Agent-Identity 在重试中; 已核实的DD随后可供处理者使用 ctx.actor它完全是可选的——它从未提出过请求,与SIWX或付款无关。 没有DD的客户可以忽略它.
当身体被验证时
用于生成的.paid(fn))和分级定价,以及航道 .validate() 或退出会话,正文被解析 在此之前 402项挑战,使挑战能够引用准确的价格(和 .validate() 以自己的身份拒绝。 在所有其他付费路线上,一个赤裸的无酬探测器得到它的402 不检查尸体 ——一个畸形的身体仍然产生402分,而不是400分. 给钱的再试再剖析并验证身体 在此之前 付款核实和结算,所以400元从不花费打电话者的钱.
.paid(), (中文(简体) ), .upTo(),以及 .session() 是相互排斥的定价模式:每条路线选择一条.
.paid(): 固定、 引出或分级
静静.
.paid('0.02')
(原始内容存档于2018-10-29). args-entered. 从被剖出的身体计算出价格. 掷出 HttpError 在402挑战之前拒绝.
.paid((body) => calculateCost(body), { maxPrice: '5.00' })
.body(genSchema)
maxPrice 上限计算数量并起到对非HttpError 定价函数所抛出的例外(HttpError 总是被重新提出。 无 maxPrice,该路由完全信任您的功能(没有上限,没有倒置),并在出错时返回500个.
等级.
.paid({
field: 'tier',
tiers: {
'10mb': { price: '0.02', label: '10 MB' },
'100mb': { price: '0.20', label: '100 MB' },
},
})
.body(uploadSchema)
.upTo(): 处理器计算,只有x402
处理器呼叫 charge(amount) 一次或多次; 请求一次解决累计总数, 上限为 maxPriceA级 charge() 这会推动运行中的全部过去 maxPrice 丢出一个400 CHARGE_OVER_CAP 出错(请求失败而一无所获)——它不是默默地被夹住的,所以如果部分工作仍应计费,请注意你的循环. 需要一个 'upto' 至少接受一个配置的x402网络(createRouterFromEnv (原始内容存档于2017-09-31). auto-adds one on Base.
.upTo('0.05')
.body(schema)
.handler(async ({ body, charge }) => {
await charge('0.001');
// ... more work ...
await charge('0.002');
return result;
});
.session():每个单位,只限MPP
通过一个微额供资方案付款渠道——微额供资方案——每个单位开具账单 session 意图。要求 MPP_OPERATOR_KEY (单位:千美元)createRouterFromEnv 设定时自动可操作会话模式). unitCost 用于 MPP 会话挑战的地图 amount· ; unitType 单位名称; maxPrice 是一个路由器-强制总上限(MPP本身仅限凭证前厅和存款).
请求模式. .handler() 准确的账单 unitCost 根据每项请求:
.session({ unitCost: '0.01', maxPrice: '0.05', unitType: 'request' })
.handler(async ({ body }) => { ... });
流来. .stream() 在SSE上为会话服务 charge() 呼叫单单,最多 maxPrice数字 :
.session({ unitCost: '0.0001', maxPrice: '0.05', unitType: 'token' })
.stream(async function* ({ body, charge }) {
for await (const token of streamLLM(body.prompt)) {
await charge();
yield token;
}
});
.metered({ tickCost, ... })是一个被折旧的别名,用于.session({ unitCost, ... })并行为一致.
流线是只使用 MPP . .stream() 编号 .paid() 编号 : .upTo() 编号 : .unprotected() 路由抛出注册.
预付款确认
对于在引用价格前需要数据库查询的检查:
router
.route({ path: 'domain/register' })
.paid(calculatePrice, { maxPrice: '10.00' })
.body(RegisterSchema)
.validate(async (body) => {
if (await isDomainTaken(body.domain)) {
throw Object.assign(new Error('Domain taken'), { status: 409 });
}
})
.handler(async ({ body, wallet }) => registerDomain(body.domain, wallet));
这些线路的管道订单: body parse -> validate -> 402 challenge -> payment -> handler. (中文(简体) ).validate() 是挑战前身体分析的导火索之一 当身体被验证时. ) (中文(简体) )
插件钩
import { createRouterFromEnv, type RouterPlugin } from '@agentcash/router';
const myPlugin: RouterPlugin = {
onRequest(meta) {},
onPaymentVerified(ctx, payment) {},
onPaymentSettled(ctx, settlement) {},
onResponse(ctx, response) {},
onError(ctx, error) {},
onAlert(ctx, alert) {},
};
export const router = createRouterFromEnv({
title: 'My API',
description: '…',
guidance: '…',
plugin: myPlugin,
});
所有的钩子都是可选的和放火和遗忘的;它们从不拖延反应. 使用钩子为资源寿命周期添加额外的遥测或灵活性.
调试中 onAlert
路由器报告内部警告和错误( 付款核实失败、 模拟失败、 配置错误) onAlert: 由于未注册插件, 这些信件会被默默地丢弃, 因此连接一个日志插件是了解为什么请求失败的最快方式 。 前进 onAlert (和 onError在您的日志中:
const loggingPlugin: RouterPlugin = {
onAlert(_ctx, alert) {
(alert.level === 'error' ? console.error : console.warn)(
`[router:${alert.route}] ${alert.message}`,
alert.meta ?? '',
);
},
onError(_ctx, error) {
console.error(`[router] ${error.status} ${error.message} (settled=${error.settled})`);
},
};