8 releases
Uses new Rust 2024
new 0.2.4 | Jul 8, 2025 |
---|---|
0.2.3 | Jul 3, 2025 |
0.2.2 | Jun 28, 2025 |
0.1.2 | Jun 12, 2025 |
0.1.0 | May 25, 2025 |
#588 in HTTP server
657 downloads per month
185KB
3K
SLoC
x402-axum
Axum middleware for protecting routes with x402 protocol payments.
This crate provides a drop-in tower::Layer
that intercepts incoming requests,
validates X-Payment
headers using a configured x402 facilitator,
and settles the payment before responding.
If no valid payment is provided, a 402 Payment Required
response is returned with details about accepted assets and amounts.
Features
- Built for Axum
- Fluent builder API for composing payment requirements and prices
- Enforces on-chain payment before executing protected handlers
- Returns standards-compliant
402 Payment Required
responses - Emits rich tracing spans with optional OpenTelemetry integration (
telemetry
feature) - Compatible with any x402 facilitator (remote or in-process)
Installation
Add to your Cargo.toml
:
x402-axum = "0.2"
If you want to enable tracing and OpenTelemetry support, use the telemetry feature (make sure to register a tracing subscriber in your application):
x402-axum = { version = "0.2", features = ["telemetry"] }
Specifying Prices
Prices in x402 are defined using the PriceTag
struct. A PriceTag
includes:
- Asset (
asset
) — the ERC-20 token used for payment - Amount (
amount
) — the required token amount, either as an integer or a human-readable decimal - Recipient (
pay_to
) — the address that will receive the tokens
You can construct PriceTag
s directly or use fluent builder helpers that simplify common flows.
Asset
Bring Your Own Token
If you're integrating a custom token, define it using TokenDeployment
. This includes token address, decimals, the network it lives on, and EIP-712 metadata (name/version):
use x402_rs::types::{TokenAsset, TokenDeployment, EvmAddress, TokenAssetEip712};
use x402_rs::network::Network;
let asset = TokenDeployment {
asset: TokenAsset {
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e".parse().unwrap(),
network: Network::BaseSepolia,
},
decimals: 6,
eip712: TokenAssetEip712 {
name: "MyToken".into(),
version: "1".into(),
},
};
Known tokens (like USDC)
For common stablecoins like USDC, you can use the convenience struct USDCDeployment
:
use x402_rs::network::{Network, USDCDeployment};
let asset = USDCDeployment::by_network(Network::BaseSepolia);
Amount
Human-Readable Amounts
Use .amount("0.025")
on asset to define a price using a string or a number.
This will be converted to the correct on-chain amount based on the asset’s decimals:
usdc.amount("0.025") // → 25000 for 0.025 USDC with 6 decimals
Raw Token Amounts
If you already know the amount in base units (e.g. 25000 for 0.025 USDC with 6 decimals), use .token_amount(...)
:
usdc.token_amount(25000)
This will use the value onchain verbatim.
Recipient
Use .pay_to(...)
to set the address that should receive the payment.
let price_tag = usdc.amount(0.025).pay_to("0xYourAddress").unwrap();
Integrating with Middleware
Once you’ve created your PriceTag, pass it to the middleware:
let x402 = X402Middleware::try_from("https://x402.org/facilitator/").unwrap();
let usdc = USDCDeployment::by_network(Network::BaseSepolia);
let app = Router::new().route("/paid-content", get(handler).layer(
// To allow multiple options (e.g., USDC or another token), chain them:
x402
.with_price_tag(usdc.amount("0.025").pay_to("0xYourAddress").unwrap())
.or_price_tag(other_token.amount("0.035").pay_to("0xYourAddress").unwrap())
),
);
You can extract shared fields like the payment recipient, then vary prices per route:
let x402 = X402Middleware::try_from("https://x402.org/facilitator/").unwrap();
let asset = USDCDeployment::by_network(Network::BaseSepolia)
.pay_to("0xYourAddress"); // Both /vip-content and /extra-vip-content are paid to 0xYourAddress
let app: Router = Router::new()
.route(
"/vip-content",
get(my_handler).layer(x402.with_price_tag(asset.amount("0.025").unwrap())),
)
.route(
"/extra-vip-content",
get(my_handler).layer(x402.with_price_tag(asset.amount("0.25").unwrap())),
);
Example
use axum::{Router, routing::get, Json};
use x402_axum::X402Middleware;
use x402_axum::price::IntoPriceTag;
use x402_rs::network::{Network, USDCDeployment};
use http::StatusCode;
use serde_json::json;
#[tokio::main]
async fn main() {
let x402 = X402Middleware::try_from("https://x402.org/facilitator/").unwrap();
let usdc = USDCDeployment::by_network(Network::BaseSepolia)
.pay_to("0xYourAddress");
let app = Router::new().route(
"/paid-content",
get(handler).layer(
x402.with_description("Access to /paid-content")
.with_price_tag(usdc.amount(0.01).unwrap())
),
);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
.await
.expect("Failed to start server");
println!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}
async fn handler() -> (StatusCode, Json<serde_json::Value>) {
(StatusCode::OK, Json(json!({ "message": "Hello, payer!" })))
}
HTTP Behavior
If no valid payment is included, the middleware responds with:
// HTTP/1.1 402 Payment Required
// Content-Type: application/json
{
"error": "X-PAYMENT header is required",
"accepts": [
// Payment Requirements
],
"x402Version": 1
}
Optional Telemetry
If the telemetry
feature is enabled, the middleware emits structured tracing spans such as:
x402.handle_request
,x402.verify_payment
,x402.settle_payment
,
You can connect these to OpenTelemetry exporters like Jaeger, Tempo, or Otel Collector.
To enable:
[dependencies]
x402-axum = { version = "0.2", features = ["telemetry"] }
Related Crates
- x402-rs: Core x402 types, facilitator traits, helpers.
License
Dependencies
~26–40MB
~629K SLoC