x402-axum

Crates.iox402-axum
lib.rsx402-axum
version0.8.0
created_at2025-05-25 17:28:27.30546+00
updated_at2026-01-19 15:16:03.580509+00
descriptionAxum middleware for enforcing x402 protocol payments on protected routes
homepagehttps://x402.rs
repositoryhttps://github.com/x402-rs/x402-rs
max_upload_size
id1688560
size303,331
Sergey Ukustov (ukstv)

documentation

https://docs.rs/x402-axum

README

x402-axum

Crates.io Docs.rs

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.

Features

  • Built for Axum
  • Full Protocol v2 Support - Complete implementation of x402 protocol v2
  • Multi-chain Support - EVM (EIP-155) and Solana chains
  • Multi-scheme Architecture - Support for various payment schemes
  • Fluent builder API for configuring payment requirements
  • Configurable settlement timing (before or after request execution)
  • Returns standards-compliant 402 Payment Required responses
  • Emits rich tracing spans with optional OpenTelemetry integration (telemetry feature)
  • Compatible with any x402 facilitator
  • Configurable facilitator cache TTL for performance optimization

Installation

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"] }

Quickstart

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!")
}

Settlement Timing

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

Dynamic Pricing

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),
            )]
        }
    })),
);

Conditional Free Access (Empty Price Tags)

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:

  • Free tiers or promotional access
  • Conditional pricing based on user authentication
  • A/B testing with paid vs free access
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 Required
  • GET /api/data?free → Bypasses payment, returns content directly

Defining Prices

Prices 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)

Built-in Schemes

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),
        ))
    ),
);

Custom Schemes

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:

  1. Define your scheme struct - A unit struct that serves as a namespace for your scheme
  2. Add a price_tag method - A static method that constructs the protocol-specific price tag
  3. Implement [PaygateProtocol] - Handle verification, error responses, and facilitator enrichment

Example 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.

Settlement Timing

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:

  • Avoid failed settlements requiring external retry mechanisms
  • Prevent payment authorization expiration before final settlement
  • Ensure payment is settled before granting access to the resource

Configuration

Base URL

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());

Resource URL

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())
    ),
);

Description and MIME Type

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")
    ),
);

Facilitator Cache TTL

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));

HTTP Behavior

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>

Error Handling

The middleware provides detailed error information through the VerificationError and PaygateError types:

  • VerificationError::PaymentHeaderRequired: Missing payment header
  • VerificationError::InvalidPaymentHeader: Malformed payment header
  • VerificationError::NoPaymentMatching: No matching payment requirements found
  • VerificationError::VerificationFailed: Payment verification failed
  • PaygateError::Settlement: Payment settlement failed

These errors are automatically converted to appropriate 402 Payment Required responses with detailed error messages.

Optional Telemetry

If the telemetry feature is enabled, the middleware emits structured tracing spans:

  • x402.handle_request
  • x402.verify_payment
  • x402.settle_payment

You can connect these to OpenTelemetry exporters like Jaeger, Tempo, or Otel Collector.

Related Crates

  • x402-rs: Core x402 types, facilitator traits, helpers.
  • x402-reqwest: Reqwest middleware for paying x402 requests.

License

Apache-2.0

Commit count: 625

cargo fmt