基于 EIP-3009 与 Permit2 的 EVM 支付实现:深入解读 @x402/evm 包
【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402
@x402/evm是 x402 支付协议在 EVM(以太坊虚拟机)链上的官方实现,采用Exact支付方案,核心基于 EIP-3009TransferWithAuthorization签名授权,并支持以 Permit2 作为通用兜底。本文以 typescript/packages/mechanisms/evm/README.md 为主体,结合仓库源码与测试,完整梳理该包的客户端(Client)、Facilitator、资源服务器(Server)三种角色、V1/V2 协议差异、注册与 RPC 配置方式、签名者抽象、资产转移方法与支持网络清单,帮助你直接把这套"免 gas 的 HTTP 支付"能力接入自己的 TypeScript 应用。
包定位与三大组件
@x402/evm的目标是:在 EVM 兼容链上,让买家(客户端)通过链下签名完成支付授权,让 Facilitator(支付处理方)负责链上验证与结算,让资源服务器(Service)构建支付需求(Payment Requirements),从而把传统 HTTP 资源访问改造成"先支付、后访问"的按次计费接口。
从源码结构看,该包提供三类角色对应的实现,三者共享同一套 EVM 签名与 EIP-712 基础设施:
- Client(付款方):为需要发起支付的应用程序设计,持有钱包/签名者,负责根据服务端返回的支付需求生成 EIP-3009 或 Permit2 支付载荷。对应实现见 client/scheme.ts 中的
ExactEvmScheme。 - Facilitator(支付处理方):为验证并执行链上交易的支付处理器设计,负责
verify(离线验证签名与需求)与settle(链上结算)。对应实现见 facilitator/scheme.ts。 - Service(资源服务器):为接受支付并构建支付需求的资源服务器设计,负责生成
PaymentRequirements并校验支付结果。
命名说明:README 中习惯称
ExactEvmClient/ExactEvmFacilitator/ExactEvmServer,而当前源码中三个角色的实际类名统一为ExactEvmScheme(分别实现SchemeNetworkClient/SchemeNetworkFacilitator接口),通过不同的注册入口暴露。
安装与包导出结构
在任意 TypeScript 项目中安装:
npm install @x402/evm该包依赖@x402/core(核心协议类型与客户端)、viem(EVM 交互)与zod(载荷校验),依赖声明可查看 package.json。
包的导出被划分为多个子路径,便于按需引入:
| 导出路径 | 内容 |
|---|---|
@x402/evm | V2 协议(CAIP-2 网络标识):ExactEvmScheme、toClientEvmSigner、toFacilitatorEvmSigner、ClientEvmSigner/FacilitatorEvmSigner类型、Permit2 辅助函数(createPermit2ApprovalTx、getPermit2AllowanceReadParams)、EIP-3009/Permit2 载荷类型与常量 |
@x402/evm/v1 | V1 协议(简单网络名):ExactEvmSchemeV1与NETWORKS常量 |
@x402/evm/exact/client | 客户端专用导出:ExactEvmScheme、registerExactEvmScheme、EvmClientConfig类型 |
@x402/evm/exact/facilitator | Facilitator 专用导出:ExactEvmScheme、registerExactEvmScheme、EvmFacilitatorConfig类型 |
@x402/evm/exact/server | 服务器专用导出:ExactEvmScheme、registerExactEvmScheme、EvmResourceServerConfig类型 |
@x402/evm/exact/v1/client、@x402/evm/exact/v1/facilitator | V1 客户端与 Facilitator 单独入口 |
@x402/evm/upto/* | Upto 方案(按需支付)的 client/server/facilitator 实现 |
主入口 src/index.ts 还会额外导出PERMIT2_ADDRESS、x402ExactPermit2ProxyAddress、x402UptoPermit2ProxyAddress、permit2WitnessTypes、eip3009ABI等链上常量,以及isPermit2Payload/isEIP3009Payload等类型守卫。
V2 与 V1 协议差异
该包同时维护 V2(现代 x402 协议,CAIP-2 网络标识)与 V1(旧协议,简单网络名)两套实现,差异对照如下:
| 维度 | V2(主包) | V1(@x402/evm/v1) |
|---|---|---|
| 网络格式 | CAIP-2,如eip155:8453 | 简单名称,如base-sepolia |
| 通配符支持 | 支持,如eip155:* | 不支持,固定网络列表 |
| 载荷结构 | 部分载荷(核心层包裹元数据) | 完整载荷 |
| 扩展支持 | 完整支持 | 有限支持 |
| 默认有效期 | 1 小时 | 10 分钟(含缓冲) |
V1 支持网络由 src/v1/index.ts 中的EVM_NETWORK_CHAIN_ID_MAP定义(即NETWORKS常量的来源):
[ "ethereum", "sepolia", "abstract", "abstract-testnet", "base-sepolia", "base", "avalanche-fuji", "avalanche", "iotex", "sei", "sei-testnet", "polygon", "polygon-amoy", "peaq", "story", "educhain", "skale-base-sepolia", "megaeth", "monad", "stable", "stable-testnet" ]注意:README 中给出的 V1 网络清单是核心列表;当前源码
EVM_NETWORK_CHAIN_ID_MAP还额外包含了ethereum、sepolia、megaeth、monad、stable、stable-testnet,并以getEvmChainIdV1(network)提供"网络名 → 链 ID"的映射能力,遇到未知网络名会抛出Unsupported v1 network错误。
用法一:直接注册(全控制)
最直接的方式是显式注册 scheme 到x402Client实例。README 示例中同时注册了 V2 通配符与 V1 网络:
import { x402Client } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; import { ExactEvmSchemeV1 } from "@x402/evm/v1"; const client = new x402Client() .register("eip155:*", new ExactEvmScheme(signer)) .registerSchemeV1("base-sepolia", new ExactEvmSchemeV1(signer)) .registerSchemeV1("base", new ExactEvmSchemeV1(signer));对于希望"一步到位"的场景,可以使用registerExactEvmScheme(见 client/register.ts),它一次性完成:
- 注册 V2 scheme:未传
networks时注册eip155:*通配符,否则逐网络注册; - 注册全部 V1 网络:遍历
NETWORKS常量,为每个网络注册ExactEvmSchemeV1; - 应用客户端策略(
policies)。
其配置对象EvmClientConfig支持字段:
signer(必填):用于创建支付载荷的 EVM 签名者;paymentRequirementsSelector:可选的支付需求选择函数,缺省使用默认选择器(取第一个可用选项);policies:可选PaymentPolicy[];schemeOptions:可选 RPC 配置,支持单配置{ rpcUrl }或按链 ID 键控的多配置{ 8453: { rpcUrl } };networks:可选,指定要注册的网络,缺省注册eip155:*。
import { registerExactEvmScheme } from "@x402/evm/exact/client/register"; import { x402Client } from "@x402/core/client"; import { privateKeyToAccount } from "viem/accounts"; const account = privateKeyToAccount("0x..."); const client = new x402Client(); registerExactEvmScheme(client, { signer: account });Extension RPC 配置(可选)
ExactEvmScheme的基础流程只需要签名者支持address+signTypedData。当服务端声明了EIP-2612 / ERC-20 Approval 的 gas sponsoring 扩展时,客户端还需要链上读取与交易签名能力(readContract、getTransactionCount、estimateFeesPerGas、signTransaction)。这些能力可以显式通过 RPC URL 配置补齐——SDK不会应用任何"链默认 RPC"兜底:
// 按网络显式注册 const client = new x402Client() .register("eip155:137", new ExactEvmScheme(signer, { rpcUrl: polygonRpcUrl })) .register("eip155:8453", new ExactEvmScheme(signer, { rpcUrl: baseRpcUrl })); // 通配符注册 + 按链 ID 键控的配置映射 const wildcardClient = new x402Client().register( "eip155:*", new ExactEvmScheme(signer, { 137: { rpcUrl: polygonRpcUrl }, 8453: { rpcUrl: baseRpcUrl }, }), );底层逻辑在 shared/rpc.ts 中:
EvmSchemeOptions是"扁平配置{ rpcUrl }"与"按链 ID 键控映射Record<number, { rpcUrl }>"的联合类型,isConfigByChainId通过"所有键均为数字"判断属于哪种形态;resolveRpcUrl(network, options)从 CAIP-2 网络标识解析出链 ID(getEvmChainId),再查表得到对应 RPC URL;resolveExtensionRpcCapabilities(network, signer, options)会在签名者缺少readContract/getTransactionCount/estimateFeesPerGas时,用createPublicClient创建的 viem 公共客户端按需回填这些能力(RPC 客户端按 URL 缓存复用)。
用法二:基于 Config(更灵活)
不逐个手写注册时,可以直接从配置对象构建客户端:
import { x402Client } from "@x402/core/client"; import { ExactEvmScheme } from "@x402/evm"; const client = x402Client.fromConfig({ schemes: [ { network: "eip155:*", client: new ExactEvmScheme(signer) }, { network: "base-sepolia", client: new ExactEvmSchemeV1(signer), x402Version: 1 } ], policies: [myCustomPolicy] });签名者抽象:ClientEvmSigner 与 FacilitatorEvmSigner
客户端与 Facilitator 对签名者的能力要求不同,定义集中在 src/signer.ts:
ClientEvmSigner(付款方)核心能力:
address:签名者地址;signTypedData(message):签署 EIP-712 结构化数据(基础流程唯一必需的能力);- 可选
readContract:链上读取,仅扩展富化(EIP-2612 / ERC-20 Approval)需要; - 可选
signTransaction/getTransactionCount/estimateFeesPerGas:ERC-20 Approval gas sponsoring 需要。
典型构造方式是用 viem 的WalletClient扩展publicActions,或通过toClientEvmSigner(account, publicClient)组合:
import { createWalletClient, http } from "viem"; import { privateKeyToAccount } from "viem/accounts"; import { baseSepolia } from "viem/chains"; import { publicActions } from "viem/actions"; const client = createWalletClient({ account: privateKeyToAccount('0x...'), chain: baseSepolia, transport: http(), }).extend(publicActions);toClientEvmSigner(signer, publicClient)会把签名者自身的可选能力与 publicClient 的能力合并:优先取签名者自带实现,缺失时回退到 publicClient 的readContract/getTransactionCount/estimateFeesPerGas。
FacilitatorEvmSigner(结算方)要求更完整:getAddresses()(支持多地址轮换、密钥轮换与高可用)、readContract、verifyTypedData、writeContract、sendTransaction、waitForTransactionReceipt、getCode。toFacilitatorEvmSigner(wallet)会将单地址钱包包装为getAddresses() => [address]以兼容多地址接口。
客户端如何路由:EIP-3009 还是 Permit2
ExactEvmScheme.createPaymentPayload(见 client/scheme.ts)依据支付需求中的requirements.extra.assetTransferMethod决定走哪条路径,缺省回退到"eip3009"(兼容旧版 Facilitator):
eip3009:直接生成 EIP-3009 载荷(createEIP3009Payload)。permit2:先生成 Permit2 载荷,再按优先级尝试附加扩展:- 若服务端声明
eip2612GasSponsoring且签名者支持readContract,当 Permit2 授权额度不足时,自动签署 EIP-2612 permit(trySignEip2612PermitExtension),实现"免 gas 的首次授权"; - 否则尝试 ERC-20 Approval 扩展(
trySignErc20ApprovalExtension),为不支持 EIP-2612 的代币签署approve交易; - 都不适用时直接返回 Permit2 载荷。
- 若服务端声明
Facilitator 侧则通过isPermit2Payload类型守卫区分两种载荷,分别路由到verifyPermit2/settlePermit2或verifyEIP3009/settleEIP3009(见 facilitator/scheme.ts)。
Facilitator 构造时还支持两个开关(均默认false):
deployERC4337WithEIP6492:遇到未部署合约账户(如 ERC-4337 智能钱包)的签名时,通过 EIP-6492 部署并验证;simulateInSettle:结算的二次校验阶段是否执行链上模拟。
链上结算:Permit2 代理合约
Permit2 路径的链上结算由 x402 自部署的代理合约完成,相关常量见 src/constants.ts:
PERMIT2_ADDRESS = 0x000000000022D473030F116dDEE9F6B43aC78BA3:Uniswap Permit2 的规范地址,所有 EVM 链通过 CREATE2 部署为同一地址;x402ExactPermit2ProxyAddress = 0x402085c248EeA27D92E8b30b2C58ed07f9E20001:Exact 方案代理,vanity 地址0x4020...0001,基于 Arachnid 确定性部署器与 vanity-miner 挖掘的盐(前缀0x4020、后缀0001)确定性部署;x402UptoPermit2ProxyAddress = 0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002:Upto 方案代理(0x4020...0002)。
EIP-712 结构化类型同样定义在 constants 中。Exact 方案的 Permit2 witness 为Witness(address to, uint256 validAfter),且类型必须按字母序排列(TokenPermissions < Witness)以匹配 Permit2 合约的 typehash 计算;Upto 方案的 witness 额外包含facilitator字段,确保只有被授权的 Facilitator 才能结算。
EIP-3009 侧,eip3009ABI提供了transferWithAuthorization(含v,r,s与 bytes 签名两种重载)、balanceOf、version、name、authorizationState等接口定义,authorizationTypes定义了TransferWithAuthorization的 EIP-712 字段(from、to、value、validAfter、validBefore、nonce)。
资产转移方式与默认资产
该包支持两种资产转移方法(详见 docs/core-concepts/network-and-token-support.mdx):
- EIP-3009:适用于原生支持
transferWithAuthorization()的代币(如 USDC、EURC)。最简单、真正免 gas——一次链下签名即可完成授权,无需前置授权步骤,是首选路径。 - Permit2:适用于任意 ERC-20 代币。作为通用兜底,需要一次性链上授权(可通过上文提到的 EIP-2612 / ERC-20 Approval gas sponsoring 扩展把这次授权也做成免 gas)。
两种方式都由 Facilitator 赞助 gas、以签名授权、保证支付安全。
当服务器使用美元字符串定价(如"$0.01")时,需要知道使用哪个稳定币。默认资产表在 shared/defaultAssets.ts 的DEFAULT_STABLECOINS中按 CAIP-2 网络标识索引,例如:
eip155:8453(Base 主网):USDC0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913,EIP-712 name"USD Coin"、version"2"、6 位小数;eip155:84532(Base Sepolia):USDC("USDC"/"2"/ 6 位小数);eip155:4326(MegaETH):MegaUSD("MegaUSD"/"1"/ 18 位小数)。
ExactDefaultAssetInfo还包含两个客户端行为提示字段:assetTransferMethod(对不支持 EIP-3009 的代币标记为"permit2")与supportsEip2612(标记代币是否实现 EIP-2612permit(),决定是否在extra中附带 name/version 供客户端签署免 gas permit)。当前默认资产链的完整清单与新增链的接入方式,参见 网络与代币支持文档。对于未配置默认资产的链,应使用registerMoneyParser()或直接以TokenAmount(amountInAtomicUnits+ 资产地址)指定价格。
支持的网络
V2 网络(CAIP-2 标识,eip155:<chainId>覆盖任意 EVM 链):
eip155:1— Ethereum 主网eip155:8453— Base 主网eip155:84532— Base Sepoliaeip155:*— 通配符,匹配所有 EVM 链- 任意
eip155:<chainId>网络
V1 网络(简单名称):见上文NETWORKS常量清单。
开发与测试命令
仓库内对该包提供了完整的开发脚本(见 package.json):
# 构建(tsup 打包 CJS + ESM + 类型声明) npm run build # 单元测试(vitest) npm run test # 集成测试(独立配置 vitest.integration.config.ts) npm run test:integration # Lint 与格式检查 npm run lint npm run format测试覆盖集中在 test 目录:unit/exact/下有针对客户端 RPC 回填、载荷创建与 Facilitator 验证/结算的测试,unit/v1/覆盖 V1 客户端与 Facilitator,integrations/exact-evm.test.ts则提供 Exact 方案的端到端集成验证。
相关包与进一步阅读
@x402/core:核心协议类型与客户端(x402Client、策略、支付需求选择器);@x402/fetch:带自动支付处理的 HTTP 封装;@x402/svm:Solana/SVM 实现;@x402/stellar:Stellar 实现;- 钱包端与服务器端完整接入流程,可参考 docs/getting-started/quickstart-for-buyers.mdx 与 docs/getting-started/quickstart-for-sellers.mdx;
- 链上结算合约本体位于 contracts/evm/src/x402ExactPermit2Proxy.sol,其测试见 contracts/evm/test/x402ExactPermit2Proxy.t.sol,可作为理解
settle/settleWithPermit语义的补充材料。
【免费下载链接】x402A payments protocol for the internet. Built on HTTP.项目地址: https://gitcode.com/GitHub_Trending/x4/x402
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考