Skip to main content

Overview

This tutorial walks you through building a complete paid API service from scratch using the x402x payment protocol. You will learn how to:
  • Quickly obtain the seller’s recipient address
  • Create a seller server
  • Create a buyer client
  • Handle the payment flow

Prerequisites

1. Seller recipient address

Go to app.x402x.ai to register and obtain the seller’s wallet (an EIP-7702 address will be automatically generated, see Why EIP-7702).

2. Token address

Currently supported token addresses by the WTF Facilitator:
  • USD1 (BSC): 0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d

3. Install dependencies

  • npm
  • pnpm
  • bun
npm install x402x-server@beta x402x-facilitator@beta x402x-fetch@beta express viem

Build it

1. Create the seller server

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

const facilitator = new Facilitator({
  recipientAddress: "0xSellerEIP7702RecipientAddress", // Seller 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: "API access test",
});

const result = await server.process(
  req.headers["x-payment"],
  requirements
);
import express from "express";
import { X402Server } from "x402x-server";
import { Facilitator } from "x402x-facilitator";
import { createPublicClient, http } from "viem";
import { parseEther } from "viem";
import { bsc } from "viem/chains";

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

const SELLER_EIP7702_RECIPIENT_ADDRESS = '0xSellerEIP7702RecipientAddress';
const ASSET_ADDRESS = '0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d';
const MAX_AMOUNT_REQUIRED = parseEther("0.01").toString();
const PORT = 3939;

const main = async () => {
  const client = createPublicClient({
    chain: bsc,
    transport: http('https://bsc-dataseed1.bnbchain.org'),
  });

  const facilitator = new Facilitator({
    recipientAddress: SELLER_EIP7702_RECIPIENT_ADDRESS, // Seller recipient address
  });

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

  await server.initialize([ASSET_ADDRESS])

  app.post("/api/data", async (req, res) => {
    try {
      const requirements = await server.createRequirements({
        asset: ASSET_ADDRESS, // USD1
        maxAmountRequired: MAX_AMOUNT_REQUIRED, // 0.01 USD1 (18 decimals)
        description: "API access test",
        resource: `http://localhost:${PORT}/api/data`,
      });

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

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

      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();

2. Create the buyer client

Create client.ts, core logic:
// 1. Create a wallet client
const client = createWalletClient({
  account,
  transport: http(),
  chain: bsc,
});

// 2. Wrap fetch to support automatic payment
const fetchWithPay = wrapFetchWithPayment(
    fetch,
    walletClient.extend(publicActions),
    parseEther("0.01")
);

// 3. Make the request (payment handled automatically)
const response = await fetchWithPay('http://localhost:3939/api/data', {
  method: "POST",
});
import { createWalletClient, http, parseEther, publicActions } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { wrapFetchWithPayment } from "x402x-fetch";
import { bsc } from "viem/chains";

const PRIVATE_KEY='' // Wallet holding the USD1 token
const SERVER_URL='http://localhost:3939/api/data'

async function main() {
    // Create wallet
    const account = privateKeyToAccount(PRIVATE_KEY);
    const client = createWalletClient({
        account,
        transport: http(),
        chain: bsc,
    });

    // Create the paying fetch
    const fetchWithPay = wrapFetchWithPayment(
        fetch,
        client.extend(publicActions),
        parseEther("0.01")
    );

    // Call the paid API
    const response = await fetchWithPay(SERVER_URL, {
        method: "POST",
    });

    if (!response.ok) {
        const error = await response.json();
        console.error("❌ Error:", error);
        return;
    }

    const data = await response.json();
    console.log("✅ Received:", data);
}

main();

3. Run

# Terminal 1 - start the server
bun run server.ts # or: npx tsx server.ts

# Terminal 2 - run the client
bun run client.ts # or: npx tsx client.ts

Expected output

Server:
✅ Server running on http://localhost:3939
Client:
✅ Received: {
  message: 'Success!',
  data: 'This is premium content',
  payer: '0xYourAddress',
  txHash: '0x...'
}
🎉 Congratulations! You’ve built your first x402x application.

What’s next