rust_x402/
lib.rs

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