rust_x402/
lib.rs

1//! # x402 - HTTP-native micropayments
2//!
3//! A Rust implementation of the x402 protocol for HTTP-native micropayments.
4//! This library provides the core types, client, and middleware for implementing
5//! payment-protected HTTP resources.
6
7pub mod blockchain;
8pub mod client;
9pub mod crypto;
10pub mod error;
11pub mod facilitator;
12pub mod middleware;
13pub mod proxy;
14pub mod real_facilitator;
15pub mod template;
16pub mod types;
17pub mod wallet;
18
19// Re-exports for convenience
20pub use blockchain::{BlockchainClient, BlockchainClientFactory};
21pub use client::X402Client;
22pub use error::{Result, X402Error};
23pub use real_facilitator::{
24    BlockchainFacilitatorClient, BlockchainFacilitatorConfig, BlockchainFacilitatorFactory,
25};
26pub use types::*;
27pub use wallet::{Wallet, WalletFactory};
28
29// Feature-gated framework support
30#[cfg(feature = "axum")]
31pub mod axum;
32
33#[cfg(feature = "actix-web")]
34pub mod actix_web;
35
36#[cfg(feature = "warp")]
37pub mod warp;
38
39/// Current version of the x402 library
40pub const VERSION: &str = env!("CARGO_PKG_VERSION");
41
42/// x402 protocol version
43pub const X402_VERSION: u32 = 1;
44
45#[cfg(test)]
46mod tests {
47    use super::*;
48
49    #[test]
50    fn test_version_constants() {
51        assert_eq!(X402_VERSION, 1);
52        // VERSION is a const string, so it's never empty
53        assert_eq!(VERSION, "0.1.0");
54    }
55
56    #[test]
57    fn test_payment_requirements_creation() {
58        let requirements = PaymentRequirements::new(
59            "exact",
60            "base-sepolia",
61            "1000000",
62            "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
63            "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
64            "https://example.com/test",
65            "Test payment",
66        );
67
68        assert_eq!(requirements.scheme, "exact");
69        assert_eq!(requirements.network, "base-sepolia");
70        assert_eq!(requirements.max_amount_required, "1000000");
71        assert_eq!(
72            requirements.asset,
73            "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
74        );
75        assert_eq!(
76            requirements.pay_to,
77            "0x209693Bc6afc0C5328bA36FaF03C514EF312287C"
78        );
79        assert_eq!(requirements.resource, "https://example.com/test");
80        assert_eq!(requirements.description, "Test payment");
81    }
82
83    #[test]
84    fn test_payment_requirements_usdc_info() {
85        let mut requirements = PaymentRequirements::new(
86            "exact",
87            "base-sepolia",
88            "1000000",
89            "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
90            "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
91            "https://example.com/test",
92            "Test payment",
93        );
94
95        requirements
96            .set_usdc_info(crate::types::Network::Testnet)
97            .unwrap();
98        assert!(requirements.extra.is_some());
99
100        let extra = requirements.extra.as_ref().unwrap();
101        assert_eq!(extra["name"], "USDC");
102        assert_eq!(extra["version"], "2");
103    }
104
105    #[test]
106    fn test_payment_payload_creation() {
107        let authorization = ExactEvmPayloadAuthorization::new(
108            "0x857b06519E91e3A54538791bDbb0E22373e36b66",
109            "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
110            "1000000",
111            "1745323800",
112            "1745323985",
113            "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480",
114        );
115
116        let payload = ExactEvmPayload {
117            signature: "0x2d6a7588d6acca505cbf0d9a4a227e0c52c6c34008c8e8986a1283259764173608a2ce6496642e377d6da8dbbf5836e9bd15092f9ecab05ded3d6293af148b571c".to_string(),
118            authorization,
119        };
120
121        let payment_payload = PaymentPayload::new("exact", "base-sepolia", payload);
122
123        assert_eq!(payment_payload.x402_version, X402_VERSION);
124        assert_eq!(payment_payload.scheme, "exact");
125        assert_eq!(payment_payload.network, "base-sepolia");
126    }
127
128    #[test]
129    fn test_payment_payload_base64_encoding() {
130        let authorization = ExactEvmPayloadAuthorization::new(
131            "0x857b06519E91e3A54538791bDbb0E22373e36b66",
132            "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
133            "1000000",
134            "1745323800",
135            "1745323985",
136            "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480",
137        );
138
139        let payload = ExactEvmPayload {
140            signature: "0x2d6a7588d6acca505cbf0d9a4a227e0c52c6c34008c8e8986a1283259764173608a2ce6496642e377d6da8dbbf5836e9bd15092f9ecab05ded3d6293af148b571c".to_string(),
141            authorization,
142        };
143
144        let payment_payload = PaymentPayload::new("exact", "base-sepolia", payload);
145        let encoded = payment_payload.to_base64().unwrap();
146        let decoded = PaymentPayload::from_base64(&encoded).unwrap();
147
148        assert_eq!(payment_payload.x402_version, decoded.x402_version);
149        assert_eq!(payment_payload.scheme, decoded.scheme);
150        assert_eq!(payment_payload.network, decoded.network);
151    }
152
153    #[test]
154    fn test_authorization_validity() {
155        let now = chrono::Utc::now().timestamp();
156        let valid_after = (now - 100).to_string();
157        let valid_before = (now + 100).to_string();
158
159        let authorization = ExactEvmPayloadAuthorization::new(
160            "0x857b06519E91e3A54538791bDbb0E22373e36b66",
161            "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
162            "1000000",
163            valid_after,
164            valid_before,
165            "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480",
166        );
167
168        assert!(authorization.is_valid_now().unwrap());
169    }
170
171    #[test]
172    fn test_authorization_expired() {
173        let now = chrono::Utc::now().timestamp();
174        let valid_after = (now - 200).to_string();
175        let valid_before = (now - 100).to_string(); // Expired
176
177        let authorization = ExactEvmPayloadAuthorization::new(
178            "0x857b06519E91e3A54538791bDbb0E22373e36b66",
179            "0x209693Bc6afc0C5328bA36FaF03C514EF312287C",
180            "1000000",
181            valid_after,
182            valid_before,
183            "0xf3746613c2d920b5fdabc0856f2aeb2d4f88ee6037b8cc5d04a71a4462f13480",
184        );
185
186        assert!(!authorization.is_valid_now().unwrap());
187    }
188
189    #[test]
190    fn test_facilitator_config() {
191        let config = FacilitatorConfig {
192            url: "https://example.com/facilitator".to_string(),
193            timeout: Some(std::time::Duration::from_secs(30)),
194            create_auth_headers: None,
195        };
196
197        assert_eq!(config.url, "https://example.com/facilitator".to_string());
198        assert_eq!(config.timeout, Some(std::time::Duration::from_secs(30)));
199    }
200
201    #[test]
202    fn test_blockchain_facilitator_config() {
203        let config = BlockchainFacilitatorConfig {
204            rpc_url: Some("https://example.com/facilitator".to_string()),
205            network: "base-sepolia".to_string(),
206            verification_timeout: std::time::Duration::from_secs(30),
207            confirmation_blocks: 1,
208            max_retries: 3,
209            retry_delay: std::time::Duration::from_secs(1),
210        };
211
212        assert_eq!(
213            config.rpc_url,
214            Some("https://example.com/facilitator".to_string())
215        );
216        assert_eq!(
217            config.verification_timeout,
218            std::time::Duration::from_secs(30)
219        );
220    }
221
222    #[test]
223    fn test_networks() {
224        assert_eq!(networks::BASE_MAINNET, "base");
225        assert_eq!(networks::BASE_SEPOLIA, "base-sepolia");
226        assert_eq!(networks::AVALANCHE_MAINNET, "avalanche");
227        assert_eq!(networks::AVALANCHE_FUJI, "avalanche-fuji");
228
229        assert!(networks::is_supported("base-sepolia"));
230        assert!(networks::is_supported("base"));
231        assert!(!networks::is_supported("unsupported-network"));
232
233        assert_eq!(
234            networks::get_usdc_address("base-sepolia"),
235            Some("0x036CbD53842c5426634e7929541eC2318f3dCF7e")
236        );
237        assert_eq!(
238            networks::get_usdc_address("base"),
239            Some("0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913")
240        );
241    }
242
243    #[test]
244    fn test_schemes() {
245        assert_eq!(schemes::EXACT, "exact");
246    }
247}