Making Payments
Learn how to build x402 clients that can pay for services
x402 clients automatically handle the payment flow:
- Make a request to a protected endpoint
- Receive
402 Payment Requiredwith payment details inPayment-Requiredheader - Create and sign a payment transaction
- Retry the request with payment proof in
Payment-Signatureheader - Receive the requested resource
Rust (x402-reqwest)
The x402-reqwest crate provides middleware for reqwest that handles x402 payments automatically.
Installation
[dependencies]
alloy-signer-local = "1.4"
reqwest = "0.13"
solana-client = "3.1.4"
solana-keypair = "3.1.0"
tokio = "1"
x402-chain-eip155 = { version = "1.1", features = ["client"] }
x402-chain-solana = { version = "1.1", features = ["client"] }
x402-reqwest = "1.1"
EVM Payments (Base, Ethereum, etc.)
use alloy_signer_local::PrivateKeySigner;
use reqwest::Client;
use std::sync::Arc;
use x402_reqwest::{ReqwestWithPayments, ReqwestWithPaymentsBuild, X402Client};
use x402_chain_eip155::{V1Eip155ExactClient, V2Eip155ExactClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load your wallet (never hardcode real keys!)
let signer: PrivateKeySigner = "0x...".parse()?;
let signer = Arc::new(signer);
// Create x402 client and register EVM schemes
let x402_client = X402Client::new()
.register(V1Eip155ExactClient::new(signer.clone()))
.register(V2Eip155ExactClient::new(signer));
// Build reqwest client with middleware
let http_client = Client::new()
.with_payments(x402_client)
.build();
// Make request - payment handled automatically
let response = http_client
.get("http://localhost:3000/protected-route")
.send()
.await?;
println!("Response: {:?}", response.text().await?);
Ok(())
}
Solana Payments
use reqwest::Client;
use solana_keypair::Keypair;
use solana_client::nonblocking::rpc_client::RpcClient;
use std::sync::Arc;
use x402_reqwest::{ReqwestWithPayments, ReqwestWithPaymentsBuild, X402Client};
use x402_chain_solana::{V1SolanaExactClient, V2SolanaExactClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Load Solana wallet
let keypair = Keypair::from_base58_string("...");
let keypair = Arc::new(keypair);
let rpc_client = Arc::new(RpcClient::new("https://api.mainnet-beta.solana.com".to_string()));
// Create x402 client and register Solana schemes
let x402_client = X402Client::new()
.register(V1SolanaExactClient::new(keypair.clone(), rpc_client.clone()))
.register(V2SolanaExactClient::new(keypair, rpc_client));
// Build reqwest client
let http_client = Client::new()
.with_payments(x402_client)
.build();
// Make request
let response = http_client
.get("http://localhost:3000/protected-route")
.send()
.await?;
println!("Response: {:?}", response.text().await?);
Ok(())
}
Configuration
Payment Selection allows for more flexible payment strategies.
Preferring specific networks:
Use PreferChain to prioritize certain networks (e.g., Base) over others.
use x402_types::scheme::client::PreferChain;
// Prefer Base Mainnet, then any EVM chain, then anything else
let selector = PreferChain::new(vec![
"eip155:8453".parse().unwrap(), // Base Mainnet
"eip155:*".parse().unwrap(), // Any EVM chain
]);
let client = X402Client::new()
.with_selector(selector)
.register(...);
Limiting Payment Amount:
Use MaxAmount to reject payments exceeding a certain value.
use x402_types::scheme::client::MaxAmount;
use alloy_primitives::U256;
// Limit payments to 1 USDC (1_000_000 atomic units)
let client = X402Client::new()
.with_selector(MaxAmount(U256::from(1_000_000)))
.register(...);
Custom Logic:
You can implement the PaymentSelector trait to define custom logic (e.g., “Prefer Solana if amount < $1, otherwise Base”).
TypeScript / JavaScript
For TypeScript clients, use @x402/fetch or @x402/axios.
Installation
# Using fetch
npm install @x402/fetch @x402/core @x402/evm viem
# Or using Axios
npm install @x402/axios @x402/core @x402/evm axios viem
Using @x402/fetch
@x402/fetch extends the native fetch API to handle 402 responses automatically.
import { wrapFetchWithPayment } from "@x402/fetch";
import { x402Client } from "@x402/core/client";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
// Create signer
const signer = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
// Create x402 client and register EVM scheme
const client = new x402Client();
registerExactEvmScheme(client, { signer });
// Wrap fetch with payment handling
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
// Make request - payment is handled automatically
const response = await fetchWithPayment("https://api.example.com/paid-endpoint", {
method: "GET",
});
const data = await response.json();
console.log("Response:", data);
Using @x402/axios
@x402/axios adds a payment interceptor to Axios.
import { x402Client, wrapAxiosWithPayment } from "@x402/axios";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import axios from "axios";
// Create signer
const signer = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
// Create x402 client and register EVM scheme
const client = new x402Client();
registerExactEvmScheme(client, { signer });
// Create an Axios instance with payment handling
const api = wrapAxiosWithPayment(
axios.create({ baseURL: "https://api.example.com" }),
client,
);
// Make request - payment is handled automatically
const response = await api.get("/paid-endpoint");
console.log("Response:", response.data);
Using with MCP (AI Agents)
For AI agents using Model Context Protocol, you can wrap the transport or use the client directly in your tool implementation.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import axios from "axios";
import { x402Client, wrapAxiosWithPayment } from "@x402/axios";
import { registerExactEvmScheme } from "@x402/evm/exact/client";
import { registerExactSvmScheme } from "@x402/svm/exact/client";
import { privateKeyToAccount } from "viem/accounts";
import { createKeyPairSignerFromBytes } from "@solana/kit";
import { base58 } from "@scure/base";
const evmPrivateKey = process.env.EVM_PRIVATE_KEY as `0x${string}`;
const svmPrivateKey = process.env.SVM_PRIVATE_KEY as string;
const baseURL = process.env.RESOURCE_SERVER_URL || "http://localhost:3000";
const endpointPath = process.env.ENDPOINT_PATH || "/weather";
if (!evmPrivateKey && !svmPrivateKey) {
throw new Error("At least one of EVM_PRIVATE_KEY or SVM_PRIVATE_KEY must be provided");
}
/**
* Creates an axios client configured with x402 payment support for EVM and/or SVM.
*/
async function createClient() {
const client = new x402Client();
// Register EVM scheme if private key is provided
if (evmPrivateKey) {
const evmSigner = privateKeyToAccount(evmPrivateKey);
registerExactEvmScheme(client, { signer: evmSigner });
}
// Register SVM scheme if private key is provided
if (svmPrivateKey) {
const svmSigner = await createKeyPairSignerFromBytes(base58.decode(svmPrivateKey));
registerExactSvmScheme(client, { signer: svmSigner });
}
return wrapAxiosWithPayment(axios.create({ baseURL }), client);
}
async function main() {
const api = await createClient();
// Create an MCP server
const server = new McpServer({
name: "x402 MCP Client Demo",
version: "2.0.0",
});
// Add a tool that calls the paid API
server.registerTool(
"get-data-from-resource-server",
{},
async () => {
const res = await api.get(endpointPath);
return {
content: [{ type: "text", text: JSON.stringify(res.data) }],
};
},
);
const transport = new StdioServerTransport();
await server.connect(transport);
}
main().catch(error => {
console.error(error);
process.exit(1);
});
Get Testnet Tokens
For dev purposes, you could get testnet tokens from faucets:
- Base Sepolia Faucet to get native Base Sepolia ETH
- Solana Devnet Faucet to get Solana Devnet SOL
- Circle Faucet to get Circle’s test tokens (USDC, EURC,…)