SDK 参考

Rust SDK 参考#

Crate#

Crate描述
x402-core核心:服务端、facilitator 客户端、类型、HTTP 工具、HMAC 认证
x402-axumAxum 中间件(Tower Layer/Service)
x402-evmEVM 机制:exact、aggr_deferred
注意

Rust SDK 目前提供服务端(卖方)和facilitator 客户端功能。买方侧的支付签名功能正在计划中。


核心类型#

Network / Money / Price#

Rust
pub type Network = String;
// CAIP-2 format, e.g., "eip155:196"

pub type Money = String;
// User-friendly amount, e.g., "$0.01", "0.01"

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Price {
    Money(Money),
    Asset(AssetAmount),
}

AssetAmount#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AssetAmount {
    pub asset: String,      // Token contract address
    pub amount: String,     // Amount in token's smallest unit
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub extra: Option<HashMap<String, serde_json::Value>>,
}

ResourceInfo#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ResourceInfo {
    pub url: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mime_type: Option<String>,
}

PaymentRequirements#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequirements {
    pub scheme: String,              // "exact" | "aggr_deferred"
    pub network: Network,            // CAIP-2 identifier
    pub asset: String,               // Token contract address
    pub amount: String,              // Price in token's smallest unit
    pub pay_to: String,              // Recipient wallet address
    pub max_timeout_seconds: u64,    // Authorization validity window
    #[serde(default)]
    pub extra: HashMap<String, serde_json::Value>,  // Scheme-specific data
}

PaymentRequired#

402 响应体。

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentRequired {
    #[serde(rename = "x402Version")]
    pub x402_version: u32,           // Protocol version (2)
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
    pub resource: ResourceInfo,
    pub accepts: Vec<PaymentRequirements>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub extensions: Option<HashMap<String, serde_json::Value>>,
}

PaymentPayload#

客户端的签名支付。

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PaymentPayload {
    #[serde(rename = "x402Version")]
    pub x402_version: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub resource: Option<ResourceInfo>,
    pub accepted: PaymentRequirements,
    pub payload: HashMap<String, serde_json::Value>,  // Scheme-specific signed data
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub extensions: Option<HashMap<String, serde_json::Value>>,
}

Facilitator 类型#

VerifyRequest / VerifyResponse#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifyRequest {
    #[serde(rename = "x402Version")]
    pub x402_version: u32,
    pub payment_payload: PaymentPayload,
    pub payment_requirements: PaymentRequirements,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifyResponse {
    pub is_valid: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub invalid_reason: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub invalid_message: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub payer: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub extensions: Option<HashMap<String, serde_json::Value>>,
}

SettleRequest / SettleResponse#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SettleRequest {
    #[serde(rename = "x402Version")]
    pub x402_version: u32,
    pub payment_payload: PaymentPayload,
    pub payment_requirements: PaymentRequirements,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sync_settle: Option<bool>,   // OKX extension: wait for on-chain confirmation
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SettleResponse {
    pub success: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error_reason: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error_message: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub payer: Option<String>,
    pub transaction: String,          // Tx hash (empty for aggr_deferred)
    pub network: Network,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub amount: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub status: Option<String>,       // OKX: "pending" | "success" | "timeout"
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub extensions: Option<HashMap<String, serde_json::Value>>,
}

SupportedKind / SupportedResponse#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SupportedKind {
    #[serde(rename = "x402Version")]
    pub x402_version: u32,
    pub scheme: String,
    pub network: Network,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub extra: Option<HashMap<String, serde_json::Value>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SupportedResponse {
    pub kinds: Vec<SupportedKind>,
    pub extensions: Vec<String>,
    pub signers: HashMap<String, Vec<String>>,
}

SettleStatusResponse#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SettleStatusResponse {
    pub success: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error_reason: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub error_message: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub payer: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub transaction: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub network: Option<Network>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub status: Option<String>,       // "pending" | "success" | "failed"
}

Traits#

SchemeNetworkServer#

服务端 scheme 实现。

Rust
#[async_trait]
pub trait SchemeNetworkServer: Send + Sync {
    fn scheme(&self) -> &str;

    async fn parse_price(
        &self,
        price: &Price,
        network: &Network,
    ) -> Result<AssetAmount, X402Error>;

    async fn enhance_payment_requirements(
        &self,
        payment_requirements: PaymentRequirements,
        supported_kind: &SupportedKind,
        facilitator_extensions: &[String],
    ) -> Result<PaymentRequirements, X402Error>;
}

FacilitatorClient#

与远程 facilitator 通信的网络边界。

Rust
#[async_trait]
pub trait FacilitatorClient: Send + Sync {
    async fn get_supported(&self) -> Result<SupportedResponse, X402Error>;
    async fn verify(&self, request: &VerifyRequest) -> Result<VerifyResponse, X402Error>;
    async fn settle(&self, request: &SettleRequest) -> Result<SettleResponse, X402Error>;
    async fn get_settle_status(&self, tx_hash: &str) -> Result<SettleStatusResponse, X402Error>;
}

ResourceServerExtension#

Rust
#[async_trait]
pub trait ResourceServerExtension: Send + Sync {
    fn key(&self) -> &str;

    async fn enrich_payment_required(
        &self,
        payment_required: PaymentRequired,
        context: &PaymentRequiredContext,
    ) -> PaymentRequired;

    async fn enrich_verify_extensions(
        &self,
        extensions: HashMap<String, serde_json::Value>,
        payment_payload: &PaymentPayload,
        payment_requirements: &PaymentRequirements,
    ) -> HashMap<String, serde_json::Value>;

    async fn enrich_settle_extensions(
        &self,
        extensions: HashMap<String, serde_json::Value>,
        payment_payload: &PaymentPayload,
        payment_requirements: &PaymentRequirements,
    ) -> HashMap<String, serde_json::Value>;
}

pub struct PaymentRequiredContext {
    pub url: String,
    pub method: String,
}

pub struct SettleResultContext {
    pub url: String,
    pub method: String,
    pub payment_payload: PaymentPayload,
    pub payment_requirements: PaymentRequirements,
    pub settle_response: SettleResponse,
}

FacilitatorExtension#

Rust
#[async_trait]
pub trait FacilitatorExtension: Send + Sync {
    fn key(&self) -> &str;
    fn supported_networks(&self) -> Vec<Network>;
}

服务端 API(X402ResourceServer)#

构造函数与注册#

Rust
use x402_core::server::X402ResourceServer;
use std::sync::Arc;

let mut server = X402ResourceServer::new(Arc::new(facilitator));

// Register schemes (chainable via move semantics)
server.register("eip155:196", Arc::new(ExactEvmScheme::new()));
server.register("eip155:196", Arc::new(DeferredEvmScheme::new()));
// Wildcard: "eip155:*" matches all EVM chains

方法#

Rust
impl X402ResourceServer {
    pub fn new(facilitator: Arc<dyn FacilitatorClient>) -> Self;

    pub fn register(
        &mut self,
        network: &str,
        scheme: Arc<dyn SchemeNetworkServer>,
    );

    pub async fn initialize(&mut self) -> Result<(), X402Error>;

    pub fn supported(&self) -> Option<&SupportedResponse>;
    pub fn facilitator(&self) -> &dyn FacilitatorClient;

    pub async fn build_payment_requirements(
        &self,
        scheme: &str,
        price: &str,         // "$0.01"
        network: &str,       // "eip155:196"
        pay_to: &str,        // "0xSeller"
        max_timeout_seconds: u64,
        resource: &ResourceInfo,
    ) -> Result<PaymentRequirements, X402Error>;

    pub async fn verify_payment(
        &self,
        payment_payload: &PaymentPayload,
        payment_requirements: &PaymentRequirements,
    ) -> Result<VerifyResponse, X402Error>;

    pub async fn settle_payment(
        &self,
        payment_payload: &PaymentPayload,
        payment_requirements: &PaymentRequirements,
        sync_settle: Option<bool>,
    ) -> Result<SettleResponse, X402Error>;

    pub async fn poll_settle_status(
        &self,
        tx_hash: &str,
        poll_interval: Duration,  // DEFAULT_POLL_INTERVAL = 1s
        poll_deadline: Duration,  // DEFAULT_POLL_DEADLINE = 5s
    ) -> PollResult;
}

PollResult#

Rust
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PollResult {
    Success,   // Transaction confirmed on-chain
    Failed,    // Transaction failed on-chain
    Timeout,   // Polling deadline exceeded
}

OKX Facilitator 客户端(OkxHttpFacilitatorClient)#

Rust
use x402_core::http::OkxHttpFacilitatorClient;

let client = OkxHttpFacilitatorClient::new(
    "https://www.okx.com",          // base_url
    "your-api-key",                  // api_key
    "your-secret-key",               // secret_key
    "your-passphrase",               // passphrase
);

实现了 FacilitatorClient trait。所有请求均包含 HMAC-SHA256 认证。

调用的端点:

方法OKX 路径
get_supported()GET /api/v6/pay/x402/supported
verify(request)POST /api/v6/pay/x402/verify
settle(request)POST /api/v6/pay/x402/settle
get_settle_status(tx_hash)GET /api/v6/pay/x402/settle/status?txHash=...

OKX 的响应被包装在 {"code": 0, "data": {...}, "msg": ""} 中 -- 客户端会自动解包。


HMAC 认证#

Rust
use x402_core::http::hmac::{sign_request, build_auth_headers};

// Sign a request
let signature = sign_request(secret_key, timestamp, method, request_path, body);
// Message = timestamp + METHOD + requestPath + body
// Signature = Base64(HMAC-SHA256(secretKey, message))

// Build all auth headers
let headers = build_auth_headers(api_key, secret_key, passphrase, method, request_path, body);
// Returns: OK-ACCESS-KEY, OK-ACCESS-SIGN, OK-ACCESS-TIMESTAMP, OK-ACCESS-PASSPHRASE

HTTP 工具#

请求头编码/解码#

Rust
use x402_core::http::{
    encode_payment_signature_header,
    decode_payment_signature_header,
    encode_payment_required_header,
    decode_payment_required_header,
    encode_payment_response_header,
    decode_payment_response_header,
};

let encoded = encode_payment_required_header(&payment_required)?;  // → base64 string
let decoded = decode_payment_required_header(&header_value)?;       // → PaymentRequired

常量#

Rust
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(1);
pub const DEFAULT_POLL_DEADLINE: Duration = Duration::from_secs(5);
pub const PAYMENT_SIGNATURE_HEADER: &str = "PAYMENT-SIGNATURE";
pub const PAYMENT_REQUIRED_HEADER: &str = "PAYMENT-REQUIRED";
pub const PAYMENT_RESPONSE_HEADER: &str = "PAYMENT-RESPONSE";

路由配置#

Rust
use x402_core::http::{RoutesConfig, RoutePaymentConfig, AcceptConfig};
use std::collections::HashMap;

pub type RoutesConfig = HashMap<String, RoutePaymentConfig>;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RoutePaymentConfig {
    pub accepts: Vec<AcceptConfig>,   // Accepted payment options
    pub description: String,          // Resource description
    pub mime_type: String,            // Response MIME type
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sync_settle: Option<bool>,    // Enable sync settlement
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AcceptConfig {
    pub scheme: String,    // "exact" | "aggr_deferred"
    pub price: String,     // "$0.01" or token amount
    pub network: String,   // "eip155:196"
    pub pay_to: String,    // Recipient wallet
}

示例:

Rust
let mut routes = HashMap::new();
routes.insert("GET /api/data".to_string(), RoutePaymentConfig {
    accepts: vec![
        AcceptConfig {
            scheme: "exact".to_string(),
            price: "$0.01".to_string(),
            network: "eip155:196".to_string(),
            pay_to: "0xSeller".to_string(),
        },
        AcceptConfig {
            scheme: "aggr_deferred".to_string(),
            price: "$0.001".to_string(),
            network: "eip155:196".to_string(),
            pay_to: "0xSeller".to_string(),
        },
    ],
    description: "Premium data".to_string(),
    mime_type: "application/json".to_string(),
    sync_settle: Some(true),
});

Axum 中间件(x402-axum)#

基本用法#

Rust
use x402_axum::payment_middleware;

let app = Router::new()
    .route("/api/data", get(handler))
    .layer(payment_middleware(server, routes));

构造函数#

Rust
// Basic middleware
pub fn payment_middleware(
    server: X402ResourceServer,
    routes: RoutesConfig,
) -> PaymentLayer;

// With custom poll deadline
pub fn payment_middleware_with_poll_deadline(
    server: X402ResourceServer,
    routes: RoutesConfig,
    poll_deadline: Duration,
) -> PaymentLayer;

// With settlement timeout recovery hook
pub fn payment_middleware_with_timeout_hook(
    server: X402ResourceServer,
    routes: RoutesConfig,
    timeout_hook: OnSettlementTimeoutHook,
) -> PaymentLayer;

// With both
pub fn payment_middleware_with_timeout_hook_and_deadline(
    server: X402ResourceServer,
    routes: RoutesConfig,
    timeout_hook: OnSettlementTimeoutHook,
    poll_deadline: Duration,
) -> PaymentLayer;

OnSettlementTimeoutHook#

Rust
pub struct SettlementTimeoutResult {
    pub confirmed: bool,
}

pub type OnSettlementTimeoutHook = Box<
    dyn Fn(String, String) -> Pin<Box<dyn Future<Output = SettlementTimeoutResult> + Send>>
        + Send + Sync,
>;

// Usage:
let hook: OnSettlementTimeoutHook = Box::new(|route, tx_hash| {
    Box::pin(async move {
        tracing::warn!("Timeout for route={} tx={}", route, tx_hash);
        SettlementTimeoutResult { confirmed: false }
    })
});

中间件流程#

  1. 检查路由是否需要支付(find_route_config)

  2. 如果没有 payment-signature 请求头 -> 返回 402 并附带 PAYMENT-REQUIRED 请求头

  3. 解码并验证支付 payload

  4. 将 payload 与路由接受的 requirements 进行匹配

  5. 通过 facilitator 验证(POST /verify)

  6. 调用内部处理器并缓冲响应

  7. 通过 facilitator 结算(POST /settle)

  8. 如果异步(status: "pending") -> 在截止时间内轮询

  9. 如果超时 -> 调用超时回调(如果已配置)

  10. 向响应添加 PAYMENT-RESPONSE 请求头

重新导出#

Rust
pub use x402_core::http::{
    AcceptConfig,
    OnSettlementTimeoutHook,
    PollResult,
    RoutePaymentConfig,
    RoutesConfig,
    SettlementTimeoutResult,
    DEFAULT_POLL_DEADLINE,
    DEFAULT_POLL_INTERVAL,
};
pub use x402_core::server::X402ResourceServer;

EVM 机制(x402-evm)#

ExactEvmScheme#

Rust
use x402_evm::ExactEvmScheme;

let scheme = ExactEvmScheme::new();
scheme.scheme();  // "exact"

处理以下功能:

  • 价格解析:"$0.01""0.01"AssetAmount

  • 使用正确小数位的代币金额转换

  • 按网络查找默认资产(Base 上的 USDC,X Layer 上的 USDT)

  • 为 EIP-3009 代币注入 EIP-712 域信息到 extra

DeferredEvmScheme#

Rust
use x402_evm::DeferredEvmScheme;

let scheme = DeferredEvmScheme::new();
scheme.scheme();  // "aggr_deferred"

所有价格/需求逻辑均委托给 ExactEvmScheme。从卖方角度来看完全相同。

EVM Payload 类型#

Rust
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AssetTransferMethod {
    #[serde(rename = "eip3009")]
    Eip3009,
    Permit2,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EIP3009Authorization {
    pub from: String,
    pub to: String,
    pub value: String,
    pub valid_after: String,
    pub valid_before: String,
    pub nonce: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExactEIP3009Payload {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub signature: Option<String>,
    pub authorization: EIP3009Authorization,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExactPermit2Payload {
    pub signature: String,
    pub permit2_authorization: Permit2Authorization,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ExactEvmPayloadV2 {
    EIP3009(ExactEIP3009Payload),
    Permit2(ExactPermit2Payload),
}

impl ExactEvmPayloadV2 {
    pub fn is_permit2(&self) -> bool;
    pub fn is_eip3009(&self) -> bool;
}

资产配置#

Rust
#[derive(Debug, Clone)]
pub struct DefaultAssetInfo {
    pub address: &'static str,       // Token contract address
    pub name: &'static str,          // EIP-712 domain name
    pub version: &'static str,       // EIP-712 domain version
    pub decimals: u8,                // Token decimal places
    pub asset_transfer_method: Option<&'static str>,  // "permit2" override
    pub supports_eip2612: bool,      // EIP-2612 permit() support
}

#[derive(Debug, Clone)]
pub struct ChainConfig {
    pub network: &'static str,       // CAIP-2 identifier
    pub chain_id: u64,
}

// Pre-registered assets:
// BASE_MAINNET_USDC:     eip155:8453,  0x833589..., 6 decimals
// BASE_SEPOLIA_USDC:     eip155:84532, 0x036CbD..., 6 decimals
// XLAYER_MAINNET_USDT:   eip155:196,   0x779ded..., 6 decimals, name: "USD₮0"
// XLAYER_TESTNET_USDT:   eip155:195,   TBD

pub fn default_stablecoins() -> HashMap<&'static str, DefaultAssetInfo>;
pub fn get_default_asset(network: &str) -> Option<DefaultAssetInfo>;

错误类型#

Rust
#[derive(Debug, thiserror::Error)]
pub enum X402Error {
    #[error(transparent)]
    Verify(#[from] VerifyError),
    #[error(transparent)]
    Settle(#[from] SettleError),
    #[error(transparent)]
    FacilitatorResponse(#[from] FacilitatorResponseError),
    #[error("configuration error: {0}")]
    Config(String),
    #[error("route configuration error: {0}")]
    RouteConfig(String),
    #[error("unsupported scheme: {0}")]
    UnsupportedScheme(String),
    #[error("unsupported network: {0}")]
    UnsupportedNetwork(String),
    #[error("price parse error: {0}")]
    PriceParse(String),
    #[error("http error: {0}")]
    Http(#[from] reqwest::Error),
    #[error("serialization error: {0}")]
    Serialization(#[from] serde_json::Error),
    #[error("base64 decode error: {0}")]
    Base64Decode(#[from] base64::DecodeError),
    #[error("{0}")]
    Other(String),
}

#[derive(Debug, Clone, thiserror::Error)]
pub struct VerifyError {
    pub status_code: u16,
    pub invalid_reason: Option<String>,
    pub invalid_message: Option<String>,
    pub payer: Option<String>,
}

#[derive(Debug, Clone, thiserror::Error)]
pub struct SettleError {
    pub status_code: u16,
    pub error_reason: Option<String>,
    pub error_message: Option<String>,
    pub payer: Option<String>,
    pub transaction: String,
    pub network: Network,
}

#[derive(Debug, Clone, thiserror::Error)]
pub struct FacilitatorResponseError(pub String);

工具函数#

Rust
pub fn safe_base64_encode(data: &str) -> String;
pub fn safe_base64_decode(data: &str) -> Result<String, X402Error>;

// Network pattern matching: "eip155:*" matches "eip155:196"
pub fn network_matches_pattern(network: &str, pattern: &str) -> bool;

pub fn find_schemes_by_network<'a, T>(
    map: &'a HashMap<String, HashMap<String, T>>,
    network: &str,
) -> Option<&'a HashMap<String, T>>;

pub fn find_by_network_and_scheme<'a, T>(
    map: &'a HashMap<String, HashMap<String, T>>,
    scheme: &str,
    network: &str,
) -> Option<&'a T>;

pub fn deep_equal(obj1: &serde_json::Value, obj2: &serde_json::Value) -> bool;

Schema 验证#

Rust
pub fn validate_payment_requirements(req: &PaymentRequirements) -> Result<(), X402Error>;
pub fn validate_payment_payload(payload: &PaymentPayload) -> Result<(), X402Error>;
pub fn validate_payment_required(required: &PaymentRequired) -> Result<(), X402Error>;

验证所有必填字段均不为空。