Clients 6 min read

Making Payments

Learn how to build x402 clients that can pay for services

x402 clients automatically handle the payment flow:

  1. Make a request to a protected endpoint
  2. Receive 402 Payment Required with payment details in Payment-Required header
  3. Create and sign a payment transaction
  4. Retry the request with payment proof in Payment-Signature header
  5. 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: