Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
48 changes: 48 additions & 0 deletions .changeset/x402-v2-migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
"agents": minor
---

Migrate x402 MCP integration from legacy `x402` package to `@x402/core` and `@x402/evm` v2

**Breaking changes for x402 users:**

- Peer dependencies changed: replace `x402` with `@x402/core` and `@x402/evm`
- `PaymentRequirements` type now uses v2 fields (e.g. `amount` instead of `maxAmountRequired`)
- `X402ClientConfig.account` type changed from `viem.Account` to `ClientEvmSigner` (structurally compatible with `privateKeyToAccount()`)

**Migration guide:**

1. Update dependencies:

```bash
npm uninstall x402
npm install @x402/core @x402/evm
```

2. Update network identifiers — both legacy names and CAIP-2 format are accepted:

```typescript
// Before
{
network: "base-sepolia";
}
// After (either works)
{
network: "base-sepolia";
} // legacy name, auto-converted
{
network: "eip155:84532";
} // CAIP-2 format (preferred)
```

3. If you access `PaymentRequirements` fields in callbacks, update to v2 field names (see `@x402/core` docs).

4. The `version` field on `X402Config` and `X402ClientConfig` is now deprecated and ignored — the protocol version is determined automatically.

**Other changes:**

- `X402ClientConfig.network` is now optional — the client auto-selects from available payment requirements
- Server-side lazy initialization: facilitator connection is deferred until the first paid tool invocation
- Payment tokens support both v2 (`PAYMENT-SIGNATURE`) and v1 (`X-PAYMENT`) HTTP headers
- Added `normalizeNetwork` export for converting legacy network names to CAIP-2 format
- Re-exports `PaymentRequirements`, `PaymentRequired`, `Network`, `FacilitatorConfig`, and `ClientEvmSigner` from `agents/x402`
6 changes: 3 additions & 3 deletions examples/x402-mcp/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# x402 MCP Example

This example demonstrates how to create paid MCP tools using the [x402 payment protocol](https://x402.org) with Cloudflare Agents.
This example demonstrates how to create paid MCP tools using the [x402 payment protocol](https://x402.org) v2 with Cloudflare Agents.

## Overview

Expand Down Expand Up @@ -39,7 +39,7 @@ import { withX402 } from "agents/x402";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

const server = withX402(new McpServer({ name: "PayMCP", version: "1.0.0" }), {
network: "base-sepolia",
network: "eip155:84532", // Base Sepolia (CAIP-2 format; legacy "base-sepolia" also accepted)
recipient: "0x...",
facilitator: { url: "https://x402.org/facilitator" }
});
Expand All @@ -66,7 +66,7 @@ import { privateKeyToAccount } from "viem/accounts";
const account = privateKeyToAccount(env.PRIVATE_KEY);

const x402Client = withX402Client(mcpClient, {
network: "base-sepolia",
network: "eip155:84532", // optional — auto-selects from payment requirements
account
});

Expand Down
3 changes: 2 additions & 1 deletion examples/x402-mcp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
},
"type": "module",
"dependencies": {
"agents": "*"
"agents": "*",
"viem": "^2.30.0"
}
}
16 changes: 10 additions & 6 deletions examples/x402-mcp/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { Agent, getAgentByName, type Connection, type WSMessage } from "agents";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { McpAgent } from "agents/mcp";
import { withX402, withX402Client, type X402Config } from "agents/x402";
import {
withX402,
withX402Client,
type X402Config,
type PaymentRequirements
} from "agents/x402";
import { z } from "zod";
import type { PaymentRequirements } from "x402/types";
import { privateKeyToAccount } from "viem/accounts";
import ui from "./ui";

Expand Down Expand Up @@ -36,17 +40,17 @@ export class PayAgent extends Agent<Env> {

const { id } = await this.mcp.connect("http://localhost:8787/mcp");

// Build the x402 MCP client
// Build the x402 MCP client (v2 — network auto-selected from payment requirements)
this.x402Client = withX402Client(this.mcp.mcpConnections[id].client, {
network: "base-sepolia",
network: "eip155:84532",
account
});
}

async onMessage(conn: Connection, message: WSMessage) {
if (typeof message === "string") {
try {
const parsed = JSON.parse(message as string);
const parsed = JSON.parse(message);
if (parsed?.type) {
switch (parsed.type) {
case "confirm":
Expand Down Expand Up @@ -94,7 +98,7 @@ export class PayAgent extends Agent<Env> {

// Create an MCP server with paid tools
const X402_CONFIG: X402Config = {
network: "base-sepolia",
network: "eip155:84532", // Base Sepolia (CAIP-2 format)
recipient: process.env.MCP_ADDRESS as `0x${string}`,
facilitator: { url: "https://x402.org/facilitator" } // Payment facilitator URL
};
Expand Down
7 changes: 5 additions & 2 deletions examples/x402/package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
{
"author": "",
"dependencies": {
"@x402/core": "^2.3.0",
"@x402/evm": "^2.3.0",
"@x402/fetch": "^2.3.0",
"@x402/hono": "^2.3.0",
"agents": "*",
"hono": "^4.11.9",
"x402-fetch": "^1.1.0",
"x402-hono": "^1.1.0"
"viem": "^2.30.0"
},
"keywords": [],
"name": "@cloudflare/agents-x402-example",
Expand Down
44 changes: 33 additions & 11 deletions examples/x402/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { Hono } from "hono";
import { Agent, getAgentByName } from "agents";
import { wrapFetchWithPayment } from "x402-fetch";
import { paymentMiddleware } from "x402-hono";
import { env } from "cloudflare:workers";

// v2 x402 imports
import { wrapFetchWithPayment } from "@x402/fetch";
import { paymentMiddleware, x402ResourceServer } from "@x402/hono";
import { x402Client } from "@x402/core/client";
import { HTTPFacilitatorClient } from "@x402/core/server";
import { registerExactEvmScheme as registerClientEvmScheme } from "@x402/evm/exact/client";
import { registerExactEvmScheme as registerServerEvmScheme } from "@x402/evm/exact/server";

// This allows us to create a wallet from just a private key
// We'll use it for both the payer and receiver accounts
import { privateKeyToAccount } from "viem/accounts";
Expand Down Expand Up @@ -31,27 +37,43 @@ export class PayAgent extends Agent<Env> {
const pk = process.env.CLIENT_TEST_PK as `0x${string}`;
const agentAccount = privateKeyToAccount(pk);
console.log("Agent will pay from this address:", agentAccount.address);
this.fetchWithPay = wrapFetchWithPayment(fetch, agentAccount);

// Create v2 x402 payment client with EVM scheme support
const client = new x402Client();
registerClientEvmScheme(client, { signer: agentAccount });

this.fetchWithPay = wrapFetchWithPayment(fetch, client);
}
}

const app = new Hono();

// Create a v2 x402 resource server with EVM scheme support
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.org/facilitator"
});
const resourceServer = new x402ResourceServer(facilitatorClient);
registerServerEvmScheme(resourceServer);

// 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"
}
"GET /protected-route": {
accepts: [
{
scheme: "exact",
price: "$0.10",
network: "eip155:84532", // Base Sepolia (CAIP-2 format)
payTo: process.env.SERVER_ADDRESS as `0x${string}`
}
],
description: "Access to premium content",
mimeType: "application/json"
}
},
{ url: "https://x402.org/facilitator" } // Payment facilitator URL
resourceServer
)
);

Expand Down
Loading
Loading