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

Download history 88/week @ 2025-05-20 31/week @ 2025-05-27 125/week @ 2025-06-03 133/week @ 2025-06-10 132/week @ 2025-06-17 235/week @ 2025-06-24 151/week @ 2025-07-01

657 downloads per month

Apache-2.0

185KB
3K SLoC

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 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 PriceTags 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"] }
  • x402-rs: Core x402 types, facilitator traits, helpers.

License

Apache-2.0

Dependencies

~26–40MB
~629K SLoC