> ## Documentation Index
> Fetch the complete documentation index at: https://docs.monad.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# 如何设置支持 Monad 的 x402 启用端点

本指南演示如何使用 x402 支付和 Monad 的 facilitator 设置可付费的端点。它可在 Monad 测试网/主网上运行。

## 什么是 x402？

x402 是让 HTTP 402 "Payment Required" 状态码重新焕发生机，成为一个用于互联网原生微支付的极简协议。

x402 不再需要订阅或要求账户的付费墙，而是让任何 HTTP 端点即刻可付费：

1. 客户端请求一个资源
2. 服务器以 402 响应，并附带一个小的 JSON 支付要求
3. 客户端签署一个支付授权并重新发送请求
4. 服务器验证并提供内容

### 超越传统限制

x402 是为现代互联网经济而设计，解决了传统系统的关键限制：

* **降低手续费和摩擦：** 无需中介、高额手续费或手动设置的直接链上支付。
* **微支付与基于用量的计费：** 按调用或功能收费，简单可编程的按用付费流程。
* **机器对机器交易：** 让 AI 代理自主付费和访问服务，无需密钥或人类介入。

## 为什么在 Monad 上使用 x402？

Monad 是一个完全兼容 EVM 的 Layer 1，具有：

* 10,000 TPS
* 约 0.3 秒的出块时间
* 单槽最终性
* 并行执行
* 极低的手续费

这些特性使 Monad 成为**真正的微支付和代理间商务**的理想环境。支付**以极低成本立即结算，并避免 mempool 拥堵**，非常适合大量 AI 代理按 API 调用付费的场景。

### 核心流程（直接支付）

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Server
    participant Monad Chain

    Client->>Server: GET /premium-content
    Server-->>Client: 402 Payment Required + requirements JSON
    Note over Client: Sign authorization<br/>(local, no tx)
    Client->>Server: GET /premium-content<br/>(PAYMENT-SIGNATURE header)
    Server->>Monad Chain: transferWithAuthorization()
    Monad Chain-->>Server: tx confirmed
    Server-->>Client: 200 OK + content
```

### Facilitator 流程（生产环境推荐）

facilitator 服务是可选的，但在生产环境中推荐使用。Facilitator 可以批量处理交易、承担 gas 费、处理退款，并简化客户端逻辑。

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Server
    participant Facilitator
    participant Monad Chain

    Note over Facilitator: Facilitator is optional but solves gas,<br/>refunds, batching, and replay protection

    Client->>Server: GET /premium-content
    Server-->>Client: 402 + requirements
    Note over Client: Sign authorization<br/>(local, no tx)
    Client->>Server: GET /premium-content<br/>(PAYMENT-SIGNATURE header)
    Server->>Facilitator: POST /verify
    Facilitator-->>Server: { isValid: true }
    Server-->>Client: 200 + resource
    Server->>Facilitator: POST /settle
    Facilitator->>Monad Chain: transferWithAuthorization()
    Monad Chain-->>Facilitator: tx confirmed
    Facilitator-->>Server: { success, txHash }
```

## 使用 Monad x402 facilitator 构建基于 x402 的应用

### 前置条件

* Node.js 18+
* 一个 EVM 钱包
* 访问 Monad 测试网资金（下方的 USDC 测试代币）

<Note>
  Monad Facilitator 仅支持 x402 v2 及以上版本。

  迁移指南说明了差异：[https://docs.x402.org/guides/migration-v1-to-v2](https://docs.x402.org/guides/migration-v1-to-v2)
</Note>

<Accordion title="如何在 Monad 测试网上获取 USDC 代币">
  您可以使用 Circle 的水龙头为 Monad 测试网获取 USDC 代币：

  1. 访问 [https://faucet.circle.com](https://faucet.circle.com)
  2. 选择 **USDC** 作为代币
  3. 从 Network 下拉菜单中选择 **Monad Testnet**
  4. 输入您的钱包地址
  5. 点击 **Send 1 USDC**

  **限制：** 每对（稳定币，测试网）每 2 小时一次请求

  <img src="https://mintcdn.com/monadfoundation-40611fb6/5Mt9_Scj9fq4fC68/static/img/guides/x402-guide/3.png?fit=max&auto=format&n=5Mt9_Scj9fq4fC68&q=85&s=156c17282ff91da33d7031a964726717" alt="circle_faucet" width="2400" height="1358" data-path="static/img/guides/x402-guide/3.png" />

  您还需要测试网 MON 代币用于 gas 费。请从 [Monad 水龙头](https://faucet.monad.xyz) 获取。
</Accordion>

### 步骤 1：初始化一个 Next.js 应用

创建一个新的 Next.js 项目：

```bash theme={null}
npx create-next-app@latest my-x402-app
```

提示时选择以下选项：

* ✅ TypeScript
* ✅ ESLint
* ✅ Tailwind CSS
* ✅ `src/` 目录
* ✅ App Router
* ✅ 自定义默认导入别名：`@/*`（默认）

进入您的项目：

```bash theme={null}
cd my-x402-app
```

安装 x402 相关包：

<Note>
  使用 `@x402/evm >= 2.22.0`。它自带内置的 Monad 主网 USDC 配置，具有正确的 EIP-712 域名，并引用了正确的 upto 代理。详情参见[支持的支付方案](#supported-payment-schemes)。
</Note>

```bash theme={null}
npm install @x402/core @x402/evm @x402/fetch @x402/next
```

为您的环境变量创建 `.env.local` 文件：

```bash theme={null}
touch .env.local
```

### 步骤 2：创建 `payTo` 地址

`payTo` 地址用于接收支付并从后端与区块链交互。

复制钱包地址并将其作为 `PAY_TO_ADDRESS` 添加到您的 `.env.local` 文件中

```text theme={null}
PAY_TO_ADDRESS=0xYourWallet
```

### 步骤 3：创建服务端可付费端点

```ts title="src/app/api/premium/route.ts" lines theme={null}
import { NextResponse } from "next/server";
import { withX402, type RouteConfig } from "@x402/next";
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { ExactEvmScheme } from "@x402/evm/exact/server";
import type { Network } from "@x402/core/types";

// Monad Testnet configuration
const MONAD_NETWORK: Network = "eip155:10143";
const MONAD_USDC_TESTNET = "0x534b2f3A21130d7a60830c2Df862319e593943A3";

// Monad Facilitator URL
const FACILITATOR_URL = "https://x402-facilitator.molandak.org"; 

if (!process.env.PAY_TO_ADDRESS) {
  throw new Error("PAY_TO_ADDRESS environment variable is required");
}
const PAY_TO = process.env.PAY_TO_ADDRESS;

// Create facilitator client for Monad
const facilitatorClient = new HTTPFacilitatorClient({ url: FACILITATOR_URL });

// Create and configure x402 resource server
const server = new x402ResourceServer(facilitatorClient);

// Monad testnet USDC is not in @x402/evm's built-in asset table, so register
// it here. Mainnet (eip155:143) is built in since 2.22.0 and needs no parser.
const monadScheme = new ExactEvmScheme();
monadScheme.registerMoneyParser(async (amount: number, network: string) => {
  if (network === MONAD_NETWORK) {
    // Convert decimal amount to USDC smallest units (6 decimals).
    // `Math.floor(amount * 1_000_000)` is safe up to ~9 billion USDC
    // (~9 quadrillion atomic units) — bounded by JS Number's 2^53 precision.
    // For tokens with 18 decimals or very large balances, use `BigInt` to avoid
    // precision loss, e.g.:
    //   BigInt(Math.round(amount * 1e6)).toString()   // USDC, 6 decimals
    //   (BigInt(Math.round(amount * 1e9)) * 1_000_000_000n).toString()  // 18d, ≤9 fractional digits
    const tokenAmount = Math.floor(amount * 1_000_000).toString();
    return {
      amount: tokenAmount,
      asset: MONAD_USDC_TESTNET, // Raw address for EIP-712 verifyingContract
      extra: {
        name: "USDC",
        version: "2",
      },
    };
  }
  return null; // Use default parser for other networks
});

// Register Monad network with custom scheme
server.register(MONAD_NETWORK, monadScheme);

// Route configuration
const routeConfig: RouteConfig = {
  accepts: {
    scheme: "exact",
    network: MONAD_NETWORK,
    payTo: PAY_TO,
    price: "$0.001",
  },
  resource: "http://localhost:3000/api/premium", // Use relative path to avoid host mismatch
};

// Handler that returns full article content
async function handler(request: NextRequest) {
  return NextResponse.json({
    content: "Return premium content",
    unlockedAt: new Date().toISOString(),
  });
}

// Export GET method wrapped with x402 payment protection
export const GET = withX402(handler, routeConfig, server);
```

### 步骤 4：客户端设置（消费付费端点）

以下是使用 Next.js 应用消费付费端点的示例，然而该端点也可以通过代理脚本消费。

```tsx title="src/app/page.tsx" lines theme={null}
"use client";

import { useState, useCallback, useEffect } from "react";
import { useAccount, useWalletClient } from "wagmi";
import { wrapFetchWithPayment } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm";
import { x402Client } from "@x402/core/client";

// x402 configuration
const x402Config = {
  chainId: "eip155:10143" as const,
  usdcAddress: "0x534b2f3A21130d7a60830c2Df862319e593943A3", // MONAD USDC TESTNET
  facilitator: "https://x402-facilitator.molandak.org", // MONAD FACILITATOR URL
  price: "0.001", // USDC
};

export default function Home() {
  const { isConnected, address } = useAccount();
  const { data: walletClient } = useWalletClient();
  const [message, setMessage] = useState("Pay $0.001 USDC to unlock premium content");
  const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle");

  // This function allows signing a message, and pay USDC gaslessly. 
  const handleUnlock = useCallback(async () => {
    if (!walletClient || !address) {
      setError("Please connect your wallet first");
      return;
    }

    setIsLoading(true);
    setError(null);

    try {
      // Create EVM signer compatible with x402 ClientEvmSigner interface
      const evmSigner = {
        address: address as `0x${string}`,
        signTypedData: async (message: {
          domain: Record<string, unknown>;
          types: Record<string, unknown>;
          primaryType: string;
          message: Record<string, unknown>;
        }) => {
          return walletClient.signTypedData({
            domain: message.domain as Parameters<typeof walletClient.signTypedData>[0]["domain"],
            types: message.types as Parameters<typeof walletClient.signTypedData>[0]["types"],
            primaryType: message.primaryType,
            message: message.message,
          });
        },
      };

      // Create the Exact EVM scheme for signing
      const exactScheme = new ExactEvmScheme(evmSigner);

      // Create x402 client and register the scheme
      const client = new x402Client()
        .register(x402Config.chainId, exactScheme);

      console.log("x402 client configured for network:", x402Config.chainId);

      // Wrap fetch with x402 payment capability
      const paymentFetch = wrapFetchWithPayment(fetch, client);

      console.log("Making payment request to /api/article...");

      // Make request to protected endpoint
      const response = await paymentFetch("/api/premium", {
        method: "GET",
        headers: {
          "Content-Type": "application/json",
        },
      });

      if (!response.ok) {
        // Try to parse x402 payment-required header for detailed error
        const paymentHeader = response.headers.get("payment-required");

        if (paymentHeader && response.status === 402) {
          try {
            const paymentData = JSON.parse(atob(paymentHeader));
            console.error("Payment error details:", paymentData);

            // Extract user-friendly error message
            if (paymentData.error?.includes("insufficient_funds")) {
              throw new Error("INSUFFICIENT_FUNDS");
            }
            if (paymentData.error?.includes("unexpected_error")) {
              throw new Error("UNEXPECTED_ERROR");
            }
            if (paymentData.error) {
              throw new Error(paymentData.error);
            }
          } catch (e) {
            if (e instanceof Error && e.message === "INSUFFICIENT_FUNDS") {
              throw e;
            }
            // Failed to parse header, continue to generic error
          }
        }

        const errorText = await response.text().catch(() => "");
        let errorData: Record<string, unknown> = {};
        try {
          errorData = JSON.parse(errorText);
        } catch {
          // Not JSON
        }
        throw new Error(
          errorData.error as string ||
          errorData.details as string ||
          `Request failed: ${response.status}`
        );
      }

      const data = await response.json();

      // Cache the unlocked content in LocalStorage
      localStorage.setItem(
        "premimum_content_unlocked",
        JSON.stringify({
          content: data.content,
          timestamp: Date.now(),
        })
      );
    } catch (err) {
      console.error("Unlock error:", err);
      const message = err instanceof Error ? err.message : "Failed to unlock article";

      // Map technical errors to user-friendly messages
      if (
        message.includes("User rejected") ||
        message.includes("User denied") ||
        message.includes("user rejected")
      ) {
        setError("CANCELLED");
      } else if (message === "INSUFFICIENT_FUNDS" || message.includes("insufficient_funds")) {
        setError("INSUFFICIENT_FUNDS");
      } else if (message === "UNEXPECTED_ERROR" || message.includes("unexpected_error")) {
        setError("UNEXPECTED_ERROR");
      } else {
        setError(message);
      }
    } finally {
      setIsLoading(false);
    }
  }, [walletClient, address]);

  return (
    <main className="min-h-screen bg-zinc-950 flex items-center justify-center p-6">
      <div className="max-w-md w-full space-y-6">
        <div className="text-center space-y-2">
          <h1 className="text-2xl font-bold text-white">x402 on Monad</h1>
          <p className="text-zinc-400 text-sm">
            Micropayments via the Monad facilitator.{" "}
            <a href="https://docs.monad.xyz/guides/x402-guide" className="text-violet-400 hover:underline">
              Docs
            </a>
          </p>
        </div>

        <button
          onClick={handleUnlock}
          disabled={status === "loading"}
          className="w-full py-3 px-4 bg-violet-600 hover:bg-violet-500 disabled:bg-violet-800 disabled:cursor-wait text-white font-medium rounded-lg transition-colors"
        >
          {status === "loading" ? "Processing..." : "Pay & Unlock Content"}
        </button>

        <div className={`p-4 rounded-lg text-sm ${
          status === "error" ? "bg-red-950 text-red-300" :
          status === "success" ? "bg-green-950 text-green-300" :
          "bg-zinc-900 text-zinc-300"
        }`}>
          {message}
        </div>
      </div>
    </main>
  );
}
```

## 运行您的 x402 应用

现在您已经准备好测试您的 x402 支付流程了：

1. 启动开发服务器：
   ```bash theme={null}
   npm run dev
   ```
2. 在浏览器中打开 [http://localhost:3000](http://localhost:3000)
3. 点击"Pay & Unlock Content"
4. 连接您的钱包
5. 批准 USDC 支付
6. 立即看到内容解锁！

## Facilitator API

对于希望使用基本 Facilitator API 的开发者，以下是支持的端点及示例。

Facilitator URL：`https://x402-facilitator.molandak.org` 网络支持：主网（`eip155:143`，USDC `0x754704Bc059F8C67012fEd69BC8A327a5aafb603`）和测试网（`eip155:10143`，USDC `0x534b2f3A21130d7a60830c2Df862319e593943A3`）。

在 `@x402/evm >= 2.22.0` 上，主网 USDC 从 SDK 的内置资产表中解析。测试网没有内置条目，需要上面快速入门中所示的自定义 money parser。

### 支持的支付方案

Monad facilitator 通过 `GET /supported` 公布两种 x402 v2 方案：

| Scheme ID         | 使用场景                        | 机制                                                                          |
| ----------------- | --------------------------- | --------------------------------------------------------------------------- |
| `v2-eip155-exact` | 固定价格支付（USDC 默认）             | 直接对代币使用 EIP-3009 `transferWithAuthorization`，或对非 EIP-3009 代币回退使用 Permit2 代理 |
| `v2-eip155-upto`  | 可变金额/计量支付（LLM tokens、带宽、算力） | 仅使用 Permit2 —— 客户端签署一个上限值，facilitator 按实际使用量 ≤ 上限进行结算。支持 \$0 结算（无需链上交易）     |

对于 Monad 上的 USDC，按照 x402 规范推荐使用 **exact** 方案。**Upto** 需要位于 `0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002` 的规范 `x402UptoPermit2Proxy`（参见[规范合约](/zh/developer-essentials/network-information#canonical-contracts)），以及由 facilitator 公布的 `extra.facilitatorAddress`，客户端必须将其绑定到 Permit2 witness 中。

<Warning>
  **对于 upto 方案，请使用 `@x402/evm >= 2.12.0`（推荐 `>= 2.22.0`）。** 2.9.0–2.11.0 版本包含 upto 模块，但引用的代理地址（`0x402039b3d6E6BEC5A02c2C9fd937ac17A6940002`）并未部署在 Monad 上。支付将在结算时失败，且没有明确的错误。正确的地址（`0x4020A4f3b7b90ccA423B9fabCc0CE57C6C240002`）最早出现在 2.12.0 中，2.22.0 引用相同的代理，并增加了带有正确 EIP-712 域名的内置 Monad 主网 USDC 配置。

  如果您将来升级到更新的版本，请在部署到生产环境之前确认它引用了相同的代理地址。
</Warning>

### 可能的 HTTP 状态码

除了标准的 `200` / `4xx` / `5xx` 代码外，facilitator 还可能返回：

* **`412 PRECONDITION_FAILED`** —— 当 Permit2 支付因用户对代理合约的 Permit2 授权额度不足而无法进行时返回（`PERMIT2_ALLOWANCE_REQUIRED`）。客户端必须通过 Permit2 授权代理并重试。这与 `400`（请求体格式错误）和 `402`（资源服务器的支付要求响应）不同。

### GET `/supported`

返回支持的网络、方案和签名者地址。

```ts theme={null}
const FACILITATOR_URL = "https://x402-facilitator.molandak.org";

async function testSupported(): Promise<void> {
  console.log("\n--- GET /supported ---");

  const response = await fetch(`${FACILITATOR_URL}/supported`);
  if (!response.ok) throw new Error(`Failed: ${response.status}`);

  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));
  return data;
}
```

### POST `/verify`

验证支付签名。

```ts theme={null}
interface NetworkConfig {
  chainId: Network;
  name: string;
  rpcUrl: string;
  explorerUrl: string;
  usdcAddress: `0x${string}`;
  usdcDecimals: number;
  chainIdNumber: number;
}

/**
 * This function manually constructs and signs the EIP-712 typed data that
 * the x402 client library handles automatically.
 */
async function testVerify(
  account: ReturnType<typeof privateKeyToAccount>,
  networkConfig: NetworkConfig
): Promise<{ isValid: boolean; payload: any }> {
  console.log("\n--- POST /verify ---");
  const now = Math.floor(Date.now() / 1000);
  const nonce = keccak256(toHex(Math.random().toString()));
  const ACCOUNT_ADDRESS = "0x0000000000000000000000000000000000000000";

  // TransferWithAuthorization parameters (ERC-3009)
  const authorization = {
    from: ACCOUNT_ADDRESS, // Account that is paying
    to: PAY_TO_ADDRESS, // Receiver address
    value: "1000", // 0.001 USDC (6 decimals)
    validAfter: (now - 60).toString(), // Start validity 60s in the past to handle clock skew
    validBefore: (now + 900).toString(), // 15 minutes
    nonce,
  };

  // EIP-712 domain - must match USDC contract's DOMAIN_SEPARATOR
  const domain = {
    name: "USDC",  // Monad USDC uses "USDC" (not "USD Coin")
    version: "2",
    chainId: BigInt(networkConfig.chainIdNumber),
    verifyingContract: networkConfig.usdcAddress,
  };

  // EIP-712 type definition for TransferWithAuthorization
  const types = {
    TransferWithAuthorization: [
      { name: "from", type: "address" },
      { name: "to", type: "address" },
      { name: "value", type: "uint256" },
      { name: "validAfter", type: "uint256" },
      { name: "validBefore", type: "uint256" },
      { name: "nonce", type: "bytes32" },
    ],
  };

  const message = {
    from: authorization.from,
    to: authorization.to,
    value: BigInt(authorization.value),
    validAfter: BigInt(authorization.validAfter),
    validBefore: BigInt(authorization.validBefore),
    nonce: authorization.nonce as `0x${string}`,
  };

  const signature = await account.signTypedData({
    domain,
    types,
    primaryType: "TransferWithAuthorization",
    message,
  });

  const requestBody = {
    x402Version: 2,
    payload: {
      authorization,
      signature,
    },
    // `resource` (and its `url` / `description` / `mimeType` fields) is OPTIONAL
    // in x402 v2; the facilitator only requires `payload` + `accepted`. Include it
    // when the resource server wants its identity recorded alongside the settlement.
    resource: {
      url: "http://test/resource",
      description: "Test resource",
      mimeType: "application/json",
    },
    accepted: {
      scheme: "exact",
      network: networkConfig.chainId,
      amount: authorization.value,
      asset: networkConfig.usdcAddress,
      payTo: authorization.to,
      maxTimeoutSeconds: 300,
      extra: {
        name: "USDC",
        version: "2",
      },
    },
  };

  const response = await fetch(`${FACILITATOR_URL}/verify`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(requestBody),
  });

  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));

  return { isValid: data.isValid, payload: requestBody };
}
```

### POST `/settle`

在链上执行支付。Facilitator 支付 gas 费。

```ts theme={null}
interface NetworkConfig {
  chainId: Network;
  name: string;
  rpcUrl: string;
  explorerUrl: string;
  usdcAddress: `0x${string}`;
  usdcDecimals: number;
  chainIdNumber: number;
}

/** POST /settle - Execute the payment on-chain. Facilitator pays gas. */
async function testSettle(
  payload: any,
  networkConfig: NetworkConfig
): Promise<void> {
  console.log("\n--- POST /settle ---");

  const response = await fetch(`${FACILITATOR_URL}/settle`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  const data = await response.json();
  console.log(JSON.stringify(data, null, 2));

  if (data.success && data.transaction) {
    // Transaction success
  } else if (data.errorReason) {
    console.log(`Failed: ${data.errorReason}`);
  }
}
```

## 下一步做什么？

您已成功在 Monad 上构建了一个启用 x402 支付的应用！以下是一些扩展您实现的想法：

* **添加更多可付费端点** - 为不同的内容或 API 调用创建不同的价格层
* **构建 AI 代理集成** - 使自主代理能够为访问您的 API 付费

## 资源

* [x402 协议规范](https://www.x402.org/)
* [Monad 开发者 Discord](https://discord.gg/monaddev)
* [迁移指南：V1 到 V2](https://docs.x402.org/guides/migration-v1-to-v2)

## 需要帮助？

如果您遇到问题或有疑问，请加入 [Monad 开发者 Discord](https://discord.gg/monaddev)

祝您构建愉快！
