Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

构建按次计费 API

本指南将指导您如何使用 x402 将 API 端点货币化。服务器添加支付处理程序,客户端按请求付费,并在 HTTP 生命周期内完成结算。

您将构建什么

一个付费 HTTP API,其中服务器以 402 Payment Required 响应,客户端按请求付费,协调器在 HTTP 生命周期内链上结算 USDT0。

演示

步骤 1. 客户端:GET /weather (未支付)
        服务器:402 Payment Required
                PAYMENT-REQUIRED: { amount: "1000", asset: USDT0, network: eip155:988 }

步骤 2. 客户端签署 ERC-3009 授权

步骤 3. 客户端:GET /weather + PAYMENT-SIGNATURE header
        服务器:转发给协调器 → transferWithAuthorization 在链上结算
                (~700ms 区块确认)
        服务器:200 OK { weather: "sunny", temperature: 70 }
                PAYMENT-SETTLE-RESPONSE: { txHash: "0x8f3a...", paid: "0.001 USDT0" }

步骤 4. 在 Stablescan 上验证结算
        https://stablescan.xyz/tx/0x8f3a...

概述

卖家(服务器):
// --- 服务器 ---
app.use(paymentMiddleware({
  "GET /weather": {
    price: { amount: "1000", asset: USDT0 },
    payTo: sellerAddress,
  },
  "POST /inference": {
    price: { amount: "50000", asset: USDT0 },
    payTo: sellerAddress,
  },
}, resourceServer));
 
// 配置中未列出的路由不受限制。
买家(客户端):
// --- 客户端 ---
account = new WalletAccountEvm(seedPhrase, { provider: RPC });
client = new x402Client();
fetchWithPayment = wrapFetchWithPayment(fetch, client);
 
weatherResponse = fetchWithPayment("https://api.example.com/weather");
inferenceResponse = fetchWithPayment("https://api.example.com/inference", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ prompt: "Hello" }),
});
 
// 对于每个付费请求:
// 1. 初始请求返回 402 和 PAYMENT-REQUIRED 头部
// 2. 客户端使用钱包签署 ERC-3009 授权
// 3. 客户端使用 PAYMENT-SIGNATURE 头部重试
// 4. 协调器在链上结算,服务器返回响应

卖家:设置付费端点

卖家添加 x402 中间件来定义哪些路由需要付费。当没有付款的请求到达时,中间件会以 402 Payment Required 和付款条款响应。当存在有效的付款头部时,中间件将其转发给协调器,协调器验证签名并在链上结算付款。卖家只需配置价格和收款地址;协调器处理验证和结算。

npm install express @x402/express @x402/evm @x402/core

定价

每个路由都指定以 USDT0 基本单位(6 位小数)计价的支付金额、网络和接收资金的地址。例如,"1000" 等于 $0.001"50000" 等于 $0.05

price: {
  amount: "1000",                                      // 基本单位 (6 位小数)
  asset: USDT0_STABLE,                                 // USDT0 合约地址
  extra: { name: "USDT0", version: "1", decimals: 6 }, // EIP-712 域信息
}

extra 字段(nameversiondecimals)由买方的客户端用于 EIP-712 签名构建,并且必须与链上 USDT0 合约匹配。

路由配置

路由使用 METHOD /path 格式进行映射。每个路由都指定接受的支付方案、网络、价格以及接收资金的地址 (payTo)。descriptionmimeType 字段帮助买家和 AI 代理发现端点提供的信息。配置中未列出的路由不受限制,其行为类似于正常的 Express 路由。

// server.ts
import express from "express";
import { paymentMiddleware, x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import { HTTPFacilitatorClient } from "@x402/core/server";
 
const PAY_TO = process.env.PAY_TO_ADDRESS as `0x${string}`;
const FACILITATOR_URL = "https://x402.semanticpay.io/";
const STABLE_NETWORK = "eip155:988"; // Stable 主网 CAIP-2 ID
const USDT0_STABLE = "0x779Ded0c9e1022225f8E0630b35a9b54bE713736";
 
const facilitatorClient = new HTTPFacilitatorClient({ url: FACILITATOR_URL });
const resourceServer = new x402ResourceServer(facilitatorClient)
  .register(STABLE_NETWORK, new ExactEvmScheme());
 
const app = express();
 
app.use(
  paymentMiddleware(
    {
      // 示例 1:配置一个付费 GET 路由
      "GET /weather": {
        accepts: [
          {
            scheme: "exact",
            network: STABLE_NETWORK,
            price: {
              amount: "1000", // $0.001
              asset: USDT0_STABLE,
              extra: { name: "USDT0", version: "1", decimals: 6 },
            },
            payTo: PAY_TO,
          },
        ],
        description: "天气数据",
        mimeType: "application/json",
      },
      // 示例 2:配置一个付费 POST 路由
      "POST /inference": {
        accepts: [
          {
            scheme: "exact",
            network: STABLE_NETWORK,
            price: {
              amount: "50000", // $0.05
              asset: USDT0_STABLE,
              extra: { name: "USDT0", version: "1", decimals: 6 },
            },
            payTo: PAY_TO,
          },
        ],
        description: "AI 推理端点",
        mimeType: "application/json",
      },
    },
    resourceServer,
  ),
);
 
app.get("/weather", (req, res) => {
  res.json({ weather: "sunny", temperature: 70 });
});
 
app.post("/inference", (req, res) => {
  const { prompt } = req.body;
  res.json({ result: `推理结果为: ${prompt}` });
});
 
// 未在配置中列出,因此无需支付。
app.get("/health", (req, res) => {
  res.json({ status: "ok", payTo: PAY_TO });
});
 
const PORT = process.env.PORT || 4021;
app.listen(PORT, () => {
  console.log(`服务器监听地址 http://localhost:${PORT}`);
  console.log(`GET  /health    - 免费`);
  console.log(`GET  /weather   - 每次请求 $0.001`);
  console.log(`POST /inference - 每次请求 $0.05`);
});

买家:发出付费请求

买家无需通过手动支付流程即可访问付费端点。买家无需支付 Gas 费。协调器在链上结算,买家只需支付支付要求中指定的精确金额。

npm install @x402/fetch @x402/evm @tetherto/wdk-wallet-evm

创建钱包并检查余额

// client.ts
import WalletManagerEvm from "@tetherto/wdk-wallet-evm";
 
const account = await new WalletManagerEvm(process.env.SEED_PHRASE!, {
  provider: "https://rpc.stable.xyz",
}).getAccount(0);
 
console.log("买家地址:", account.address);
 
// USDT0 使用 6 位小数。余额 1000000 等于 1.00 USDT0。
const USDT0_STABLE = "0x779Ded0c9e1022225f8E0630b35a9b54bE713736";
const balance = await account.getTokenBalance(USDT0_STABLE);
console.log("USDT0 余额:", Number(balance) / 1e6, "USDT0");

连接到 x402 并发出付费请求

WalletAccountEvm 满足 x402 所需的签名者接口,因此可以直接注册为 x402 客户端的签名者。注册后,通过启用 x402 的客户端发送的请求会自动处理 402 支付流程。

import { x402Client, wrapFetchWithPayment } from "@x402/fetch";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
 
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
 
const response = await fetchWithPayment("http://localhost:4021/weather");
const data = await response.json();
console.log("响应:", data);

在底层,fetchWithPayment 拦截 402 响应,解析支付要求(金额、代币、网络、接收方),使用 WDK 钱包签署 ERC-3009 transferWithAuthorization,并使用 PAYMENT-SIGNATURE 头部重试请求。

测试支付流程

启动服务器并验证付费和免费路由。

1. 确认 402 响应

curl -i http://localhost:4021/weather

响应应为 402 Payment Required,并带有包含价格、资产和网络的 PAYMENT-REQUIRED 头部。

2. 运行客户端

npx tsx client.ts

客户端处理整个周期:接收 402,签署授权,重试并支付,然后打印响应。

3. 读取收据

成功支付请求后,买方可以从服务器响应中读取 PAYMENT-SETTLE-RESPONSE 头部并解析结算收据。

// (续) client.ts
import { x402HTTPClient } from "@x402/fetch";
 
const httpClient = new x402HTTPClient(client);
const receipt = httpClient.getPaymentSettleResponse(
  (name) => response.headers.get(name),
);
console.log("支付收据:", JSON.stringify(receipt, null, 2));

不使用实时协调器进行测试

由于 Semantic 协调器仅限于主网,因此您目前无法将服务器指向测试网协调器。要在不结算实际支付的情况下迭代服务器逻辑、路由处理程序和中间件行为,请使用模拟协调器客户端。

// server.test.ts
import { x402ResourceServer } from "@x402/express";
import { ExactEvmScheme } from "@x402/evm/exact/server";
 
// 模拟协调器:接受任何签名,返回伪造的结算。
const stubFacilitatorClient = {
  verify: async () => ({ isValid: true, payer: "0xMockPayer" }),
  settle: async () => ({
    success: true,
    txHash: "0xMOCK000000000000000000000000000000000000000000000000000000000001",
    networkId: "eip155:988",
  }),
};
 
export const testResourceServer = new x402ResourceServer(stubFacilitatorClient as any)
  .register("eip155:988", new ExactEvmScheme());

针对模拟运行单元测试以验证:

  • 402 响应包含正确的 PAYMENT-REQUIRED 有效负载。
  • 带有有效 PAYMENT-SIGNATURE 头的请求到达处理程序。
  • 缺少或格式错误的头的请求在处理程序运行前被拒绝。

当您准备好进行真实结算时,请切换回 HTTPFacilitatorClient 并在主网上使用小额资金运行。

高级:生命周期钩子

x402 提供了钩子,可以在流程中的关键点拦截和自定义支付处理。例如,服务器可以在验证之前运行逻辑(例如,检查 API 密钥或订阅者状态)以绕过授权请求的支付,客户端可以在签名之前强制执行消费限制。

有关完整的钩子参考和示例,请参阅 x402 生命周期的钩子

接下来推荐