Rust SDK 参考#
Crate#
| Crate | 描述 |
|---|---|
x402-core | 核心:服务端、facilitator 客户端、类型、HTTP 工具、HMAC 认证 |
x402-axum | Axum 中间件(Tower Layer/Service) |
x402-evm | EVM 机制: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 }
})
});
中间件流程#
-
检查路由是否需要支付(
find_route_config) -
如果没有
payment-signature请求头 -> 返回 402 并附带PAYMENT-REQUIRED请求头 -
解码并验证支付 payload
-
将 payload 与路由接受的 requirements 进行匹配
-
通过 facilitator 验证(
POST /verify) -
调用内部处理器并缓冲响应
-
通过 facilitator 结算(
POST /settle) -
如果异步(
status: "pending") -> 在截止时间内轮询 -
如果超时 -> 调用超时回调(如果已配置)
-
向响应添加
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>;
验证所有必填字段均不为空。