Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 172 additions & 0 deletions src/content/docs/agents/x402.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
---
title: x402
pcx_content_type: navigation
sidebar:
order: 5
group:
hideIndex: false
---

## What is x402?

[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.

## x402 Workers and Agents

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.

```ts
import { Hono } from "hono";
import { Agent, getAgentByName } from "agents";
import { wrapFetchWithPayment } from "x402-fetch";
import { paymentMiddleware } from "x402-hono";

// This allows us to create a wallet from just a private key
import { privateKeyToAccount } from "viem/accounts";

// We create an Agent that can fetch the protected route and automatically pay.
// The agent's wallet must not be empty! You can get test credits
// for base-sepolia here: https://faucet.circle.com/
export class PayAgent extends Agent<Env> {
fetchWithPay!: ReturnType<typeof wrapFetchWithPayment>;

onStart() {
// We instantiate a wallet from which the agent will pay
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// We instantiate a wallet from which the agent will pay
// Create a temporary wallet that the agent will use to pay

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is this is a temporary wallet. Yes? No?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, privateKeyToAccount is deterministic and always derives the same wallet (I'm not sure of the naming either) from a given private key.
So what's expected is that you only need to load your private key into your Worker's secrets and your wallet will be ready to use.

const pk = process.env.CLIENT_TEST_PK as `0x${string}`;
const agentAccount = privateKeyToAccount(pk);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be called "wallet"?

(Since that's how it is described in the sentence above the code snippet and is most specific)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not entirely sure on the difference on wallet vs account. Since the x402 types are Account I'll stick to that wording. I've updated the comments to make it more clear as well.

console.log("Agent will pay from this address:", agentAccount.address);
this.fetchWithPay = wrapFetchWithPayment(fetch, agentAccount);
}

async onRequest(req: Request) {
const url = new URL(req.url);
console.log("Trying to fetch paid API");

// We use the x402 fetch to access our paid endpoint
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// We use the x402 fetch to access our paid endpoint
// Use the x402 compatible fetch (fetchWithPay) to access the paid endpoint

// Note: this could be any paid endpoint, on any server
const paidUrl = new URL("/protected-route", url.origin).toString();
return this.fetchWithPay(paidUrl, {});
}
}

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

// Configure the middleware.
// Only gate the `protected-route` endpoint, everything else we keep free.
app.use(
paymentMiddleware(
process.env.SERVER_ADDRESS as `0x${string}`, // our server wallet address
{
"/protected-route": {
price: "$0.10",
network: "base-sepolia",
config: {
description: "Access to premium content",
},
},
},
{ url: "https://x402.org/facilitator" }, // Payment facilitator URL
),
);

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

// The agent will fetch our own protected route and automatically pay.
app.get("/agent", async (c) => {
const agent = await getAgentByName(c.env.PAY_AGENT, "1234");
return agent.fetch(c.req.raw);
});

export default app;
```

Check out the [complete example](https://github.com/cloudflare/agents/tree/main/examples/x402).

## MCP servers with paid tools

x402 supercharges your MCP servers so they can include paid tools.

```ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpAgent } from "agents/mcp";
import { withX402, type X402Config } from "agents/x402";

const X402_CONFIG: X402Config = {
network: "base",
recipient: env.MCP_ADDRESS,
facilitator: { url: "https://x402.org/facilitator" }, // Payment facilitator URL
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something that I need to set myself or do I just use this URL as the default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK this one is the official (?) supported payment facilitator that handles payment verifications/settling for servers, and should work for most networks. Some users might want to use different facilitators so they could update it.

In any case, I've added an inline comment to docs on facilitators (https://x402.gitbook.io/x402/core-concepts/facilitator)

};

export class PaidMCP extends McpAgent<Env> {
server = withX402(
new McpServer({ name: "PaidMCP", version: "1.0.0" }),
X402_CONFIG,
); // That's it!

async init() {
// Paid tool
this.server.paidTool(
"square",
"Squares a number",
0.01, // USD
{ number: z.number() },
{},
async ({ number }) => {
return { content: [{ type: "text", text: String(number ** 2) }] };
},
);

// Free tool
this.server.tool(
"echo",
"Echo a message",
{ message: z.string() },
async ({ message }) => {
return { content: [{ type: "text", text: message }] };
},
);
}
}
```

We also include an MCP client that you can use from anywhere (not just your Agents!) to pay for these tools.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where would someone use this outside of an agent? Example might help

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any application that connects to MCP servers is using some version of an MCP client. The wrapper agents wraps the official MCP client to support paid tool calls (which are not officially part of the spec)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e.g. Windsurf wants to support MCPs with paid tools, so they'll need an MCP client that can handle payments


```ts
import { Agent } from "agents";
import { withX402Client } from "agents/x402";
import { privateKeyToAccount } from "viem/accounts";

export class MyAgent extends Agent {
// Your Agent definitions...

onStart() {
const { id } = await this.mcp.connect(`${env.WORKER_URL}/mcp`);
const account = privateKeyToAccount(this.env.MY_PRIVATE_KEY);

this.x402Client = withX402Client(this.mcp.mcpConnections[id].client, {
network: "base-sepolia",
account,
});
}

onPaymentRequired(paymentRequirements): Promise<boolean> {
// Your HIL confirmation flow...
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's HIL?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Human-in-the-loop", updated it to use that instead to make it more clear

}

async onToolCall() {
// The first parameter becomes the confirmation callback.
// We can set it to `null` if we want the agent to pay automatically.
const res = await this.x402Client.callTool(this.onPaymentRequired, {
name: toolName,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an example tool? Otherwise I read this and wondered what this was referencing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added toolName and toolArgs as parameters to fix this

arguments: toolArgs,
});
}
}
```

Check out the [complete example](https://github.com/cloudflare/agents/tree/main/examples/x402-mcp).
Loading