x402 v2 Migration Guide
Complete guide for migrating from x402 v1 to x402 v2 protocol
This guide covers migrating from x402 v1 to x402 v2 in Lucid Agents.
Versions:
- Migrating from: Lucid Agents v2.2.x (using x402 v1)
- Migrating to: Lucid Agents v2.4.0+ (using x402 v2)
Overview
The x402 protocol has been upgraded from v1 to v2, bringing:
- Scoped packages - New
@x402/*package naming - CAIP-2 network format - Standard network identifiers (e.g.,
eip155:8453) - Improved verification - Enhanced payment protocol and facilitator support
What Changed
Package Updates
All x402-related packages have been migrated to scoped @x402/* packages:
| Old Package | New Package | Version |
|---|---|---|
x402 | @x402/core | ^2.2.0 |
x402-fetch | @x402/fetch | ^2.2.0 |
| N/A | @x402/evm | ^2.2.0 |
x402-hono | @x402/hono | ^2.2.0 |
x402-express | @x402/express | ^2.2.0 |
x402-next | @x402/next | ^2.2.0 |
Network Format (CAIP-2)
Network identifiers now use the CAIP-2 standard:
| Old Format | New Format (CAIP-2) | Network |
|---|---|---|
"base" | "eip155:8453" | Base Mainnet |
"base-sepolia" | "eip155:84532" | Base Sepolia |
"ethereum" | "eip155:1" | Ethereum Mainnet |
"sepolia" | "eip155:11155111" | Ethereum Sepolia |
"solana" | "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" | Solana Mainnet |
"solana-devnet" | "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1" | Solana Devnet |
Import Path Changes
Import paths have changed from x402/types to @x402/core/*:
// Before
import type { Network, RouteConfig } from 'x402/types';
// After
import type { Network } from '@x402/core/types';
import type { RouteConfig } from '@x402/core/server';Migration Steps
1. Update Dependencies
Update your package.json to use the new scoped packages:
# Remove old packages
bun remove x402 x402-fetch x402-hono x402-express x402-next
# Install new packages
bun add @x402/core @x402/fetch @x402/evm @x402/hono @x402/express @x402/nextOr update your package.json directly:
{
"dependencies": {
"@x402/core": "^2.2.0",
"@x402/fetch": "^2.2.0",
"@x402/evm": "^2.2.0"
}
}2. Update Network Identifiers
Replace all old network strings with CAIP-2 format:
// Before
const config: PaymentsConfig = {
payTo: '0x...',
network: 'base-sepolia',
facilitatorUrl: 'https://facilitator.daydreams.systems'
};
// After
const config: PaymentsConfig = {
payTo: '0x...',
network: 'eip155:84532', // Base Sepolia in CAIP-2 format
facilitatorUrl: 'https://facilitator.daydreams.systems'
};3. Update Import Statements
Type Imports
// Before
import type { Network, Money, RouteConfig, RoutesConfig } from 'x402/types';
// After
import type { Network, Money } from '@x402/core/types';
import type { RouteConfig, RoutesConfig } from '@x402/core/server';Payment Middleware Imports
Hono:
// Before
import { paymentMiddleware } from 'x402-hono';
// After
import { paymentMiddleware } from '@x402/hono';Express:
// Before
import { paymentMiddleware } from 'x402-express';
// After
import { paymentMiddleware } from '@x402/express';Next.js:
// Before
import { paymentMiddleware } from 'x402-next';
// After
import { paymentMiddleware } from '@x402/next';4. Update Manual x402 Client Usage
If you're using x402 client directly (not through Lucid Agents helpers), update to the new API:
// Before
import { wrapFetchWithX402, createWallet } from 'x402-fetch';
const wallet = createWallet(privateKey, 'base');
const x402Fetch = wrapFetchWithX402(fetch, wallet);
// After
import { wrapFetchWithPayment, x402Client } from '@x402/fetch';
import { ExactEvmScheme, toClientEvmSigner } from '@x402/evm';
import { privateKeyToAccount } from 'viem/accounts';
const account = privateKeyToAccount(privateKey);
const signer = toClientEvmSigner(account);
const client = new x402Client()
.register('eip155:8453', new ExactEvmScheme(signer)) // Base
.register('eip155:84532', new ExactEvmScheme(signer)) // Base Sepolia
.register('eip155:1', new ExactEvmScheme(signer)); // Ethereum
const x402Fetch = wrapFetchWithPayment(fetch, client);5. Using Lucid Agents Helpers (Recommended)
If you use Lucid Agents payment helpers, minimal changes are needed:
import { createRuntimePaymentContext } from '@lucid-agents/payments';
// The helpers now use @x402/* packages internally
const paymentContext = await createRuntimePaymentContext({
runtime,
network: 'eip155:84532' // Just update to CAIP-2 format
});
// Use the payment-enabled fetch
const response = await paymentContext.fetchWithPayment(url, options);Framework-Specific Changes
TanStack Start
The TanStack paywall middleware has been rewritten for x402 v2:
import { createTanStackPaywall } from '@lucid-agents/tanstack';
import type { FacilitatorConfig } from '@x402/core/server';
const { invoke, stream } = createTanStackPaywall({
runtime: agentRuntime,
basePath: '/api/agent',
facilitator: {
url: 'https://facilitator.daydreams.systems'
} satisfies FacilitatorConfig
});Hono
Update to use the new @x402/hono package:
import { createHonoPaywall } from '@lucid-agents/hono';
const paywall = createHonoPaywall({
runtime: agentRuntime,
basePath: '/api/agent'
});
app.use('/api/agent/*', paywall);Express
Update to use the new @x402/express package:
import { createExpressPaywall } from '@lucid-agents/express';
const paywall = createExpressPaywall({
runtime: agentRuntime,
basePath: '/api/agent'
});
app.use('/api/agent', paywall);Test Updates
Mock Facilitator Responses
Tests now need to mock v2 facilitator responses:
import { beforeAll, afterAll } from 'bun:test';
const mockFacilitatorResponse = {
kinds: [
{
scheme: "exact",
network: "eip155:84532", // CAIP-2 format
asset: {
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
decimals: 6,
eip712: {
name: "USDC",
version: "2"
}
}
}
]
};
let originalFetch: typeof globalThis.fetch;
beforeAll(() => {
originalFetch = globalThis.fetch;
globalThis.fetch = async (input, init) => {
const url = typeof input === "string"
? input
: input instanceof URL
? input.toString()
: input.url;
if (url.includes("/supported")) {
return new Response(JSON.stringify(mockFacilitatorResponse), {
status: 200,
headers: { "Content-Type": "application/json" }
});
}
return originalFetch(input, init);
};
});
afterAll(() => {
globalThis.fetch = originalFetch;
});Network Identifiers in Tests
Update all network strings in test fixtures:
// Before
const payments: PaymentsConfig = {
payTo: "0x...",
network: "base-sepolia",
facilitatorUrl: "https://facilitator.test"
};
// After
const payments: PaymentsConfig = {
payTo: "0x...",
network: "eip155:84532",
facilitatorUrl: "https://facilitator.test"
};Environment Variables
If you're using environment variables for network configuration, update them:
# Before
NETWORK=base-sepolia
# After
NETWORK=eip155:84532Update your environment variable documentation:
/**
* Required environment variables:
* - NETWORK - Network identifier in CAIP-2 format (e.g., eip155:84532 for Base Sepolia)
*/Breaking Changes Summary
Required Changes
- ✅ Update packages: Replace all
x402*packages with@x402/* - ✅ Update networks: Convert to CAIP-2 format (e.g.,
"base"→"eip155:8453") - ✅ Update imports: Change import paths from
x402/typesto@x402/core/* - ✅ Update tests: Mock v2 facilitator responses and use CAIP-2 networks
Optional (If Using Raw x402 Client)
- ✅ Update client creation: Use new
x402ClientAPI withregister()method - ✅ Update signers: Use
toClientEvmSignerfrom@x402/evm
Supported Networks
After migration, these networks are supported:
EVM Networks
eip155:8453- Base Mainneteip155:84532- Base Sepoliaeip155:1- Ethereum Mainneteip155:11155111- Ethereum Sepolia
Solana Networks
solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp- Solana Mainnetsolana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1- Solana Devnet
Troubleshooting
TypeScript Errors
Error: Type '"base-sepolia"' is not assignable to type '${string}:${string}'
Solution: Update the network string to CAIP-2 format:
network: 'eip155:84532' // Instead of 'base-sepolia'Error: Cannot find module 'x402/types'
Solution: Update import to use scoped package:
import type { Network } from '@x402/core/types';Runtime Errors
Error: Network 'base' is not supported
Solution: Register the network in CAIP-2 format:
const client = new x402Client()
.register('eip155:8453', new ExactEvmScheme(signer));Test Failures
Error: Facilitator mock not working
Solution: Update mock to return v2 format with kinds array (see Test Updates section)
Additional Resources
Need Help?
If you encounter issues:
- Check GitHub Issues
- Join our Discord
- Review the changelog