A Rust-based implementation of the x402 protocol with support for protocol v1 and v2.
This repository provides:
x402-rs(current crate):- Core protocol types, facilitator traits, and logic for on-chain payment verification and settlement
- Facilitator binary - production-grade HTTP server to verify and settle x402 payments
x402-axum- Axum middleware for accepting x402 payments,x402-reqwest- Wrapper for reqwest for transparent x402 payments,x402-axum-example- an example ofx402-axumusage with multi-chain support.x402-reqwest-example- an example ofx402-reqwestusage with multi-chain support.
The x402 protocol is a proposed standard for making blockchain payments directly through HTTP using native 402 Payment Required status code.
Servers declare payment requirements for specific routes. Clients send cryptographically signed payment payloads. Facilitators verify and settle payments on-chain.
docker run -v $(pwd)/config.json:/app/config.json -p 8080:8080 ghcr.io/x402-rs/x402-facilitatorOr build locally:
docker build -t x402-rs .
docker run -v $(pwd)/config.json:/app/config.json -p 8080:8080 x402-rsSee the Facilitator section below for full usage details
Use x402-axum to gate your routes behind on-chain payments:
use alloy_primitives::address;
use axum::{Router, routing::get};
use x402_axum::X402Middleware;
use x402_rs::networks::USDC;
use x402_rs::scheme::v2_eip155_exact::V2Eip155Exact;
let x402 = X402Middleware::try_from("https://facilitator.x402.rs").unwrap();
let app = Router::new().route(
"/paid-content",
get(handler).layer(
x402.with_price_tag(V2Eip155Exact::price_tag(
address!("0xYourAddress"),
USDC::base_sepolia().amount(10u64),
))
),
);See x402-axum crate docs.
Use x402-reqwest to send payments:
use x402_reqwest::{ReqwestWithPayments, ReqwestWithPaymentsBuild, X402Client};
use x402_rs::scheme::v1_eip155_exact::client::V1Eip155ExactClient;
use x402_rs::scheme::v2_eip155_exact::client::V2Eip155ExactClient;
use alloy_signer_local::PrivateKeySigner;
use std::sync::Arc;
use reqwest::Client;
let signer: Arc<PrivateKeySigner> = Arc::new("0x...".parse()?); // never hardcode real keys!
let x402_client = X402Client::new()
.register(V1Eip155ExactClient::new(signer.clone()))
.register(V2Eip155ExactClient::new(signer));
let client = Client::new()
.with_payments(x402_client)
.build();
let res = client
.get("https://example.com/protected")
.send()
.await?;The middleware automatically:
- Detects
402 Payment Requiredresponses - Extracts payment requirements from the response
- Signs payments using registered scheme clients
- Retries the request with the payment header attached
For multi-chain support (EVM and Solana), register additional scheme clients:
use x402_rs::scheme::v1_solana_exact::client::V1SolanaExactClient;
use x402_rs::scheme::v2_solana_exact::client::V2SolanaExactClient;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_keypair::Keypair;
let solana_keypair = Arc::new(Keypair::from_base58_string("..."));
let solana_rpc = Arc::new(RpcClient::new("https://api.devnet.solana.com"));
let x402_client = X402Client::new()
.register(V1Eip155ExactClient::new(evm_signer.clone()))
.register(V2Eip155ExactClient::new(evm_signer))
.register(V1SolanaExactClient::new(solana_keypair.clone(), solana_rpc.clone()))
.register(V2SolanaExactClient::new(solana_keypair, solana_rpc));| Milestone | Description | Status |
|---|---|---|
| Facilitator for Base USDC | Payment verification and settlement service, enabling real-time pay-per-use transactions for Base chain. | β Complete |
| Metrics and Tracing | Expose OpenTelemetry metrics and structured tracing for observability, monitoring, and debugging | β Complete |
| Server Middleware | Provide ready-to-use integration for Rust web frameworks such as axum and tower. | β Complete |
| Client Library | Provide a lightweight Rust library for initiating and managing x402 payment flows from Rust clients. | β Complete |
| Solana Support | Support Solana chain. | β Complete |
| Protocol v2 Support | Support x402 protocol version 2 with improved payload structure. | β Complete |
| Multiple chains and multiple tokens | Support various tokens and EVM compatible chains. | β Complete |
| Axum Middleware v2 Support | Full x402 protocol v2 support in x402-axum with multi-chain, multi-scheme architecture. | β Complete |
| Reqwest Client v2 Support | Full x402 protocol v2 support in x402-reqwest with multi-chain, multi-scheme architecture. | β Complete |
| Buiild your own facilitator hooks | Pre/post hooks for analytics, access control, and auditability. | π Planned |
| Bazaar Extension | Marketplace integration for discovering and purchasing x402-protected resources. | π Planned |
| Gasless Approval Flow | Support for Permit2 and ERC20 approvals to enable gasless payment authorization. | π Planned |
| Upto Scheme | Payment scheme supporting "up to" amount payments with flexible pricing. | π Planned |
| Deferred Scheme | Payment scheme supporting deferred settlement and payment scheduling. | π Planned |
The initial focus is on establishing a stable, production-quality Rust SDK and middleware ecosystem for x402 integration.
The x402-rs crate (this repo) provides a runnable x402 facilitator binary. The Facilitator role simplifies adoption of x402 by handling:
- Payment verification: Confirming that client-submitted payment payloads match the declared requirements.
- Payment settlement: Submitting validated payments to the blockchain and monitoring their confirmation.
By using a Facilitator, servers (sellers) do not need to:
- Connect directly to a blockchain.
- Implement complex cryptographic or blockchain-specific payment logic.
Instead, they can rely on the Facilitator to perform verification and settlement, reducing operational overhead and accelerating x402 adoption. The Facilitator never holds user funds. It acts solely as a stateless verification and execution layer for signed payment payloads.
For a detailed overview of the x402 payment flow and Facilitator role, see the x402 protocol documentation.
Create a config.json file with your chain and scheme configuration:
{
"port": 8080,
"host": "0.0.0.0",
"chains": {
"eip155:84532": {
"eip1559": true,
"flashblocks": true,
"signers": ["$EVM_PRIVATE_KEY"],
"rpc": [
{
"http": "https://sepolia.base.org",
"rate_limit": 50
}
]
},
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG": {
"signer": "$SOLANA_PRIVATE_KEY",
"rpc": "https://api.devnet.solana.com",
"pubsub": "wss://api.devnet.solana.com"
}
},
"schemes": [
{
"id": "v1-eip155-exact",
"chains": "eip155:*"
},
{
"id": "v2-eip155-exact",
"chains": "eip155:*"
},
{
"id": "v1-solana-exact",
"chains": "solana:*"
},
{
"id": "v2-solana-exact",
"chains": "solana:*"
}
]
}Configuration structure:
chains: A map of CAIP-2 chain identifiers to chain-specific configuration- EVM chains (
eip155:*): Configuresigners(array of private keys),rpcendpoints, and optionaleip1559/flashblocksflags - Solana chains (
solana:*): Configuresigner(single private key),rpcendpoint, and optionalpubsubendpoint
- EVM chains (
schemes: List of payment schemes to enableid: Scheme identifier in formatv{version}-{namespace}-{name}(e.g.,v2-eip155-exact)chains: Chain pattern to match (e.g.,eip155:*for all EVM chains,eip155:84532for specific chain)
Environment variable references:
Private keys can reference environment variables using $VAR or ${VAR} syntax:
"signers": ["$EVM_PRIVATE_KEY"]Then set the environment variable:
export EVM_PRIVATE_KEY=0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefPrebuilt Docker images are available at GitHub Container Registry: ghcr.io/x402-rs/x402-facilitator
Run the container:
docker run -v $(pwd)/config.json:/app/config.json -p 8080:8080 ghcr.io/x402-rs/x402-facilitatorOr build a Docker image locally:
docker build -t x402-rs .
docker run -v $(pwd)/config.json:/app/config.json -p 8080:8080 x402-rsYou can also pass environment variables for private keys:
docker run -v $(pwd)/config.json:/app/config.json \
-e EVM_PRIVATE_KEY=0x... \
-e SOLANA_PRIVATE_KEY=... \
-p 8080:8080 ghcr.io/x402-rs/x402-facilitatorThe container:
- Exposes port
8080(or a port you configure inconfig.json). - Starts on http://localhost:8080 by default.
- Requires minimal runtime dependencies (based on
debian:bullseye-slim).
If you are building an x402-powered application, update the Facilitator URL to point to your self-hosted instance.
βΉοΈ Tip: For production deployments, ensure your Facilitator is reachable via HTTPS and protect it against public abuse.
If you use Hono and x402-hono
From [x402.org Quickstart for Sellers](https://x402.gitbook.io/x402/getting-started/quickstart-for-sellers):import { Hono } from "hono";
import { serve } from "@hono/node-server";
import { paymentMiddleware } from "@x402/hono";
import { x402ResourceServer, HTTPFacilitatorClient } from "@x402/core/server";
import { registerExactEvmScheme } from "@x402/evm/exact/server";
const app = new Hono();
const payTo = "0xYourAddress";
const facilitatorClient = new HTTPFacilitatorClient({
url: "https://x402.org/facilitator"
});
const server = new x402ResourceServer(facilitatorClient);
registerExactEvmScheme(server);
app.use(
paymentMiddleware(
{
"/protected-route": {
accepts: [
{
scheme: "exact",
price: "$0.10",
network: "eip155:84532",
payTo,
},
],
description: "Access to premium content",
mimeType: "application/json",
},
},
server,
),
);
app.get("/protected-route", (c) => {
return c.json({ message: "This content is behind a paywall" });
});
serve({ fetch: app.fetch, port: 3000 });If you use `x402-axum`
use alloy_primitives::address;
use axum::{Router, routing::get};
use axum::response::IntoResponse;
use http::StatusCode;
use x402_axum::X402Middleware;
use x402_rs::networks::USDC;
use x402_rs::scheme::v2_eip155_exact::V2Eip155Exact;
let x402 = X402Middleware::new("https://facilitator.x402.rs"); // π Your self-hosted Facilitator
let app = Router::new().route(
"/paid-content",
get(handler).layer(
x402.with_price_tag(V2Eip155Exact::price_tag(
address!("0xYourAddress"),
USDC::base_sepolia().amount(10u64),
))
),
);
async fn handler() -> impl IntoResponse {
(StatusCode::OK, "This is VIP content!")
}The service reads configuration from a JSON file (config.json by default) or via CLI argument --config <path>.
{
"port": 8080,
"host": "0.0.0.0",
"chains": { ... },
"schemes": [ ... ]
}| Option | Type | Default | Description |
|---|---|---|---|
port |
number | 8080 |
HTTP server port (can also be set via PORT env var) |
host |
string | "0.0.0.0" |
HTTP host to bind to (can also be set via HOST env var) |
chains |
object | {} |
Map of CAIP-2 chain IDs to chain configuration |
schemes |
array | [] |
List of payment schemes to enable |
{
"eip155:84532": {
"eip1559": true,
"flashblocks": true,
"receipt_timeout_secs": 30,
"signers": ["$EVM_PRIVATE_KEY"],
"rpc": [
{
"http": "https://sepolia.base.org",
"rate_limit": 50
}
]
}
}| Option | Type | Required | Default | Description |
|---|---|---|---|---|
signers |
array | β | - | Array of private keys (hex format, 0x-prefixed) or env var references |
rpc |
array | β | - | Array of RPC endpoint configurations |
rpc[].http |
string | β | - | HTTP URL for the RPC endpoint |
rpc[].rate_limit |
number | β | - | Rate limit for requests per second |
eip1559 |
boolean | β | true |
Use EIP-1559 transaction type (type 2) instead of legacy transactions |
flashblocks |
boolean | β | false |
Estimate gas against "latest" block to accommodate flashblocks-enabled RPC semantics |
receipt_timeout_secs |
number | β | 30 |
Timeout for waiting for transaction receipt |
{
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": {
"signer": "$SOLANA_PRIVATE_KEY",
"rpc": "https://api.mainnet-beta.solana.com",
"pubsub": "wss://api.mainnet-beta.solana.com",
"max_compute_unit_limit": 400000,
"max_compute_unit_price": 1000000
}
}| Option | Type | Required | Default | Description |
|---|---|---|---|---|
signer |
string | β | - | Private key (base58 format, 64 bytes) or env var reference |
rpc |
string | β | - | HTTP URL for the RPC endpoint |
pubsub |
string | β | - | WebSocket URL for pubsub notifications |
max_compute_unit_limit |
number | β | 400000 |
Maximum compute unit limit for transactions |
max_compute_unit_price |
number | β | 1000000 |
Maximum compute unit price for transactions |
{
"schemes": [
{
"enabled": true,
"id": "v2-eip155-exact",
"chains": "eip155:*",
"config": {}
}
]
}| Option | Type | Required | Default | Description |
|---|---|---|---|---|
enabled |
boolean | β | true |
Whether this scheme is enabled |
id |
string | β | - | Scheme identifier: v{version}-{namespace}-{name} |
chains |
string | β | - | Chain pattern: eip155:*, solana:*, or specific chain ID |
config |
object | β | - | Scheme-specific configuration |
Important: Schemes must be explicitly listed in the schemes array to be enabled. If a scheme is not in the configuration, it will not be available for payment verification or settlement.
Available schemes:
v1-eip155-exact- ERC-3009 transferWithAuthorization for EVM chains (protocol v1)v2-eip155-exact- ERC-3009 transferWithAuthorization for EVM chains (protocol v2)v1-solana-exact- SPL token transfer for Solana (protocol v1)v2-solana-exact- SPL token transfer for Solana (protocol v2)
Environment variables can be used for:
- Private keys: Reference in config with
$VARor${VAR}syntax - Server settings:
PORTandHOSTas fallbacks if not in config file - Logging:
RUST_LOGfor log level (e.g.,info,debug,trace)
The facilitator emits OpenTelemetry-compatible traces and metrics to standard endpoints, making it easy to integrate with tools like Honeycomb, Prometheus, Grafana, and others. Tracing spans are annotated with HTTP method, status code, URI, latency, other request and process metadata.
To enable tracing and metrics export, set the appropriate OTEL_ environment variables:
# For Honeycomb, for example:
# Endpoint URL for sending OpenTelemetry traces and metrics
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.honeycomb.io:443
# Comma-separated list of key=value pairs to add as headers
OTEL_EXPORTER_OTLP_HEADERS=x-honeycomb-team=your_api_key,x-honeycomb-dataset=x402-rs
# Export protocol to use for telemetry. Supported values: `http/protobuf` (default), `grpc`
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobufThe service automatically detects and initializes exporters if OTEL_EXPORTER_OTLP_* variables are provided.
The Facilitator supports any network you configure in config.json. Common chain identifiers:
| Network | CAIP-2 Chain ID | Notes |
|---|---|---|
| Base Sepolia Testnet | eip155:84532 |
Testnet, Recommended for testing |
| Base Mainnet | eip155:8453 |
Mainnet |
| Ethereum Mainnet | eip155:1 |
Mainnet |
| Avalanche Fuji Testnet | eip155:43113 |
Testnet |
| Avalanche C-Chain Mainnet | eip155:43114 |
Mainnet |
| Polygon Amoy Testnet | eip155:80002 |
Testnet |
| Polygon Mainnet | eip155:137 |
Mainnet |
| Sei Testnet | eip155:713715 |
Testnet |
| Sei Mainnet | eip155:1329 |
Mainnet |
| Solana Mainnet | solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp |
Mainnet |
| Solana Devnet | solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG |
Testnet, Recommended for testing |
Networks are enabled by adding them to the chains section in your config.json.
βΉοΈ Tip: For initial development and testing, you can start with Base Sepolia (
eip155:84532) or Solana Devnet only.
Prerequisites:
- Rust 1.80+
cargoand a working toolchain
Build locally:
cargo buildRun with a config file:
cargo run -- --config config.jsonOr place config.json in the current directory (it will be auto-detected):
cargo runFeel free to open issues or pull requests to improve x402 support in the Rust ecosystem.