x402-hono 深度解析:在 Hono 应用中接入 x402 支付协议的 HTTP 402 付费墙中间件
【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402
x402-hono是 x402 支付协议(x402 v1)针对 Hono 框架的官方中间件集成包,它让开发者只需几行代码即可在任意 Hono 路由上挂起基于 USDC 的“按次付费”付费墙:未支付请求收到 HTTP 402 响应,携带X-PAYMENT请求头的合法支付则放行并按链上签名结算。阅读本文后,你将掌握paymentMiddleware的完整参数体系、内置 Paywall 的渲染条件、可选的 Coinbase Onramp 集成步骤,以及中间件从路由匹配、支付校验到结算回头的完整内部调用链,并能读懂其单元测试如何固化这套行为。需要说明的是:该包在 package.json 中标注的版本为 1.2.0,位于legacy目录下,README 明确声明它实现的是x402 v1 协议、已弃用、仅接受安全补丁,生产项目应迁移到 v2 版本(@x402/hono、@x402/core等),迁移方法可参考仓库内的 Migration guide: v1 to v2。
安装与快速上手
安装
npm install x402-hono包的 npm 名称为x402-hono(见 package.json),同时提供 ESM(./dist/esm)与 CJS(./dist/cjs)两种入口,并额外导出./session-token子路径入口供 Onramp 集成使用。
最小可用示例
README 的 Quick Start 是可直接复制运行的最小集成,覆盖“声明收费路由 → 实现业务路由 → 启动服务”三步:
import { Hono } from "hono"; import { paymentMiddleware, Network } from "x402-hono"; const app = new Hono(); // Configure the payment middleware app.use(paymentMiddleware( "0xYourAddress", { "/protected-route": { price: "$0.10", network: "base-sepolia", config: { description: "Access to premium content", } } } )); // Implement your route app.get("/protected-route", (c) => { return c.json({ message: "This content is behind a paywall" }); }); serve({ fetch: app.fetch, port: 3000 });启动后,任何人直接访问/protected-route都会得到 402;只有携带合法X-PAYMENT头的请求才能拿到 JSON 响应。
paymentMiddleware 的四个参数
paymentMiddleware的函数签名定义在 src/index.ts:
export function paymentMiddleware( payTo: Address | SolanaAddress, routes: RoutesConfig, facilitator?: FacilitatorConfig, paywall?: PaywallConfig, )| 参数 | 类型 | 说明 |
|---|---|---|
payTo | EVM 地址(0x${string})或 Solana 地址 | 收款地址。EVM 地址经viem的getAddress做校验和规范化;@solana/kit的SolanaAddress同样受支持 |
routes | RoutesConfig | 受保护路由及其定价配置,见下文 |
facilitator | FacilitatorConfig(可选) | x402 结算服务(Facilitator)地址与鉴权头 |
paywall | PaywallConfig(可选) | 内置 Paywall 的展示与 Onramp 配置 |
中间件初始化时会通过useFacilitator(facilitator)预先生成verify/settle/supported三个 Facilitator 客户端方法,并用computeRoutePatterns(routes)把所有路由模式预编译成正则表达式,避免每个请求重复编译。
路由配置 RoutesConfig
路由表的类型定义在 x402 底层包的类型文件:
type RoutesConfig = Record<string, Price | RouteConfig>; interface RouteConfig { price: Price; // Price in USD or token amount network: Network; // 网络名,如 "base" / "base-sepolia" / "solana" config?: PaymentMiddlewareConfig; }两个实用特性值得注意:
- 简写形式:
RoutesConfig的 value 允许直接是一个Price(价格字符串/数字),等价于只写了price的路由配置;index.test.ts 中 computeRoutePatterns 的 mock 实现清晰展示了这种归一化逻辑。 - 路径模式:支持
*通配与[...]路径参数,例如/weather/*;key 里还可以带 HTTP 动词前缀(如GET /weather),未指定动词时默认为*(匹配所有方法)——这一点同样体现在测试对路由匹配 mock 的处理中(index.test.ts#L45-L71)。
price支持多种形式(Price = Money | ERC20TokenAmount | SPLTokenAmount):美元字符串(如"$0.10")、数字(如0.01),或指定资产最小单位的对象。中间件内部调用processPriceToAtomicAmount(price, network)将其换算成链上最小单位:例如"$0.001"在 USDC(6 位小数)下会得到maxAmountRequired: "1000"——这正是 index.test.ts 中断言的accepts数组内容。
支持的网络
README 示例里写的是"base" or "base-sepolia",但从 网络定义源码 看,v1 实际支持的Network枚举远不止这两个。EVM 网络(SupportedEVMNetworks):base、base-sepolia、avalanche、avalanche-fuji、polygon、polygon-amoy、iotex、sei、sei-testnet、abstract、abstract-testnet、peaq、story、educhain、skale-base-sepolia;SVM 网络(SupportedSVMNetworks):solana、solana-devnet。配置不支持的网络时,中间件会直接抛出Unsupported network: <name>(有对应的测试用例固化该行为)。
EVM 与 Solana 构建 paymentRequirements 的差异(源码 index.ts#L129-L203):
- EVM 网络:
scheme固定为exact,资产默认取该网络的 USDC 合约地址,maxTimeoutSeconds缺省为300,并在extra字段携带 USDC 的 EIP-712 域信息(name/version),供客户端生成 ERC-20 Permit 签名。测试中断言的accepts条目中可以看到asset: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"(Base Sepolia USDC)与extra: { name: "USDC", version: "2" }。 - Solana 网络:中间件会先调用 Facilitator 的
supported()接口,在返回的kinds中找到与当前network+exactscheme 匹配的条目并取出extra.feePayer;拿不到 feePayer 时直接抛错The facilitator did not provide a fee payer for network: ...。maxTimeoutSeconds缺省为60,mimeType缺省为空串。相关行为由 solana-devnet / solana 的两组 402 断言测试 覆盖。
每路由支付配置 PaymentMiddlewareConfig
README 列出的字段与 类型定义一致,后者额外包含inputSchema与errorMessages:
interface PaymentMiddlewareConfig { description?: string; // 支付描述,写入 402 响应的 paymentRequirements mimeType?: string; // 资源 MIME 类型,EVM 下默认 "application/json" maxTimeoutSeconds?: number; // 支付有效窗口,源码默认:EVM 300 / SVM 60 inputSchema?: object; // HTTP 请求结构描述(合并进 outputSchema.input) outputSchema?: Record<string, any>; // 响应的 JSON Schema discoverable?: boolean; // 是否对外可发现,默认 true customPaywallHtml?: string; // 完全自定义的付费墙 HTML resource?: string; // 资源 URL,缺省为当前请求 URL(反代下按 X-Forwarded-* 头重建) errorMessages?: { paymentRequired?: string; invalidPayment?: string; noMatchingRequirements?: string; verificationFailed?: string; settlementFailed?: string; }; }几点实现细节:
resource未显式指定时,中间件会检查X-Forwarded-Proto与X-Forwarded-Host头:两者都存在(反向代理场景)则用它们重建资源 URL,否则退回c.req.url(index.ts#L113-L127)。description/mimeType/inputSchema/outputSchema最终都会打包进outputSchema: { input: {...}, output: ... }结构,随 402 响应一起返回,供 x402 客户端(以及 AI Agent 等自动付费方)机器可读地理解资源。errorMessages允许为五个失败阶段分别定制报错文案,五组对应的单元测试逐条验证了自定义文案会覆盖默认错误。
Facilitator 配置
type FacilitatorConfig = { url: string; // x402 facilitator 服务地址 createAuthHeaders?: CreateHeaders; // 可选:为 verify/settle 请求生成鉴权头 };FacilitatorConfig定义于 middleware.ts#L8-L11。不传facilitator时使用公共测试网 Facilitatorhttps://x402.org/facilitator(常量DEFAULT_FACILITATOR_URL,见 useFacilitator.ts#L17)。createAuthHeaders的返回结构要求按用途分桶提供头(useFacilitator.ts#L19-L24):
type CreateHeaders = () => Promise<{ verify: Record<string, string>; settle: Record<string, string>; supported: Record<string, string>; list?: Record<string, string>; }>;useFacilitator内部会向<url>/verify(POST)、<url>/settle(POST)、<url>/supported(GET)发起请求,请求体携带x402Version、paymentPayload与paymentRequirements。若配置了createAuthHeaders,则按verify/settle/supported分桶合并到各自请求头上——这意味着你可以为验证和结算使用不同的凭据。
中间件核心流程:从 402 到结算
理解 paymentMiddleware 主体 的执行顺序,等于理解了整个 v1 HTTP 支付协议的服务端一侧:
路由匹配:取
method与c.req.path,用预编译的routePatterns调用findMatchingRoute;不匹配则直接next()放行,零开销。构建 paymentRequirements:按上文 EVM / SVM 分支生成
scheme: "exact"的支付要求数组。无
X-PAYMENT头 → 返回 402。这里区分两类客户端:- 浏览器(
Accept含text/html且User-Agent含Mozilla):渲染 Paywall HTML 并以 402 返回,金额按美元价格或链上最小单位换算展示,testnet: network === "base-sepolia"决定测试网标识; - API / 客户端:返回 JSON 402:
{ "error": "X-PAYMENT header is required", "accepts": [ { "scheme": "exact", "network": "base-sepolia", "maxAmountRequired": "1000", "payTo": "0x...", "asset": "0x...", "maxTimeoutSeconds": 300, ... } ], "x402Version": 1 }测试 should return 402 with payment requirements when no payment header is present 断言了该响应结构,浏览器分支则由 should return HTML paywall for browser requests 覆盖(断言
c.html以 402 被调用)。- 浏览器(
解码支付头:调用
exact.evm.decodePayment(payment)将 Base64 编码的X-PAYMENT头还原为PaymentPayload,并打上x402Version: 1。解码失败(malformed header)返回 402 +invalidPayment错误。需要注意,从源码结构看,v1 中间件对所有网络(包括 Solana 路由)都使用 EVM 的decodePayment解析支付头,这是 v1 的实现现状。匹配支付要求:
findMatchingPaymentRequirements(paymentRequirements, decodedPayment)找到与支付载荷网络/资产匹配的 requirement;匹配不上返回 402 +noMatchingRequirements。验证:调用 Facilitator
verify;isValid === false返回 402 并附带payer与失败原因,异常(如 Facilitator 连接失败)同样落入 402 分支,错误文案可用errorMessages.verificationFailed定制。放行业务:
await next()执行你的路由处理函数。结算与响应:这里有两个关键防御——
- 业务响应状态码≥ 400 时不做结算(服务未成功交付则不扣款),直接返回原响应;
- 状态码 < 400 时调用 Facilitator
settle,成功后把交易回执写入响应头X-PAYMENT-RESPONSE(由settleResponseHeader生成);结算失败则降级为 402 响应。 - 源码注释解释了为何要先
await next()再结算:Hono 中间件无法在响应发出后再追加头,因此结算必须在构造最终响应之前完成(index.ts#L326-L334)。
这一完整路径(402 拒绝 → 验证通过放行 → 结算写头 → 各失败分支的 402)都有对应测试用例:should verify payment and proceed if valid、should return 402 if payment verification fails、should handle settlement after response 等。
内置 Paywall:浏览器用户的支付入口
当检测为浏览器请求时,中间件调用getPaywallHtml生成付费墙页面。Paywall 组件位于 x402 底层包 paywall 目录,其 README 说明了它的能力边界:
- 自动完成钱包连接、网络切换、余额检查与支付处理;支持 Coinbase Smart Wallet、Coinbase EOA、MetaMask、Rabby、Trust Wallet、Frame,以及 Phantom、Backpack 等符合 wallet-standard 的 Solana 钱包;
- 多链感知:根据可用支付要求自动选择 Base / Base Sepolia / Solana / Solana Devnet 中最佳的一条并渲染对应钱包流程,无需额外配置;
cdpClientKey为可选项,启用后使用 Coinbase 托管 RPC(Enhanced RPC)改善连接性能;- Solana 流程运行时通过 Wallet Standard 发现已安装钱包,仅在选中 Solana 支付要求时才请求
solana:signTransaction权限。
PaywallConfig四个字段(类型见 middleware.ts#L13-L18):
type PaywallConfig = { cdpClientKey?: string; // CDP Client API Key,用于增强 RPC appName?: string; // 钱包选择弹窗中展示的应用名(paywall 默认 "Dapp") appLogo?: string; // 钱包选择弹窗中的 Logo sessionTokenEndpoint?: string; // Onramp session token API 路径 };其中sessionTokenEndpoint直接决定付费墙是否显示 “Get more USDC” 充值按钮:未配置时按钮隐藏。
可选集成:Coinbase Onramp
Onramp 集成完全可选——没有它付费墙照常工作。它的作用是让钱包余额不足的用户直接从付费墙跳转 Coinbase Onramp 购买 USDC。README 给出五步配置,下面逐一对应到实现。
第 1 步:创建 session token 路由
import { Hono } from "hono"; import { POST } from "x402-hono/session-token"; const app = new Hono(); app.post("/api/x402/session-token", POST);这个POST处理函数来自 session-token.ts,其内部实现值得细看:
- 从环境变量读取
CDP_API_KEY_ID/CDP_API_KEY_SECRET,缺失时返回 500Missing CDP API credentials——这正是 README 故障排查第一条的出处; - 请求体必须是
{ addresses: [{ address, blockchains? }], assets? },addresses为空数组或缺失时返回 400addresses is required and must be a non-empty array;blockchains缺省为["base"]; - 用
@coinbase/cdp-sdk的generateJwt以 Secret API Key 生成请求级 JWT(host 为api.developer.coinbase.com,path 为/onramp/v1/token),随后携带该 JWT 调用 Coinbase Onramp 的 token 接口; - 上游返回非 2xx 时,原样透传状态码(400/401/500)与
Failed to generate session token错误。
第 2 步:告知 Paywall 端点位置
app.use(paymentMiddleware( payTo, routes, facilitator, { sessionTokenEndpoint: "path/to/session-token-route", } ));路由注册路径与sessionTokenEndpoint必须完全一致(例如配置/api/custom/onramp就要app.post("/api/custom/onramp", POST)),不一致时 Paywall 前端请求会 404,表现为 README 故障排查中的 “API route not found”。
第 3~4 步:准备 CDP 凭据并开启安全初始化
在 CDP Portal 为你的项目创建 Secret API Key(注意:Onramp 需要的是Secret API Keys,不是 Client API Keys——用错密钥类型是 “Missing CDP API credentials” 之外的另一类常见坑),然后在 CDP Portal 的 Payments → Onramp & Offramp 页面将 “Enforce secure initialization” 开关置为 Enabled。
第 5 步:设置环境变量
# .env CDP_API_KEY_ID=your_secret_api_key_id_here CDP_API_KEY_SECRET=your_secret_api_key_secret_heresession-token.ts通过process.env读取这两个值(session-token.ts#L20-L29),因此必须在服务端运行环境中可用。
Onramp 工作机制(README 的 “How Onramp Works”):1)你的后端用 CDP API 安全地生成 session token;2)用户被携带 session token 重定向到 Coinbase Onramp;3)钱包地址与 app id 从不暴露在 URL 中——这正是 Secure Initialization 的设计目的。
测试视角:如何验证中间件行为
如果你需要在自己的项目中复刻这套保障,index.test.ts 是一个现成的行为清单(该文件超过 1500 行):mock 掉x402/verify与x402/paywall后,它验证了 402 响应结构(含accepts、x402Version: 1)、Solana 网络要求extra.feePayer存在、缺失 feePayer 与不支持网络的抛错、五类自定义错误文案、浏览器收到 HTML 付费墙、验证失败/抛错的 402 降级,以及验证通过后next()被调用、结算失败的 402 分支。session-token.test.ts 则覆盖了 Onramp 端点的凭据缺失、参数校验与上游错误透传场景。
结语:版本定位与迁移
x402-hono的价值在于展示了 v1 时代 x402 协议在 Web 框架中的完整落地形态:路由级定价、Facilitator 校验/结算分离、浏览器付费墙与 Onramp 充值闭环,全部压缩在一个 Hono 中间件里。但它的定位已明确——README 顶部的弃用声明指出,该包仅接受安全补丁,新的 Hono 集成应使用 v2 体系(@x402/hono、@x402/core、@x402/evm等)。本仓库的 typescript/packages/http/hono 即对应 v2 的 Hono 适配包;如需从 v1 迁移,可对照 迁移指南 处理协议字段与包名变化。
【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考