Skip to main content

What you need to do

  1. Obtain recipient address (EIP-7702)
  2. Create PaymentRequirements
  3. Parse client X-Payment, verify and settle
  4. Return the resource or a standard 402 accordingly

Prerequisites

  • Seller recipient address: go to app.x402x.ai to create and obtain (EIP-7702 deployed address)
  • Token:
    • USD1 (BSC): 0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d
  • Install dependencies (server):
bun add x402x-server@beta x402x-facilitator@beta express viem

Core flow (server)

  • Generic
  • Express middleware
  • Hono middleware
Create server.ts, core logic:
const client = createPublicClient({ chain: bsc, transport: http() });

const facilitator = new Facilitator({
  recipientAddress: RECIPIENT_ADDRESS,
  waitUntil: "confirmed",
});

const server = new X402Server({ client, facilitator });

await server.initialize(["0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d"]);

const requirements = await server.createRequirements({
  asset: "0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d", // USD1
  maxAmountRequired: parseEther("0.01").toString(),    // 0.01 USD1 (18 decimals)
  description: "Premium API",
  resource: req.url,
});

const result = await server.process(req.headers["x-payment"] as string, requirements);
if (!result.success) {
  // Parse/verify failed: return standard 402; handle other errors as needed
  return res.status(result.status).json(result.response);
}

return res.json({ data, payer: result.data.payer, txHash: result.data.txHash });
import express from "express";
import { config } from "dotenv";
import { X402Server } from "x402x-server";
import { Facilitator } from "x402x-facilitator";
import { createPublicClient, http } from "viem";
import { parseEther } from "viem";
import { bsc } from "viem/chains";

config();

const app = express();
app.use(express.json());

const PORT = 3939;
const RECIPIENT_ADDRESS=0xSellerRecipientAddress;


async function main() {
  // Create viem client
  const client = createPublicClient({
    chain: bsc,
    transport: http(),
  });

  // Create facilitator
  const facilitator = new Facilitator({
    recipientAddress: RECIPIENT_ADDRESS,
    waitUntil: "confirmed",
  });

  // Create server
  const server = new X402Server({ client, facilitator });

  // Pre-detect supported payment types for current token
  await server.initialize(["0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d"]);

  // Paid API
  app.post("/api/data", async (req, res) => {
    try {
      // Create payment requirements (you can dynamically adjust amount/description/resource)
      const requirements = await server.createRequirements({
        asset: "0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d", // USD1
        maxAmountRequired: parseEther("0.01").toString(),    // 0.01 USD1
        description: "API access",
        resource: `http://localhost:${PORT}/api/data`,
      });

      // Process the payment
      const result = await server.process(
        req.headers["x-payment"] as string,
        requirements
      );

      if (!result.success) {
        return res.status(result.status).json(result.response);
      }

      // Payment succeeded, return data and optionally log reconciliation info
      res.json({
        message: "Success!",
        data: "This is premium content",
        payer: result.data.payer,
        txHash: result.data.txHash,
      });
    } catch (error) {
      console.error("Error:", error);
      res.status(500).json({
        error: "Internal server error",
        message: error instanceof Error ? error.message : "Unknown error",
      });
    }
  });

  app.listen(PORT, () => {
    console.log(`✅ Server running on http://localhost:${PORT}`);
  });
}

main();
  • Warm-up detection cache: call server.initialize(tokens) on startup to reduce first-request latency
  • Fast mode: disable auto detection when the payment type is known (autoDetect: false)
  • Reuse fixed prices: cache and reuse requirements for fixed-price endpoints to reduce creation overhead
  • Error layering: parse/verify → return 402; settle → return 500 and log

Common integration patterns

  • Fixed-price API: create and cache requirements at app startup, reuse on requests
  • Dynamic pricing API: compute maxAmountRequired and description from request context
// Fixed-price reuse example (on startup)
const cachedRequirements = await server.createRequirements({
  asset: "0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d",
  maxAmountRequired: parseEther("1").toString(),
  description: "Premium API (fixed price)",
  resource: "http://localhost:3000/api/data",
  mimeType: "application/json",
  maxTimeoutSeconds: 3600,
  network: "bsc",
});

// Reuse cachedRequirements when handling requests
const result = await server.process(req.headers["x-payment"] as string, cachedRequirements);

Error handling and responses

  • Parse/verify failed: return standard 402 directly (result.status and result.response)
  • Settlement failed or internal exception: return 500 and log details (including payer/txHash if available)