# x402 Payment Protocol

> Implement HTTP-native payments using the x402 protocol with EIP-3009

> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.

Source: https://dev.flare.network/fxrp/token-interactions/x402-payments

MockUSDT0 Token

This guide currently uses a **MockUSDT0** token for demonstration purposes. FXRP will be supported once it implements the required [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) standard (`transferWithAuthorization`). This guide will be updated when FXRP gains EIP-3009 support.

## Overview[​](#overview "Direct link to Overview")

The [x402 protocol](https://www.x402.org/) enables HTTP-native payments using the long-reserved HTTP 402 "Payment Required" status code. This guide demonstrates how to implement x402 payments on Flare using [EIP-3009](https://eips.ethereum.org/EIPS/eip-3009) for gasless payment settlements.

Key benefits of the x402 protocol:

-   **HTTP-native**: Payments are integrated directly into HTTP request/response flow.
-   **Gasless for payers**: Users sign authorizations off-chain; servers settle on-chain.
-   **Front-running protection**: EIP-3009's `receiveWithAuthorization` prevents transaction front-running.
-   **Idempotent**: Unique nonces ensure each payment can only be processed once.

## Architecture[​](#architecture "Direct link to Architecture")

The x402 implementation consists of four components:

1.  **MockUSDT0**: ERC-20 token with EIP-3009 support (`transferWithAuthorization`).
2.  **X402Facilitator**: Contract that verifies and settles EIP-3009 authorizations.
3.  **Server**: Express backend implementing the x402 payment flow.
4.  **Agent**: CLI tool for making automated payments.

## EIP-3009 Payment Flow[​](#eip-3009-payment-flow "Direct link to EIP-3009 Payment Flow")

The flow works as follows:

1.  Client requests a protected resource.
2.  Server returns HTTP 402 with payment requirements.
3.  Client signs an EIP-712 authorization (off-chain, no gas required).
4.  Client resends request with `X-Payment` header containing the signed authorization.
5.  Server calls `settlePayment()` on the Facilitator contract.
6.  Facilitator executes `transferWithAuthorization` to transfer tokens.
7.  Server returns the requested resource with payment confirmation.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

Before starting, ensure you have:

-   [Flare Hardhat Starter](https://github.com/flare-foundation/flare-hardhat-starter) cloned.
-   C2FLR tokens for gas on Coston2 testnet.
-   Private key configured in `.env`.

## Step 1: Deploy Contracts[​](#step-1-deploy-contracts "Direct link to Step 1: Deploy Contracts")

Deploy the MockUSDT0 token and X402Facilitator contracts.

```
yarn hardhat run scripts/x402/deploy.ts --network coston2
```

The deployment script outputs contract addresses. Add them to your `.env` file:

```
X402_TOKEN_ADDRESS=0x...X402_FACILITATOR_ADDRESS=0x...X402_PAYEE_ADDRESS=0x...
```

<details>
<summary>View deployment script</summary>

View deployment script

scripts/x402/deploy.ts

```
/** * x402 Demo Deployment Script * * Deploys MockUSDT0 and X402Facilitator contracts. * * Usage: * yarn hardhat run scripts/x402/deploy.ts --network coston2 */import { run, web3, artifacts } from "hardhat";const MockUSDT0 = artifacts.require("MockUSDT0");const X402Facilitator = artifacts.require("X402Facilitator");async function deployAndVerify() {  const [deployer] = await web3.eth.getAccounts();  console.log("═".repeat(60));  console.log("x402 Demo Deployment");  console.log("═".repeat(60));  console.log(`Deployer: ${deployer}`);  const balance = await web3.eth.getBalance(deployer);  console.log(`Balance:  ${web3.utils.fromWei(balance, "ether")} C2FLR`);  console.log("─".repeat(60));  // Deploy MockUSDT0  console.log("\n📦 Deploying MockUSDT0...");  const mockUSDT0 = await MockUSDT0.new();  console.log(`   MockUSDT0 deployed to: ${mockUSDT0.address}`);  // Get token info  const name = await mockUSDT0.name();  const symbol = await mockUSDT0.symbol();  const decimals = await mockUSDT0.decimals();  const totalSupply = await mockUSDT0.totalSupply();  console.log(`   Name: ${name}`);  console.log(`   Symbol: ${symbol}`);  console.log(`   Decimals: ${decimals}`);  console.log(    `   Initial Supply: ${web3.utils.fromWei(totalSupply.toString(), "mwei")}`,  );  // Deploy X402Facilitator  console.log("\n📦 Deploying X402Facilitator...");  const feeBps = 0; // No fees for demo  const facilitatorArgs = [deployer, feeBps];  const facilitator = await X402Facilitator.new(deployer, feeBps);  console.log(`   X402Facilitator deployed to: ${facilitator.address}`);  // Add token as supported  console.log("\n⚙️  Configuring facilitator...");  await facilitator.addSupportedToken(mockUSDT0.address);  console.log(`   Added MockUSDT0 as supported token`);  // Summary  console.log("\n" + "═".repeat(60));  console.log("Deployment Summary");  console.log("═".repeat(60));  console.log(`MockUSDT0:       ${mockUSDT0.address}`);  console.log(`X402Facilitator: ${facilitator.address}`);  console.log(`Payee Address:   ${deployer}`);  console.log("─".repeat(60));  console.log("\n📝 Add these to your .env file:");  console.log(`X402_TOKEN_ADDRESS=${mockUSDT0.address}`);  console.log(`X402_FACILITATOR_ADDRESS=${facilitator.address}`);  console.log(`X402_PAYEE_ADDRESS=${deployer}`);  console.log("─".repeat(60));  // Verify contracts  console.log("\n🔍 Verifying contracts on explorer...");  try {    await run("verify:verify", {      address: mockUSDT0.address,      constructorArguments: [],    });    console.log("   MockUSDT0 verified");  } catch (e: unknown) {    const msg = e instanceof Error ? e.message.slice(0, 100) : String(e);    console.log(`   MockUSDT0 verification: ${msg}`);  }  try {    await run("verify:verify", {      address: facilitator.address,      constructorArguments: facilitatorArgs,    });    console.log("   X402Facilitator verified");  } catch (e: unknown) {    const msg = e instanceof Error ? e.message.slice(0, 100) : String(e);    console.log(`   X402Facilitator verification: ${msg}`);  }  console.log("\n✅ Deployment complete!");}void deployAndVerify().then(() => {  process.exit(0);});
```

</details>

### Deployment Output[​](#deployment-output "Direct link to Deployment Output")

```
═══════════════════════════════════════════════════════════x402 Demo Deployment═══════════════════════════════════════════════════════════Deployer: 0x742d35Cc6634C0532925a3b844Bc454e4438f44eBalance:  100.0 C2FLR──────────────────────────────────────────────────────────📦 Deploying MockUSDT0...   MockUSDT0 deployed to: 0x1234...   Name: Mock USDT0   Symbol: mUSDT0   Decimals: 6📦 Deploying X402Facilitator...   X402Facilitator deployed to: 0x5678...⚙️  Configuring facilitator...   Added MockUSDT0 as supported token═══════════════════════════════════════════════════════════Deployment Summary═══════════════════════════════════════════════════════════MockUSDT0:       0x1234...X402Facilitator: 0x5678...Payee Address:   0x742d35Cc6634C0532925a3b844Bc454e4438f44e
```

## Step 2: Test EIP-3009 Directly[​](#step-2-test-eip-3009-directly "Direct link to Step 2: Test EIP-3009 Directly")

Before running the full x402 flow, verify EIP-3009 works correctly:

```
yarn hardhat run scripts/x402/testEip3009.ts --network coston2
```

This script:

1.  Mints test tokens if needed.
2.  Creates an EIP-712 signed authorization.
3.  Executes `transferWithAuthorization`.
4.  Verifies nonce replay protection.

## Step 3: Start the Server[​](#step-3-start-the-server "Direct link to Step 3: Start the Server")

The server implements the x402 payment middleware.

```
npx ts-node scripts/x402/server.ts
```

The server runs on `http://localhost:3402` and exposes:

Endpoint

Price

Description

`GET /api/public`

Free

Public data

`GET /api/premium-data`

0.1 USDT0

Premium data

`GET /api/report`

0.5 USDT0

Detailed report

`GET /health`

Free

Health check

<details>
<summary>View server implementation</summary>

View server implementation

scripts/x402/server.ts

```
/** * x402 Payment Demo Server * * Run: npx ts-node scripts/x402/server.ts * * This implements the x402 HTTP payment protocol with EIP-3009 support. */import express, {  type Request,  type Response,  type NextFunction,} from "express";import cors from "cors";import { ethers } from "ethers";import * as fs from "fs";import * as path from "path";import "dotenv/config";import rateLimit from "express-rate-limit";const app = express();app.use(cors());const rootRateLimiter = rateLimit({  windowMs: 15 * 60 * 1000, // 15 minutes  max: 100, // limit each IP to 100 requests per windowMs});app.use(express.json());// Configurationconst PORT = process.env.X402_PORT || 3402;const COSTON2_RPC =  process.env.COSTON2_RPC_URL || "https://coston2-api.flare.network/ext/C/rpc";const TOKEN_ADDRESS = process.env.X402_TOKEN_ADDRESS || "";const FACILITATOR_ADDRESS = process.env.X402_FACILITATOR_ADDRESS || "";const PAYEE_ADDRESS = process.env.X402_PAYEE_ADDRESS || "";const PRIVATE_KEY = process.env.PRIVATE_KEY || "";// ABIs (minimal)const FACILITATOR_ABI = [  "function verifyPayment((address from, address to, address token, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)) view returns (bytes32 paymentId, bool valid)",  "function settlePayment((address from, address to, address token, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)) returns (bytes32)",];// Provider setupconst provider = new ethers.JsonRpcProvider(COSTON2_RPC);// x402 Payment requirement structureinterface PaymentRequirement {  scheme: string;  network: string;  maxAmountRequired: string;  resource: string;  description: string;  mimeType: string;  payTo: string;  maxTimeoutSeconds: number;  asset: string;  extra: {    tokenAddress: string;    facilitatorAddress: string;    chainId: number;  };}// x402 Payment payload from clientinterface PaymentPayload {  from: string;  to: string;  token: string;  value: string;  validAfter: string;  validBefore: string;  nonce: string;  v: number;  r: string;  s: string;}// Resources that require paymentconst PAID_RESOURCES: Record<string, { price: bigint; content: () => object }> =  {    "/api/premium-data": {      price: BigInt(100000), // 0.1 USDT0 (6 decimals)      content: () => ({        message: "Premium data accessed successfully!",        data: {          flarePrice: 0.0234,          timestamp: Date.now(),          secret: "This is premium content only available after payment",        },      }),    },    "/api/report": {      price: BigInt(500000), // 0.5 USDT0      content: () => ({        message: "Detailed report generated",        report: {          title: "Market Analysis Report",          sections: ["Overview", "Technical Analysis", "Predictions"],          generatedAt: new Date().toISOString(),        },      }),    },  };// Middleware to check x402 paymentasync function x402Middleware(req: Request, res: Response, next: NextFunction) {  const resource = req.path;  const paidResource = PAID_RESOURCES[resource];  if (!paidResource) {    return next();  }  // Check for PAYMENT header  const paymentHeader = req.headers["x-payment"] as string;  if (!paymentHeader) {    // Return 402 Payment Required    const paymentRequirement: PaymentRequirement = {      scheme: "exact",      network: "flare-coston2",      maxAmountRequired: paidResource.price.toString(),      resource: resource,      description: `Payment required to access ${resource}`,      mimeType: "application/json",      payTo: PAYEE_ADDRESS,      maxTimeoutSeconds: 300,      asset: "USDT0",      extra: {        tokenAddress: TOKEN_ADDRESS,        facilitatorAddress: FACILITATOR_ADDRESS,        chainId: 114,      },    };    res.status(402).json({      error: "Payment Required",      x402Version: "1",      accepts: [paymentRequirement],    });    return;  }  // Parse and verify payment  try {    const paymentPayload: PaymentPayload = JSON.parse(      Buffer.from(paymentHeader, "base64").toString("utf-8"),    );    // Verify payment amount    if (BigInt(paymentPayload.value) < paidResource.price) {      res.status(402).json({        error: "Insufficient payment",        required: paidResource.price.toString(),        received: paymentPayload.value,      });      return;    }    // Verify with facilitator contract    const facilitator = new ethers.Contract(      FACILITATOR_ADDRESS,      FACILITATOR_ABI,      provider,    );    const [paymentId, isValid] = await facilitator.verifyPayment({      from: paymentPayload.from,      to: paymentPayload.to,      token: paymentPayload.token,      value: paymentPayload.value,      validAfter: paymentPayload.validAfter,      validBefore: paymentPayload.validBefore,      nonce: paymentPayload.nonce,      v: paymentPayload.v,      r: paymentPayload.r,      s: paymentPayload.s,    });    if (!isValid) {      res.status(402).json({        error: "Invalid payment authorization",        paymentId: paymentId,      });      return;    }    // Settle the payment    const wallet = new ethers.Wallet(PRIVATE_KEY, provider);    const facilitatorWithSigner = new ethers.Contract(      FACILITATOR_ADDRESS,      FACILITATOR_ABI,      wallet,    );    const settlePayment = facilitatorWithSigner.getFunction("settlePayment");    const tx = await settlePayment({      from: paymentPayload.from,      to: paymentPayload.to,      token: paymentPayload.token,      value: paymentPayload.value,      validAfter: paymentPayload.validAfter,      validBefore: paymentPayload.validBefore,      nonce: paymentPayload.nonce,      v: paymentPayload.v,      r: paymentPayload.r,      s: paymentPayload.s,    });    const receipt = await tx.wait();    // Add payment response header    res.setHeader(      "X-Payment-Response",      Buffer.from(        JSON.stringify({          paymentId: paymentId,          transactionHash: receipt.hash,          settled: true,        }),      ).toString("base64"),    );    // Store payment info for the request    (req as unknown as { paymentInfo: object }).paymentInfo = {      paymentId,      transactionHash: receipt.hash,      amount: paymentPayload.value,    };    next();  } catch (error: unknown) {    console.error("Payment verification error:", error);    res.status(402).json({      error: "Payment verification failed",      message: error instanceof Error ? error.message : String(error),    });  }}// Apply rate limiting and x402 middleware to all routesapp.use(rootRateLimiter, (req, res, next) => {  void x402Middleware(req, res, next);});// Public endpoint (no payment required)app.get("/api/public", (req: Request, res: Response) => {  res.json({    message: "This is free public data",    timestamp: Date.now(),  });});// Protected endpoints (payment required)app.get("/api/premium-data", (req: Request, res: Response) => {  const content = PAID_RESOURCES["/api/premium-data"].content();  (content as unknown as { paymentInfo: object }).paymentInfo = (    req as unknown as { paymentInfo: object }  ).paymentInfo;  res.json(content);});app.get("/api/report", (req: Request, res: Response) => {  const content = PAID_RESOURCES["/api/report"].content();  (content as unknown as { paymentInfo: object }).paymentInfo = (    req as unknown as { paymentInfo: object }  ).paymentInfo;  res.json(content);});// Get payment requirements without triggering 402app.get("/api/payment-info/:resource", (req: Request, res: Response) => {  const resource = "/api/" + req.params.resource;  const paidResource = PAID_RESOURCES[resource];  if (!paidResource) {    res.status(404).json({ error: "Resource not found" });    return;  }  res.json({    resource,    price: paidResource.price.toString(),    priceFormatted: `${Number(paidResource.price) / 1e6} USDT0`,    tokenAddress: TOKEN_ADDRESS,    facilitatorAddress: FACILITATOR_ADDRESS,    payeeAddress: PAYEE_ADDRESS,    chainId: 114,  });});// Health checkapp.get("/health", (req: Request, res: Response) => {  res.json({    status: "ok",    config: {      token: TOKEN_ADDRESS,      facilitator: FACILITATOR_ADDRESS,      payee: PAYEE_ADDRESS,    },  });});// Serve frontend - inject config into HTMLapp.get("/", rootRateLimiter, (req: Request, res: Response) => {  const frontendPath = path.join(__dirname, "frontend.html");  let html = fs.readFileSync(frontendPath, "utf-8");  // Inject token address into the HTML  html = html.replace(    'placeholder="MockUSDT0 address"',    `placeholder="MockUSDT0 address" value="${TOKEN_ADDRESS}"`,  );  res.setHeader("Content-Type", "text/html");  res.send(html);});// Start serverfunction startServer() {  if (!TOKEN_ADDRESS || !FACILITATOR_ADDRESS || !PAYEE_ADDRESS) {    console.error("❌ Missing required environment variables:");    console.error(      "   X402_TOKEN_ADDRESS, X402_FACILITATOR_ADDRESS, X402_PAYEE_ADDRESS",    );    console.error(      "\nRun deployment first: yarn hardhat run scripts/x402/deploy.ts --network coston2",    );    process.exit(1);  }  app.listen(PORT, () => {    console.log("═".repeat(60));    console.log("x402 Demo Server");    console.log("═".repeat(60));    console.log(`Frontend:    http://localhost:${PORT}/`);    console.log(`Token:       ${TOKEN_ADDRESS}`);    console.log(`Facilitator: ${FACILITATOR_ADDRESS}`);    console.log(`Payee:       ${PAYEE_ADDRESS}`);    console.log("─".repeat(60));    console.log("Endpoints:");    console.log("  GET /                   - Frontend UI");    console.log("  GET /api/public         - Free");    console.log("  GET /api/premium-data   - 0.1 USDT0");    console.log("  GET /api/report         - 0.5 USDT0");    console.log("  GET /health             - Health check");    console.log("═".repeat(60));  });}startServer();
```

</details>

### Server Code Breakdown[​](#server-code-breakdown "Direct link to Server Code Breakdown")

1.  **Configuration**: Load environment variables for token, facilitator, and payee addresses.
    
2.  **Payment Requirements**: Define resources that require payment with their prices.
    
3.  **x402 Middleware**: Intercept requests to protected endpoints.
    
    -   If no `X-Payment` header, return 402 with payment requirements.
    -   If payment header present, verify and settle the payment.
4.  **Payment Verification**: Use the Facilitator contract to verify the EIP-3009 authorization.
    
5.  **Payment Settlement**: Call `settlePayment()` to execute the token transfer.
    
6.  **Response Headers**: Include `X-Payment-Response` with transaction details.
    

## Step 4: Run the Agent[​](#step-4-run-the-agent "Direct link to Step 4: Run the Agent")

The agent is an interactive CLI for making x402 payments.

```
npx ts-node scripts/x402/agent.ts
```

<details>
<summary>View agent implementation</summary>

View agent implementation

scripts/x402/agent.ts

```
/** * x402 Payment Agent * * Interactive CLI agent for making x402 payments with EIP-3009. * * Run: npx ts-node scripts/x402/agent.ts */// full walkthrough of x402 payment process including authorization and payment.import { ethers } from "ethers";import * as readline from "readline";import "dotenv/config";// Configurationconst COSTON2_RPC =  process.env.COSTON2_RPC_URL || "https://coston2-api.flare.network/ext/C/rpc";const PRIVATE_KEY = process.env.PRIVATE_KEY || "";const TOKEN_ADDRESS = process.env.X402_TOKEN_ADDRESS || "";const FACILITATOR_ADDRESS = process.env.X402_FACILITATOR_ADDRESS || "";const BACKEND_URL = process.env.X402_BACKEND_URL || "http://localhost:3402";// ABIsconst TOKEN_ABI = [  "function name() view returns (string)",  "function symbol() view returns (string)",  "function decimals() view returns (uint8)",  "function balanceOf(address) view returns (uint256)",  "function DOMAIN_SEPARATOR() view returns (bytes32)",  "function authorizationState(address, bytes32) view returns (bool)",  "function mint(address to, uint256 amount)",];const FACILITATOR_ABI = [  "function verifyPayment((address from, address to, address token, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s)) view returns (bytes32 paymentId, bool valid)",];// EIP-712 Types for transferWithAuthorizationconst EIP712_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" },  ],};interface PaymentRequirement {  scheme: string;  network: string;  maxAmountRequired: string;  payTo: string;  extra: {    tokenAddress: string;    facilitatorAddress: string;    chainId: number;  };}interface AuthorizationParams {  from: string;  to: string;  value: bigint;  validAfter: number;  validBefore: number;  nonce: string;}interface SignedAuthorization extends AuthorizationParams {  v: number;  r: string;  s: string;  signature: string;}class X402Agent {  private provider: ethers.JsonRpcProvider;  private wallet: ethers.Wallet;  private tokenContract: ethers.Contract;  private facilitatorContract: ethers.Contract;  constructor() {    this.provider = new ethers.JsonRpcProvider(COSTON2_RPC);    this.wallet = new ethers.Wallet(PRIVATE_KEY, this.provider);    this.tokenContract = new ethers.Contract(      TOKEN_ADDRESS,      TOKEN_ABI,      this.wallet,    );    this.facilitatorContract = new ethers.Contract(      FACILITATOR_ADDRESS,      FACILITATOR_ABI,      this.wallet,    );  }  async getBalance(): Promise<string> {    const balance = await this.tokenContract.balanceOf(this.wallet.address);    const decimals = await this.tokenContract.decimals();    const symbol = await this.tokenContract.symbol();    return `${ethers.formatUnits(balance, decimals)} ${symbol}`;  }  async fetchPaymentRequirements(    resourcePath: string,  ): Promise<PaymentRequirement | null> {    const response = await fetch(`${BACKEND_URL}${resourcePath}`);    if (response.status !== 402) {      console.log("Resource is free or already accessible");      const data = await response.json();      console.log("Response:", JSON.stringify(data, null, 2));      return null;    }    const paymentData = await response.json();    return paymentData.accepts[0];  }  async createAuthorization(    params: AuthorizationParams,  ): Promise<SignedAuthorization> {    const tokenName = await this.tokenContract.name();    const domain = {      name: tokenName,      version: "1",      chainId: 114,      verifyingContract: TOKEN_ADDRESS,    };    const message = {      from: params.from,      to: params.to,      value: params.value,      validAfter: params.validAfter,      validBefore: params.validBefore,      nonce: params.nonce,    };    console.log("\n📝 EIP-712 Authorization Details:");    console.log("─".repeat(50));    console.log(`From:         ${params.from}`);    console.log(`To:           ${params.to}`);    console.log(`Value:        ${ethers.formatUnits(params.value, 6)} USDT0`);    console.log(      `Valid After:  ${new Date(params.validAfter * 1000).toISOString()}`,    );    console.log(      `Valid Before: ${new Date(params.validBefore * 1000).toISOString()}`,    );    console.log(`Nonce:        ${params.nonce}`);    console.log("─".repeat(50));    const signature = await this.wallet.signTypedData(      domain,      EIP712_TYPES,      message,    );    const sig = ethers.Signature.from(signature);    return {      ...params,      v: sig.v,      r: sig.r,      s: sig.s,      signature,    };  }  async verifyAuthorization(    auth: SignedAuthorization,  ): Promise<{ paymentId: string; valid: boolean }> {    const payload = {      from: auth.from,      to: auth.to,      token: TOKEN_ADDRESS,      value: auth.value,      validAfter: auth.validAfter,      validBefore: auth.validBefore,      nonce: auth.nonce,      v: auth.v,      r: auth.r,      s: auth.s,    };    const [paymentId, valid] =      await this.facilitatorContract.verifyPayment(payload);    return { paymentId, valid };  }  async executePaymentAndFetch(    resourcePath: string,    auth: SignedAuthorization,  ): Promise<{ data: unknown; paymentResponse: unknown }> {    const paymentPayload = {      from: auth.from,      to: auth.to,      token: TOKEN_ADDRESS,      value: auth.value.toString(),      validAfter: auth.validAfter.toString(),      validBefore: auth.validBefore.toString(),      nonce: auth.nonce,      v: auth.v,      r: auth.r,      s: auth.s,    };    const paymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString(      "base64",    );    const response = await fetch(`${BACKEND_URL}${resourcePath}`, {      headers: {        "X-Payment": paymentHeader,      },    });    const data = await response.json();    if (response.status === 200) {      const paymentResponseHeader = response.headers.get("X-Payment-Response");      if (paymentResponseHeader) {        data.x402PaymentResponse = JSON.parse(          Buffer.from(paymentResponseHeader, "base64").toString(),        );      }    }    return { status: response.status, data };  }  async processPayment(resourcePath: string): Promise<void> {    console.log(`\n🔍 Checking payment requirements for ${resourcePath}...`);    // Step 1: Fetch payment requirements    const requirement = await this.fetchPaymentRequirements(resourcePath);    if (!requirement) {      return;    }    console.log("\n💰 Payment Required:");    console.log(      `   Amount: ${ethers.formatUnits(requirement.maxAmountRequired, 6)} USDT0`,    );    console.log(`   Payee:  ${requirement.payTo}`);    // Step 2: Check balance    const balance = await this.getBalance();    console.log(`\n💳 Your Balance: ${balance}`);    // Step 3: Prompt for confirmation    const confirmed = await this.promptConfirmation(      `Do you want to authorize payment of ${ethers.formatUnits(requirement.maxAmountRequired, 6)} USDT0?`,    );    if (!confirmed) {      console.log("❌ Payment cancelled");      return;    }    // Step 4: Create authorization    console.log("\n✍️  Creating EIP-3009 authorization...");    const nonce = ethers.hexlify(ethers.randomBytes(32));    const validAfter = Math.floor(Date.now() / 1000) - 60;    const validBefore = Math.floor(Date.now() / 1000) + 300;    const authParams: AuthorizationParams = {      from: this.wallet.address,      to: requirement.payTo,      value: BigInt(requirement.maxAmountRequired),      validAfter,      validBefore,      nonce,    };    const signedAuth = await this.createAuthorization(authParams);    console.log("✅ Authorization signed");    // Step 5: Verify authorization    console.log("\n🔐 Verifying authorization with facilitator...");    const { paymentId, valid } = await this.verifyAuthorization(signedAuth);    console.log(`   Payment ID: ${paymentId}`);    console.log(`   Valid: ${valid}`);    if (!valid) {      console.log("❌ Authorization verification failed");      return;    }    // Step 6: Execute payment and fetch resource    console.log("\n📤 Submitting payment and fetching resource...");    const result = await this.executePaymentAndFetch(resourcePath, signedAuth);    if (result.status === 200) {      console.log("\n✅ Payment successful!");      console.log("─".repeat(50));      console.log("📦 Resource Data:");      console.log(JSON.stringify(result.data, null, 2));      if (result.data.x402PaymentResponse) {        console.log("\n🧾 Payment Receipt:");        console.log(          `   Transaction: ${result.data.x402PaymentResponse.transactionHash}`,        );        console.log(          `   Payment ID:  ${result.data.x402PaymentResponse.paymentId}`,        );      }    } else {      console.log(`\n❌ Payment failed (${result.status}):`);      console.log(JSON.stringify(result.data, null, 2));    }  }  private promptConfirmation(question: string): Promise<boolean> {    return new Promise((resolve) => {      const rl = readline.createInterface({        input: process.stdin,        output: process.stdout,      });      rl.question(`\n${question} (y/n): `, (answer) => {        rl.close();        resolve(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");      });    });  }  private prompt(question: string): Promise<string> {    return new Promise((resolve) => {      const rl = readline.createInterface({        input: process.stdin,        output: process.stdout,      });      rl.question(question, (answer) => {        rl.close();        resolve(answer);      });    });  }  async interactiveMode(): Promise<void> {    console.log("\n🤖 x402 Payment Agent");    console.log("═".repeat(50));    console.log(`Wallet:      ${this.wallet.address}`);    console.log(`Token:       ${TOKEN_ADDRESS}`);    console.log(`Facilitator: ${FACILITATOR_ADDRESS}`);    console.log(`Backend:     ${BACKEND_URL}`);    console.log("═".repeat(50));    const balance = await this.getBalance();    console.log(`Balance:     ${balance}`);    // eslint-disable-next-line no-constant-condition    while (true) {      console.log("\n📋 Available Commands:");      console.log("  1. Fetch /api/public (free)");      console.log("  2. Fetch /api/premium-data (0.1 USDT0)");      console.log("  3. Fetch /api/report (0.5 USDT0)");      console.log("  4. Check balance");      console.log("  5. Mint test tokens");      console.log("  6. Exit");      const choice = await this.prompt("\nSelect option: ");      switch (choice) {        case "1":          await this.fetchPaymentRequirements("/api/public");          break;        case "2":          await this.processPayment("/api/premium-data");          break;        case "3":          await this.processPayment("/api/report");          break;        case "4": {          const bal = await this.getBalance();          console.log(`\n💳 Balance: ${bal}`);          break;        }        case "5": {          console.log("\n🪙 Minting test tokens...");          const tx = await this.tokenContract.mint(            this.wallet.address,            ethers.parseUnits("1000", 6),          );          await tx.wait();          console.log("✅ Minted 1000 test tokens");          break;        }        case "6":          console.log("\n👋 Goodbye!");          process.exit(0);          break;        default:          console.log("Invalid option");      }    }  }}// Run agentasync function main() {  if (!PRIVATE_KEY) {    console.error("❌ PRIVATE_KEY not set in environment");    process.exit(1);  }  if (!TOKEN_ADDRESS) {    console.error("❌ X402_TOKEN_ADDRESS not set in environment");    console.error(      "\nRun deployment first: yarn hardhat run scripts/x402/deploy.ts --network coston2",    );    process.exit(1);  }  if (!FACILITATOR_ADDRESS) {    console.error("❌ X402_FACILITATOR_ADDRESS not set in environment");    console.error(      "\nRun deployment first: yarn hardhat run scripts/x402/deploy.ts --network coston2",    );    process.exit(1);  }  const agent = new X402Agent();  await agent.interactiveMode();}main().catch(console.error);
```

</details>

### Agent Commands[​](#agent-commands "Direct link to Agent Commands")

```
📋 Available Commands:  1. Fetch /api/public (free)  2. Fetch /api/premium-data (0.1 USDT0)  3. Fetch /api/report (0.5 USDT0)  4. Check balance  5. Mint test tokens  6. Exit
```

### Example Payment Flow[​](#example-payment-flow "Direct link to Example Payment Flow")

```
🔍 Checking payment requirements for /api/premium-data...💰 Payment Required:   Amount: 0.1 USDT0   Payee:  0x742d35Cc6634C0532925a3b844Bc454e4438f44e💳 Your Balance: 1000.0 USDT0Do you want to authorize payment of 0.1 USDT0? (y/n): y✍️  Creating EIP-3009 authorization...📝 EIP-712 Authorization Details:──────────────────────────────────────────────────From:         0xYourAddress...To:           0xPayeeAddress...Value:        0.1 USDT0Valid After:  2024-01-15T10:00:00.000ZValid Before: 2024-01-15T10:05:00.000ZNonce:        0x1234...──────────────────────────────────────────────────✅ Authorization signed🔐 Verifying authorization with facilitator...   Payment ID: 0xabcd...   Valid: true📤 Submitting payment and fetching resource...✅ Payment successful!──────────────────────────────────────────────────📦 Resource Data:{  "message": "Premium data accessed successfully!",  "data": {    "flarePrice": 0.0234,    "timestamp": 1705312800000,    "secret": "This is premium content only available after payment"  }}🧾 Payment Receipt:   Transaction: 0xdef456...   Payment ID:  0xabcd...
```

## x402 Protocol Details[​](#x402-protocol-details "Direct link to x402 Protocol Details")

### 402 Response Format[​](#402-response-format "Direct link to 402 Response Format")

When a resource requires payment, the server returns:

```
{  "error": "Payment Required",  "x402Version": "1",  "accepts": [    {      "scheme": "exact",      "network": "flare-coston2",      "maxAmountRequired": "100000",      "payTo": "0x...",      "asset": "USDT0",      "extra": {        "tokenAddress": "0x...",        "facilitatorAddress": "0x...",        "chainId": 114      }    }  ]}
```

### X-Payment Header[​](#x-payment-header "Direct link to X-Payment Header")

The client sends payment authorization as a Base64-encoded JSON:

```
{  "from": "0x...",  "to": "0x...",  "token": "0x...",  "value": "100000",  "validAfter": "1704067200",  "validBefore": "1704070800",  "nonce": "0x...",  "v": 28,  "r": "0x...",  "s": "0x..."}
```

### X-Payment-Response Header[​](#x-payment-response-header "Direct link to X-Payment-Response Header")

The server confirms settlement with:

```
{  "paymentId": "0x...",  "transactionHash": "0x...",  "settled": true}
```

## EIP-712 Signature[​](#eip-712-signature "Direct link to EIP-712 Signature")

The agent signs authorizations using EIP-712 typed data:

```
const domain = {  name: "Mock USDT0",  version: "1",  chainId: 114,  verifyingContract: tokenAddress,};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: userAddress,  to: payeeAddress,  value: amount,  validAfter: Math.floor(Date.now() / 1000) - 60,  validBefore: Math.floor(Date.now() / 1000) + 300,  nonce: ethers.hexlify(ethers.randomBytes(32)),};const signature = await wallet.signTypedData(domain, types, message);
```

## Security Considerations[​](#security-considerations "Direct link to Security Considerations")

1.  **Front-running Protection**: Use `receiveWithAuthorization` when the payee is a contract to prevent front-running attacks.
    
2.  **Nonce Management**: Always use cryptographically random 32-byte nonces. Never reuse nonces.
    
3.  **Time Bounds**: Set reasonable `validAfter` and `validBefore` windows. The example uses a 5-minute window.
    
4.  **Domain Verification**: Always verify the contract address in the EIP-712 domain matches the expected token.
    

## Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting")

**Error: X402\_TOKEN\_ADDRESS not set**

-   Solution: Deploy contracts first and add addresses to `.env`.

**Error: Payment verification failed**

-   Solution: Ensure the authorization hasn't expired (`validBefore`).
-   Check that the nonce hasn't been used before.

**Error: Insufficient balance**

-   Solution: Mint test tokens using the agent's option 5.

## References[​](#references "Direct link to References")

-   [x402 Protocol Specification](https://www.x402.org/)
-   [EIP-3009: Transfer With Authorization](https://eips.ethereum.org/EIPS/eip-3009)
-   [Coinbase x402 Implementation](https://github.com/coinbase/x402)
-   [EIP-712: Typed Structured Data Hashing](https://eips.ethereum.org/EIPS/eip-712)

Next Steps

To continue your development journey:

-   Learn about [FAssets minting](/fassets/developer-guides/fassets-direct-minting).
-   Explore [swapping tokens to FXRP](/fxrp/token-interactions/usdt0-fxrp-swap).
-   Read about [Flare's data protocols](/network/overview).
