| Crates.io | x402-axum |
| lib.rs | x402-axum |
| version | 0.8.0 |
| created_at | 2025-05-25 17:28:27.30546+00 |
| updated_at | 2026-01-19 15:16:03.580509+00 |
| description | Axum middleware for enforcing x402 protocol payments on protected routes |
| homepage | https://x402.rs |
| repository | https://github.com/x402-rs/x402-rs |
| max_upload_size | |
| id | 1688560 |
| size | 303,331 |
Axum middleware for protecting routes with x402 protocol payments.
This crate provides a drop-in tower::Layer that intercepts incoming requests,
validates payment headers using a configured x402 facilitator,
and settles the payment before or after request execution (configurable).
If no valid payment is provided, a 402 Payment Required response is returned with details about accepted assets and amounts.
402 Payment Required responsestelemetry feature)Add to your Cargo.toml:
x402-axum = "0.8"
If you want to enable tracing and OpenTelemetry support, use the telemetry feature:
x402-axum = { version = "0.8", features = ["telemetry"] }
use axum::{Router, routing::get};
use axum::response::IntoResponse;
use http::StatusCode;
use x402_axum::X402Middleware;
use x402_rs::networks::{KnownNetworkEip155, USDC};
use x402_rs::scheme::v1_eip155_exact::V1Eip155Exact;
let x402 = X402Middleware::new("https://facilitator.x402.rs");
let app: Router = Router::new().route(
"/protected",
get(my_handler).layer(
x402.with_price_tag(V1Eip155Exact::price_tag(
"0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07".parse().unwrap(),
USDC::base_sepolia().amount(10),
))
),
);
async fn my_handler() -> impl IntoResponse {
(StatusCode::OK, "This is VIP content!")
}
By default, the middleware settles payments after request execution. You can change this:
let x402 = X402Middleware::new("https://facilitator.x402.rs")
.settle_before_execution(); // Settle before executing the handler
Or explicitly set settlement after execution (default behavior):
let x402 = X402Middleware::new("https://facilitator.x402.rs")
.settle_after_execution(); // Settle after successful request execution
The middleware supports dynamic pricing through the with_dynamic_price method, which allows you to compute prices per-request based on headers, URI, or other runtime factors:
use x402_axum::X402Middleware;
use x402_rs::networks::{KnownNetworkEip155, USDC};
use x402_rs::scheme::v2_eip155_exact::V2Eip155Exact;
let x402 = X402Middleware::new("https://facilitator.x402.rs");
let app = Router::new().route(
"/api/data",
get(handler).layer(x402.with_dynamic_price(|headers, uri, _base_url| {
// Check for discount query parameter
let has_discount = uri.query().map(|q| q.contains("discount")).unwrap_or(false);
let amount = if has_discount { 50 } else { 100 };
async move {
vec![V2Eip155Exact::price_tag(
"0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07".parse().unwrap(),
USDC::base_sepolia().amount(amount),
)]
}
})),
);
When the dynamic pricing callback returns an empty vector, the middleware bypasses payment enforcement entirely and forwards the request directly to the handler. This is useful for implementing:
use x402_axum::X402Middleware;
use x402_rs::networks::{KnownNetworkEip155, USDC};
use x402_rs::scheme::v2_eip155_exact::V2Eip155Exact;
let x402 = X402Middleware::new("https://facilitator.x402.rs");
let app = Router::new().route(
"/api/data",
get(handler).layer(x402.with_dynamic_price(|_headers, uri, _base_url| {
// Check if "free" query parameter is present
let is_free = uri.query().map(|q| q.contains("free")).unwrap_or(false);
async move {
if is_free {
// Return empty vector to bypass payment enforcement
vec![]
} else {
// Normal pricing - payment required
vec![V2Eip155Exact::price_tag(
"0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07".parse().unwrap(),
USDC::base_sepolia().amount(100),
)]
}
}
})),
);
With this configuration:
GET /api/data → Returns 402 Payment RequiredGET /api/data?free → Bypasses payment, returns content directlyPrices are defined using the scheme-specific price tag types from x402_rs. The crate includes
built-in schemes for common protocols:
V1Eip155Exact::price_tag() - V1 EIP-155 exact payment on EVM chains (ERC-3009)V2Eip155Exact::price_tag() - V2 EIP-155 exact payment on EVM chains (ERC-3009)V1SolanaExact::price_tag() - V1 Solana exact payment (SPL token transfer)V2SolanaExact::price_tag() - V2 Solana exact payment (SPL token transfer)use x402_axum::X402Middleware;
use x402_rs::networks::{KnownNetworkEip155, KnownNetworkSolana, USDC};
use x402_rs::scheme::v1_eip155_exact::V1Eip155Exact;
use x402_rs::scheme::v1_solana_exact::V1SolanaExact;
let x402 = X402Middleware::new("https://facilitator.x402.rs");
// Accept both EVM and Solana payments
let app = Router::new().route(
"/premium",
get(handler).layer(
x402.with_price_tag(V1Eip155Exact::price_tag(
"0xBAc675C310721717Cd4A37F6cbeA1F081b1C2a07".parse().unwrap(),
USDC::base_sepolia().amount(10),
)).with_price_tag(V1SolanaExact::price_tag(
"EGBQqKn968sVv5cQh5Cr72pSTHfxsuzq7o7asqYB5uEV".parse().unwrap(),
USDC::solana().amount(100),
))
),
);
You can implement custom payment schemes by implementing the [PaygateProtocol] trait from
x402_axum::paygate. This allows you to support additional blockchains, payment mechanisms,
or otherwise custom schemes.
To create a custom scheme, you'll need to:
price_tag method - A static method that constructs the protocol-specific price tagPaygateProtocol] - Handle verification, error responses, and facilitator enrichmentExample structure for a custom scheme:
use axum_core::response::Response;
use x402_axum::paygate::{PaygateError, PaygateProtocol, ResourceInfoBuilder, VerificationError};
use x402_rs::proto::{self, v2, SupportedResponse};
/// Your custom scheme struct
pub struct MyCustomScheme;
impl MyCustomScheme {
/// Create a price tag for this scheme
pub fn price_tag(
pay_to: String,
asset: String,
amount: u64,
) -> v2::PriceTag {
v2::PriceTag {
requirements: v2::PaymentRequirements {
scheme: "my-custom-scheme".to_string(),
pay_to,
asset,
network: /* your chain id */,
amount: amount.to_string(),
max_timeout_seconds: 300,
extra: None,
},
enricher: None, // Or Some(Arc::new(your_enricher_fn)) if needed
}
}
}
// Implement PaygateProtocol for the price tag type (v2::PriceTag in this case)
// Note: PaygateProtocol is already implemented for v1::PriceTag and v2::PriceTag
// You only need to implement it if you're creating a completely custom price tag type
For a complete example, see the How to Write a Scheme guide.
By default, the middleware settles payments after request execution. You can change this:
let x402 = X402Middleware::new("https://facilitator.x402.rs")
.settle_before_execution(); // Settle before executing the handler
Settling before execution is useful when you want to:
Set a base URL for computing resource URLs dynamically:
use url::Url;
let x402 = X402Middleware::new("https://facilitator.x402.rs")
.with_base_url(Url::parse("https://api.example.com").unwrap());
Set an explicit resource URL (recommended in production):
use url::Url;
let app = Router::new().route(
"/premium-content",
get(handler).layer(
x402.with_price_tag(V1Eip155Exact::price_tag(
recipient,
USDC::base_sepolia().amount(10),
)).with_resource(Url::parse("https://api.example.com/premium-content").unwrap())
),
);
let app = Router::new().route(
"/api/data",
get(handler).layer(
x402.with_price_tag(price_tag)
.with_description("Access to premium API")
.with_mime_type("application/json")
),
);
Configure the TTL for caching the facilitator's supported response:
use std::time::Duration;
let x402 = X402Middleware::new("https://facilitator.x402.rs")
.with_supported_cache_ttl(Duration::from_secs(300)); // 5 minutes
To disable caching entirely:
let x402 = X402Middleware::new("https://facilitator.x402.rs")
.with_supported_cache_ttl(Duration::from_secs(0));
If no valid payment is included, the middleware responds with a 402 Payment Required:
V1 Protocol:
// HTTP/1.1 402 Payment Required
// Content-Type: application/json
{
"error": "X-PAYMENT header is required",
"accepts": [...],
"x402Version": "1"
}
V2 Protocol:
// HTTP/1.1 402 Payment Required
// Payment-Required: <base64-encoded PaymentRequired>
The middleware provides detailed error information through the VerificationError and PaygateError types:
VerificationError::PaymentHeaderRequired: Missing payment headerVerificationError::InvalidPaymentHeader: Malformed payment headerVerificationError::NoPaymentMatching: No matching payment requirements foundVerificationError::VerificationFailed: Payment verification failedPaygateError::Settlement: Payment settlement failedThese errors are automatically converted to appropriate 402 Payment Required responses with detailed error messages.
If the telemetry feature is enabled, the middleware emits structured tracing spans:
x402.handle_requestx402.verify_paymentx402.settle_paymentYou can connect these to OpenTelemetry exporters like Jaeger, Tempo, or Otel Collector.