Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/cloudflare/agents/llms.txt

Use this file to discover all available pages before exploring further.

HTTP payment gating using the x402 protocol with Hono middleware. A /protected-route requires a $0.10 payment on Base Sepolia - an Agent with a test wallet pays automatically.

What it demonstrates

  • @x402/hono middleware - paymentMiddleware() gates any Hono route behind a price
  • @x402/fetch - wrapFetchWithPayment(fetch) wraps fetch so the agent signs and pays automatically
  • @x402/evm - EVM scheme registration for both client and server
  • @callable - the agent exposes fetchProtectedRoute as a callable method
  • useAgent + agent.call() - the React frontend triggers the paid fetch via WebSocket RPC

Architecture

┌─────────────┐
│   Client    │
│  (Browser)  │
└──────┬──────┘
       │ agent.call("fetchProtectedRoute")

       v
┌─────────────────────────────────────┐
│         PayAgent (DO)               │
│  - Has private key                  │
│  - Signs payment                    │
│  - Makes HTTP request               │
└──────┬──────────────────────────────┘
       │ fetch("/protected-route")
       │ + x402 payment header

       v
┌─────────────────────────────────────┐
│   Protected Route (Hono)            │
│  - Verifies payment                 │
│  - Returns content if paid          │
└─────────────────────────────────────┘

Server Implementation

Gating a Route

src/server.ts
import { Hono } from "hono";
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { registerExactEvmScheme } from "@x402/evm/exact/server";

const app = new Hono<{ Bindings: Env }>();

const facilitatorClient = new HTTPFacilitatorClient({
  url: "https://x402.org/facilitator"
});
const resourceServer = new x402ResourceServer(facilitatorClient);
registerExactEvmScheme(resourceServer);

app.use(
  paymentMiddleware(
    {
      "GET /protected-route": {
        accepts: [
          {
            scheme: "exact",
            price: "$0.10",
            network: "eip155:84532",  // Base Sepolia testnet
            payTo: process.env.SERVER_ADDRESS as `0x${string}`
          }
        ],
        description: "Access to premium content",
        mimeType: "application/json"
      }
    },
    resourceServer
  )
);

app.get("/protected-route", (c) => {
  return c.json({
    message: "This content is behind a paywall. Thanks for paying!"
  });
});

export default app;

Agent That Pays

src/server.ts
import { Agent, callable } from "agents";
import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { toClientEvmSigner } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";

export class PayAgent extends Agent<Env> {
  fetchWithPay?: ReturnType<typeof wrapFetchWithPayment>;

  onStart() {
    const pk = process.env.CLIENT_TEST_PK;
    if (!pk) {
      console.warn("CLIENT_TEST_PK not set");
      return;
    }

    const account = privateKeyToAccount(pk as `0x${string}`);
    console.log("Agent will pay from:", account.address);

    const client = new x402Client();
    registerExactEvmScheme(client, { signer: toClientEvmSigner(account) });
    this.fetchWithPay = wrapFetchWithPayment(fetch, client);
  }

  @callable()
  async fetchProtectedRoute() {
    if (!this.fetchWithPay) {
      return {
        text: "Agent not ready - CLIENT_TEST_PK not configured",
        isError: true
      };
    }

    const paidUrl = "http://localhost:5173/protected-route";
    const res = await this.fetchWithPay(paidUrl, {});
    const data = await res.json();

    return {
      text: JSON.stringify(data, null, 2),
      isError: !res.ok
    };
  }
}

How It Works

1

Server defines price

The paymentMiddleware configures which routes require payment and at what price:
"GET /protected-route": {
  accepts: [{
    scheme: "exact",
    price: "$0.10",
    network: "eip155:84532",
    payTo: "0x..."
  }]
}
2

Client makes request

The client calls the agent’s fetchProtectedRoute method:
const result = await agent.call("fetchProtectedRoute", []);
3

Agent discovers price

When fetchWithPay makes a request, it receives a 402 Payment Required response with payment options.
4

Agent signs payment

The agent automatically:
  • Selects a payment method (EVM on Base Sepolia)
  • Signs a payment transaction with its private key
  • Retries the request with payment headers
5

Server verifies and serves

The middleware verifies the payment signature and on-chain transaction, then serves the content.

Environment Setup

Copy .env.example to .env:
cp .env.example .env
Fill in the required variables:
.env
# Address to receive payments (Base Sepolia testnet)
SERVER_ADDRESS=0x...

# Private key for signing payments (test key only!)
# Get test funds from https://faucet.circle.com/
CLIENT_TEST_PK=0x...
Never commit real private keys! Use test keys only and get testnet funds from the Circle faucet.

Running the Example

1

Install dependencies

npm install
2

Configure environment

cp .env.example .env
# Edit .env with your addresses
3

Start the server

npm start
4

Trigger payment

Open http://localhost:5173 and click “Fetch & Pay”. The agent will automatically pay and fetch the protected content.

Payment Flow Details

1. Initial Request (No Payment)

GET /protected-route HTTP/1.1
Host: localhost:5173

2. Server Response (402 Payment Required)

HTTP/1.1 402 Payment Required
WWW-Authenticate: x402 resource="http://localhost:5173/protected-route"

{
  "accepts": [
    {
      "scheme": "exact",
      "price": "$0.10",
      "network": "eip155:84532",
      "payTo": "0x..."
    }
  ]
}

3. Client Signs Payment

The agent:
  1. Parses the payment options
  2. Creates an EVM transaction
  3. Signs with its private key
  4. Submits to the blockchain
  5. Gets a transaction hash

4. Retry with Payment Proof

GET /protected-route HTTP/1.1
Host: localhost:5173
Authorization: x402 scheme=exact, txhash=0x..., network=eip155:84532

5. Server Verifies and Responds

The middleware:
  1. Extracts the payment proof
  2. Verifies the transaction on-chain
  3. Checks amount and recipient
  4. Serves the content if valid
HTTP/1.1 200 OK
Content-Type: application/json

{
  "message": "This content is behind a paywall. Thanks for paying!"
}

Comparison: x402 vs x402-mcp

Featurex402 (This Example)x402-mcp
What’s gatedHTTP endpointsMCP tools
ProtocolHTTP with 402 Payment RequiredMCP with payment extensions
Use caseREST APIs, web contentAI agent tools
Libraries@x402/hono, @x402/fetchwithX402(), withX402Client()
IntegrationHono middlewareAgent SDK wrappers

Security Considerations

Private Key Management

  • Never hardcode private keys in source code
  • Use environment variables or Cloudflare secrets
  • Use test keys for development, real keys only in production
  • Rotate keys regularly

Payment Verification

The middleware automatically:
  • Verifies transaction signatures
  • Checks transaction confirmation on-chain
  • Validates payment amount and recipient
  • Prevents replay attacks

Network Configuration

For production:
  • Use mainnet (eip155:8453 for Base)
  • Monitor payment transactions
  • Set appropriate timeout values
  • Handle network errors gracefully

x402 MCP

Paid MCP tools using Agent SDK integration

MCP Server

Build MCP servers with persistent state

GitHub Webhook

Handle webhooks with signature verification

Email Agent

Process emails with secure routing

Further Reading

Build docs developers (and LLMs) love