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
Copy
npm install x402x-server@beta x402x-facilitator@beta x402x-fetch@beta express viem
Copy
pnpm add x402x-server@beta x402x-facilitator@beta x402x-fetch@beta express viem
Copy
bun add 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:Copy
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
);
View full server code (Generic)
View full server code (Generic)
Copy
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();
Create
server.ts, core logic:Copy
const client = createPublicClient({
chain: bsc,
transport: http('https://bsc-dataseed1.bnbchain.org'),
});
const facilitator = new Facilitator({
recipientAddress: SELLER_EIP7702_RECIPIENT_ADDRESS,
});
const server = new X402Server({ client, facilitator });
await server.initialize([ASSET_ADDRESS]);
const paymentMiddleware = createExpressMiddleware({
server,
getToken: () => ASSET_ADDRESS,
getAmount: () => MAX_AMOUNT_REQUIRED,
getConfig: () => ({
description: "Premium API access",
mimeType: "application/json",
}),
});
app.post("/api/data", paymentMiddleware, (req, res) => {
const { payer, txHash } = req.x402!;
res.json({
success: true,
data: "Premium content",
payer,
txHash,
});
});
View full server code (Express middleware)
View full server code (Express middleware)
Copy
import express from "express";
import { X402Server, createExpressMiddleware } from "x402x-server";
import { Facilitator } from "x402x-facilitator";
import { createPublicClient, http, 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;
async function main() {
const client = createPublicClient({
chain: bsc,
transport: http('https://bsc-dataseed1.bnbchain.org'),
});
const facilitator = new Facilitator({
recipientAddress: SELLER_EIP7702_RECIPIENT_ADDRESS,
});
const server = new X402Server({ client, facilitator });
await server.initialize([ASSET_ADDRESS]);
const paymentMiddleware = createExpressMiddleware({
server,
getToken: () => ASSET_ADDRESS,
getAmount: () => MAX_AMOUNT_REQUIRED,
getConfig: () => ({
description: "Premium API access",
mimeType: "application/json",
}),
});
app.get("/health", (_, res) => {
res.json({ status: "ok" });
});
app.post("/api/data", paymentMiddleware, (req, res) => {
const { payer, txHash } = req.x402!;
res.json({
success: true,
data: "Premium content",
payer,
txHash,
});
});
app.listen(PORT, () => {
console.log(`✅ Server running on http://localhost:${PORT}`);
});
}
main();
Create
server.ts, core logic:Copy
const client = createPublicClient({
chain: bsc,
transport: http('https://bsc-dataseed1.bnbchain.org'),
});
const facilitator = new Facilitator({
recipientAddress: SELLER_EIP7702_RECIPIENT_ADDRESS,
});
const server = new X402Server({ client, facilitator });
await server.initialize([ASSET_ADDRESS]);
const paymentMiddleware = createHonoMiddleware({
server,
getToken: () => ASSET_ADDRESS,
getAmount: () => MAX_AMOUNT_REQUIRED,
getConfig: () => ({
description: "Premium API access",
mimeType: "application/json",
}),
});
app.post("/api/data", paymentMiddleware, (c) => {
const { payer, txHash } = c.get("x402");
return c.json({
success: true,
data: "Premium content",
payer,
txHash,
});
});
View full server code (Hono middleware)
View full server code (Hono middleware)
Copy
import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { createHonoMiddleware, X402Server } from "x402x-server";
import { Facilitator } from "x402x-facilitator";
import { createPublicClient, http, parseEther } from "viem";
import { bsc } from "viem/chains";
// Define Hono env types
type Env = {
Variables: {
x402: {
payer: string;
txHash: string;
};
};
};
const SELLER_EIP7702_RECIPIENT_ADDRESS = '0x9b2407D5d02b00A612084543CaFd9D8E5564941A';
const ASSET_ADDRESS = '0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d';
const MAX_AMOUNT_REQUIRED = parseEther("0.01").toString();
const PORT = 3939;
async function main() {
const app = new Hono<Env>();
const client = createPublicClient({
chain: bsc,
transport: http('https://bsc-dataseed1.bnbchain.org'),
});
const facilitator = new Facilitator({
recipientAddress: SELLER_EIP7702_RECIPIENT_ADDRESS,
});
const server = new X402Server({ client, facilitator });
await server.initialize([ASSET_ADDRESS]);
const paymentMiddleware = createHonoMiddleware({
server,
getToken: () => ASSET_ADDRESS,
getAmount: () => MAX_AMOUNT_REQUIRED,
getConfig: () => ({
description: "Premium API access",
mimeType: "application/json",
}),
});
app.get("/health", (c) => {
return c.json({ status: "ok" });
});
app.post("/api/data", paymentMiddleware, (c) => {
const { payer, txHash } = c.get("x402");
return c.json({
success: true,
data: "Premium content",
payer,
txHash,
});
});
serve({
fetch: app.fetch,
port: PORT,
});
console.log(`Server running on port ${PORT}`);
}
main();
2. Create the buyer client
Createclient.ts, core logic:
Copy
// 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",
});
View full client code
View full client code
Copy
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
Copy
# 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:Copy
✅ Server running on http://localhost:3939
Copy
✅ Received: {
message: 'Success!',
data: 'This is premium content',
payer: '0xYourAddress',
txHash: '0x...'
}