|
| 1 | +--- |
| 2 | +title: x402 |
| 3 | +pcx_content_type: navigation |
| 4 | +sidebar: |
| 5 | + order: 5 |
| 6 | + group: |
| 7 | + hideIndex: false |
| 8 | +--- |
| 9 | + |
| 10 | +## What is x402? |
| 11 | + |
| 12 | +[x402](https://www.x402.org/) is an open payment standard that enables services to charge for access to their APIs and content directly over HTTP. It is built around the HTTP 402 Payment Required status code and allows clients to programmatically pay for resources without accounts, sessions, or credential management. |
| 13 | + |
| 14 | +## x402 Workers and Agents |
| 15 | + |
| 16 | +You can now create paywalled endpoints in your Workers and query any x402 server from your agent by wrapping the `fetch` with x402 and your wallet. |
| 17 | + |
| 18 | +```ts |
| 19 | +import { Hono } from "hono"; |
| 20 | +import { Agent, getAgentByName } from "agents"; |
| 21 | +import { wrapFetchWithPayment } from "x402-fetch"; |
| 22 | +import { paymentMiddleware } from "x402-hono"; |
| 23 | + |
| 24 | +// This allows us to create a wallet from just a private key |
| 25 | +import { privateKeyToAccount } from "viem/accounts"; |
| 26 | + |
| 27 | +// We create an Agent that can fetch the protected route and automatically pay. |
| 28 | +// The agent's wallet must not be empty! You can get test credits |
| 29 | +// for base-sepolia here: https://faucet.circle.com/ |
| 30 | +export class PayAgent extends Agent<Env> { |
| 31 | + fetchWithPay!: ReturnType<typeof wrapFetchWithPayment>; |
| 32 | + |
| 33 | + onStart() { |
| 34 | + // We instantiate a wallet from which the agent will pay |
| 35 | + const pk = process.env.CLIENT_TEST_PK as `0x${string}`; |
| 36 | + const agentAccount = privateKeyToAccount(pk); |
| 37 | + console.log("Agent will pay from this address:", agentAccount.address); |
| 38 | + this.fetchWithPay = wrapFetchWithPayment(fetch, agentAccount); |
| 39 | + } |
| 40 | + |
| 41 | + async onRequest(req: Request) { |
| 42 | + const url = new URL(req.url); |
| 43 | + console.log("Trying to fetch paid API"); |
| 44 | + |
| 45 | + // We use the x402 fetch to access our paid endpoint |
| 46 | + // Note: this could be any paid endpoint, on any server |
| 47 | + const paidUrl = new URL("/protected-route", url.origin).toString(); |
| 48 | + return this.fetchWithPay(paidUrl, {}); |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +const app = new Hono<{ Bindings: Env }>(); |
| 53 | + |
| 54 | +// Configure the middleware. |
| 55 | +// Only gate the `protected-route` endpoint, everything else we keep free. |
| 56 | +app.use( |
| 57 | + paymentMiddleware( |
| 58 | + process.env.SERVER_ADDRESS as `0x${string}`, // our server wallet address |
| 59 | + { |
| 60 | + "/protected-route": { |
| 61 | + price: "$0.10", |
| 62 | + network: "base-sepolia", |
| 63 | + config: { |
| 64 | + description: "Access to premium content", |
| 65 | + }, |
| 66 | + }, |
| 67 | + }, |
| 68 | + { url: "https://x402.org/facilitator" }, // Payment facilitator URL |
| 69 | + ), |
| 70 | +); |
| 71 | + |
| 72 | +// Our paid endpoint will return some premium content. |
| 73 | +app.get("/protected-route", (c) => { |
| 74 | + return c.json({ |
| 75 | + message: "This content is behind a paywall. Thanks for paying!", |
| 76 | + }); |
| 77 | +}); |
| 78 | + |
| 79 | +// The agent will fetch our own protected route and automatically pay. |
| 80 | +app.get("/agent", async (c) => { |
| 81 | + const agent = await getAgentByName(c.env.PAY_AGENT, "1234"); |
| 82 | + return agent.fetch(c.req.raw); |
| 83 | +}); |
| 84 | + |
| 85 | +export default app; |
| 86 | +``` |
| 87 | + |
| 88 | +Check out the [complete example](https://github.com/cloudflare/agents/tree/main/examples/x402). |
| 89 | + |
| 90 | +## MCP servers with paid tools |
| 91 | + |
| 92 | +x402 supercharges your MCP servers so they can include paid tools. |
| 93 | + |
| 94 | +```ts |
| 95 | +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 96 | +import { McpAgent } from "agents/mcp"; |
| 97 | +import { withX402, type X402Config } from "agents/x402"; |
| 98 | + |
| 99 | +const X402_CONFIG: X402Config = { |
| 100 | + network: "base", |
| 101 | + recipient: env.MCP_ADDRESS, |
| 102 | + facilitator: { url: "https://x402.org/facilitator" }, // Payment facilitator URL |
| 103 | +}; |
| 104 | + |
| 105 | +export class PaidMCP extends McpAgent<Env> { |
| 106 | + server = withX402( |
| 107 | + new McpServer({ name: "PaidMCP", version: "1.0.0" }), |
| 108 | + X402_CONFIG, |
| 109 | + ); // That's it! |
| 110 | + |
| 111 | + async init() { |
| 112 | + // Paid tool |
| 113 | + this.server.paidTool( |
| 114 | + "square", |
| 115 | + "Squares a number", |
| 116 | + 0.01, // USD |
| 117 | + { number: z.number() }, |
| 118 | + {}, |
| 119 | + async ({ number }) => { |
| 120 | + return { content: [{ type: "text", text: String(number ** 2) }] }; |
| 121 | + }, |
| 122 | + ); |
| 123 | + |
| 124 | + // Free tool |
| 125 | + this.server.tool( |
| 126 | + "echo", |
| 127 | + "Echo a message", |
| 128 | + { message: z.string() }, |
| 129 | + async ({ message }) => { |
| 130 | + return { content: [{ type: "text", text: message }] }; |
| 131 | + }, |
| 132 | + ); |
| 133 | + } |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +We also include an MCP client that you can use from anywhere (not just your Agents!) to pay for these tools. |
| 138 | + |
| 139 | +```ts |
| 140 | +import { Agent } from "agents"; |
| 141 | +import { withX402Client } from "agents/x402"; |
| 142 | +import { privateKeyToAccount } from "viem/accounts"; |
| 143 | + |
| 144 | +export class MyAgent extends Agent { |
| 145 | + // Your Agent definitions... |
| 146 | + |
| 147 | + onStart() { |
| 148 | + const { id } = await this.mcp.connect(`${env.WORKER_URL}/mcp`); |
| 149 | + const account = privateKeyToAccount(this.env.MY_PRIVATE_KEY); |
| 150 | + |
| 151 | + this.x402Client = withX402Client(this.mcp.mcpConnections[id].client, { |
| 152 | + network: "base-sepolia", |
| 153 | + account, |
| 154 | + }); |
| 155 | + } |
| 156 | + |
| 157 | + onPaymentRequired(paymentRequirements): Promise<boolean> { |
| 158 | + // Your HIL confirmation flow... |
| 159 | + } |
| 160 | + |
| 161 | + async onToolCall() { |
| 162 | + // The first parameter becomes the confirmation callback. |
| 163 | + // We can set it to `null` if we want the agent to pay automatically. |
| 164 | + const res = await this.x402Client.callTool(this.onPaymentRequired, { |
| 165 | + name: toolName, |
| 166 | + arguments: toolArgs, |
| 167 | + }); |
| 168 | + } |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +Check out the [complete example](https://github.com/cloudflare/agents/tree/main/examples/x402-mcp). |
0 commit comments