02 — 三层架构与编排器
本章回答"为什么 x402 一套代码能支持多链、多付款方案、多传输层"。答案是它把系统切成互不依赖的三层,并用三个"编排器"把可插拔的方案实现编织起来。
2.1 三层解耦:为什么这么切
规范 §架构 把 x402 明确划成三层(specs/x402-specification-v2.md 开头):
| 层 | 负责 | 变化维度 | 例子 |
|---|---|---|---|
| Types(数据) | 消息长什么样 | 不变 | PaymentRequired / PaymentPayload / SettleResponse |
| Logic(逻辑) | 怎么形成、验证、结算一笔付款 | 随方案×网络变 | exact-EVM、exact-SVM、upto-EVM |
| Representation(表示) | 消息怎么传输 | 随传输层变 | HTTP header、MCP _meta、A2A |
切这三刀的回报:加一条新链,只动 Logic 层(写一个新 mechanism);加一种新传输,只动 Representation 层(写一个新 adapter);Types 层和已有代码一律不碰。这就是为什么仓库里能同时躺着 EVM/SVM/Aptos/Stellar/TON… 十几条链而核心逻辑只有一份。
┌─────────────────────────────────────────────┐
│ Representation 层:HTTP / MCP / A2A 适配 │ ← 换传输只动这层
├─────────────────────────────────────────────┤
│ 编排器:x402Client / ResourceServer / │ ← 路由 + 生命周期,链无关
│ x402Facilitator(注册表 + hook) │
├─────────────────────────────────────────────┤
│ Logic 层:SchemeNetwork* 实现 │ ← 加链/加方案只动这层
│ exact-EVM exact-SVM upto-EVM ... │
├─────────────────────────────────────────────┤
│ Types 层:PaymentRequired/Payload/Settle... │ ← 几乎永不变
└─────────────────────────────────────────────┘
2.2 方案契约:三个 SchemeNetwork* 接口
一个"方案×网络"组合(如 exact × Base)要落地,得在三个角色处各实现一个接口。它们都定义在 core/src/types/mechanisms.ts:
客 户端侧:SchemeNetworkClient —— 只做一件事,造付款
// core/src/types/mechanisms.ts:62-71 — SchemeNetworkClient
export interface SchemeNetworkClient {
readonly scheme: string;
readonly schemeHooks?: SchemeClientHooks;
createPaymentPayload(
x402Version: number,
paymentRequirements: PaymentRequirements,
context?: PaymentPayloadContext,
): Promise<PaymentPayloadResult>; // 产出 {payload, extensions?}
}
它拿到一份选定的报价,吐出 payload(那段方案特定的签名货物)。对 exact-EVM 来说,这一步就是"用钱包签一笔 EIP-3009 授权"(第 3 章)。
设施方侧:SchemeNetworkFacilitator —— 验证 + 结算
// core/src/types/mechanisms.ts:82-154 — SchemeNetworkFacilitator(节选)
export interface SchemeNetworkFacilitator {
readonly scheme: string;
readonly caipFamily: string; // "eip155:*" / "solana:*",用于按链族归类签名者
getExtra(network): Record<string, unknown> | undefined; // 进 /supported 的方案附加信息
getSigners(network): string[]; // 该 facilitator 在此网络用的签名/代付地址
verify(payload, requirements, context?): Promise<VerifyResponse>;
settle(payload, requirements, context?): Promise<SettleResponse>;
}
这是工程含量最高的一支:verify 不上链、只检查;settle 真的发交易。第 3 章会逐行看 EVM 的实现。
服务器侧:SchemeNetworkServer —— 把价钱翻译成报价
// core/src/types/mechanisms.ts:187-256 — SchemeNetworkServer(节选)
export interface SchemeNetworkServer {
readonly scheme: string;
parsePrice(price: Price, network): Promise<AssetAmount>; // "$0.10" → {amount:"100000", asset:"0x.."}
enhancePaymentRequirements(base, supportedKind, facilitatorExtensions): Promise<PaymentRequirements>;
getAssetDecimals?(asset, network): number; // 缺省 6(USDC)
enrichPaymentRequiredResponse?: SchemeEnrichPaymentRequiredResponseHook;
// ...各种生命周期 enrich hook
}
它的核心活是 parsePrice:把人类写的 "$0.10" 换成链上的原子单位 + 代币地址(注释里就给了例子,mechanisms.ts:200-208)。这就是为什么服务端配置可以写 price: "$0.001" 而不用手算 USDC 的 6 位小数。
三个接口,一个共同点: 它们都只接受/返回 Types 层的通用结构(PaymentRequirements/PaymentPayload/...),所以编排器可以完全不懂链就把它们串起来。
2.3 三个编排器:注册表 + 路由 + 生命周期
编排器是"链无关的胶水"。它们都长一个样:内部维护一张 Map(按版本/网络/方案三级索引),register() 往里塞 scheme 实现,请 求来了按 (version, network, scheme) 查表路由。
x402Client —— 选报价 + 造付款
核心方法 createPaymentPayload(core/src/client/x402Client.ts:385)。它的工作流分三步,注释写得很清楚(x402Client.ts:582-592):
selectPaymentRequirements 流程(client/x402Client.ts:593)
① 按已注册的 scheme 过滤 accepts[] ← 只留"我支持的网络+方案"
(filter,:600)
② 依次跑所有 policy 过滤/变换 ← 用户自定义偏好,如"只要便宜的/只要某条链"
(:621)
③ selector 选最终一项 ← 默认选第一个:accepts[0](:207)
选定后,按 (version, scheme, network) 找到对应 SchemeNetworkClient,调它的 createPaymentPayload 拿到 payload,再裹上 resource/accepted/extensions 组装成完整 PaymentPayload(x402Client.ts:413-442)。
policy 这个设计值得记:它是一串"过滤/变换报价数组"的纯函数,注册后在选择前依次跑(registerPolicy,:275)。客户端用它表达"我只用 Base 链""我只接受 < $0.01 的报价"这类偏好——预算控制是客户端策略,不是协议的事(规范明确把 budget management 列为 out of scope)。
x402ResourceServer —— 造报价 + 委托验证/结算
这是最胖的一个类(core/src/server/x402ResourceServer.ts,1700+ 行),但骨架就几件事:
initialize()(:559):启动时向所有 facilitator 拉/supported,建立"哪个 facilitator 支持哪个 (version, network, scheme)"的索引。先到的 facilitator 优先(:599-603注释"gives precedence to earlier facilitators")。buildPaymentRequirements()(:676):调 scheme 的parsePrice+enhancePaymentRequirements把一条资源配置变成一个PaymentRequirements。verifyPayment()(:910)/settlePayment()(:1063):跑 before/after hook,按(version, network, scheme)找到 facilitator client,委托出去。
注意 verify/settle 不在这里上链——它们调的是 FacilitatorClient(默认是 HTTPFacilitatorClient,向 facilitator 的 /verify、/settle 端点发 HTTP)。资源服务器自己永远不碰私钥、不连 RPC。
x402Facilitator —— 按 scheme/network 路由到 mechanism
facilitator 端的编排器(core/src/facilitator/x402Facilitator.ts)。它 register(networks, facilitator) 收下一个个 SchemeNetworkFacilitator,verify/settle 来了就按 scheme 名 + 网络匹配找对应实现(x402Facilitator.ts:312-326)。
匹配里有个巧思——支持网络通配。注册多个同族网络时会派生出 eip155:* 这样的 pattern(derivePattern,:540),匹配时先试精确、再试 pattern(:316-324)。所以一个 EVM facilitator 注册一次就能服务所有 eip155:* 链。getSupported()(:219)把所有注册的 kind 摊平成数组、并按 caipFamily 把签名者地址归类,正是规范 §7.3 那个 /supported 响应。
2.4 横切关注点:hook 与 extension
三个编排器都内建了生命周期 hook(before/after verify/settle、payment creation 等)和**extension(扩展)**机制。这部分代码量很大但思想简单:
- hook 是"在某个生命周期点插一脚"的回调,可以 abort(中止)或 recover(兜底)。比如
onBeforeVerify返回{abort:true, reason}就直接判付款无效。收集 hook 的顺序统一是"手动注册的 → 选中 scheme 自带的 → 在用的 extension 的"(getLabeledHooks,x402ResourceServer.ts:1556)。 - extension 是协议级的可选模块(如 Bazaar 发现、EIP-2612 gas 赞助)。服务器在
PaymentRequired.extensions里声明,客户端在PaymentPayload.extensions里回显。
这里有个安全要点值得单独点出:extension 回显时,客户端"只能追加、不能删改"服务器声明的字段。服务器侧 validateExtensions(x402ResourceServer.ts:1267)会逐键比对,echo 的内容必须包含服务器原始 advertised 的全部字段,否则判 extension_echo_mismatch。同理报价里 payTo 等关键字段在 extension enrich 时是"非空即不可变"(ResourceConfig.payTo 注释,:36-46;assertAcceptsAllowlistedAfterExtensionEnrich)——防止扩展把钱改道到别的地址。
下一章下钻到 Logic 层最经典的一支:exact 方案在 EVM 上到底怎么签、怎么验、怎么扣钱。