1use crate::config::{ConfigError, ConfigManager};
3use crate::types::{
4 ChainType, Currency, PaymentRequest, PaymentVerification, VerificationResult,
5 X402ProtocolResponse,
6};
7use crate::verifier::{PaymentVerifier, VerificationError, VerifierRegistry};
8use std::collections::HashMap;
9use std::sync::{Arc, RwLock};
10use uuid::Uuid;
11
12pub struct X402 {
43 config_manager: ConfigManager,
44 verifier_registry: VerifierRegistry,
45 payment_sessions_cache: Arc<RwLock<HashMap<String, PaymentSession>>>,
46}
47
48impl X402 {
49 pub fn new(config_manager: ConfigManager) -> Result<Self, EngineError> {
50 Ok(Self {
51 config_manager,
52 verifier_registry: VerifierRegistry::new(),
53 payment_sessions_cache: Arc::new(RwLock::new(HashMap::new())),
54 })
55 }
56
57 pub fn from_config_file(path: &str) -> Result<Self, EngineError> {
58 let config_manager = ConfigManager::from_file(path)?;
59 Self::new(config_manager)
60 }
61
62 pub fn from_default_config() -> Result<Self, EngineError> {
63 let config_manager = ConfigManager::new()?;
64 Self::new(config_manager)
65 }
66
67 pub async fn register_chain_verifier(
69 &mut self,
70 chain_type: ChainType,
71 rpc_url: String,
72 ) -> Result<(), EngineError> {
73 let chain_config = self
74 .config_manager
75 .get_chain_config(&chain_type)
76 .ok_or_else(|| EngineError::ChainNotSupported(chain_type.clone()))?;
77 let verifier: Box<dyn PaymentVerifier> = match &chain_type {
78 ChainType::Evm(_) => {
79 use crate::verifier::evm::EvmVerifier;
80 let evm_verifier = EvmVerifier::new(rpc_url, chain_type.clone())
81 .await
82 .map_err(EngineError::VerificationError)?;
83 Box::new(evm_verifier)
84 }
85 ChainType::Solana(_) => {
86 use crate::verifier::solana::SolanaVerifier;
87 let solana_verifier = SolanaVerifier::new();
88 Box::new(solana_verifier)
89 }
90 _ => {
91 return Err(EngineError::ChainNotSupported(chain_type));
92 }
93 };
94 self.verifier_registry
95 .register_verifier(chain_type, verifier);
96 Ok(())
97 }
98
99 pub async fn verify_payment(
100 &self,
101 user_address: &str,
102 payment_nonce: &str,
103 ) -> Result<PaymentVerification, EngineError> {
104 let (chain_type, payment_request) = {
105 let sessions = self.payment_sessions_cache.read().unwrap();
106 let session = sessions
107 .get(payment_nonce)
108 .ok_or(EngineError::InvalidSession)?;
109
110 if session.user_address != user_address {
111 return Err(EngineError::AddressMismatch);
112 }
113
114 (
115 session.payment_request.chain.chain_type.clone(),
116 session.payment_request.clone(),
117 )
118 };
119 let verifier = self
120 .verifier_registry
121 .get_verifier(&chain_type)
122 .ok_or(EngineError::ChainNotSupported(chain_type))?;
123 let verification = verifier
124 .verify_payment(&payment_request, user_address)
125 .await
126 .map_err(EngineError::VerificationFailed)?;
127 if verification.is_paid {
128 let mut sessions = self.payment_sessions_cache.write().unwrap();
129 if let Some(session) = sessions.get_mut(payment_nonce) {
130 session.verified = true;
131 }
132 }
133 Ok(verification)
134 }
135
136 fn create_payment_request(
137 &self,
138 user_address: &str,
139 resource_path: &str,
140 custom_amount: Option<&str>,
141 ) -> Result<PaymentRequest, EngineError> {
142 let config = self.config_manager.get_config();
143 let default_chain = self.config_manager.get_default_chain_config()?;
144
145 let amount = custom_amount
146 .map(|s| s.to_string())
147 .unwrap_or_else(|| config.payments.default_amount.clone());
148 let currency = match &config.service.default_currency {
149 crate::config::CurrencyConfig {
150 currency_type,
151 address,
152 decimals,
153 } => match currency_type {
154 crate::config::CurrencyType::Native => Currency::Native,
155 crate::config::CurrencyType::Erc20 => {
156 let token_address =
157 address.clone().ok_or(EngineError::InvalidCurrencyConfig)?;
158 Currency::Token {
159 address: token_address,
160 decimals: *decimals,
161 }
162 }
163 _ => Currency::Native,
164 },
165 };
166 Ok(PaymentRequest {
167 amount,
168 currency,
169 recipient: self.config_manager.get_service_address(),
170 chain: default_chain.clone(),
171 description: Some(format!("Access to: {}", resource_path)),
172 expires_at: Some(
173 std::time::SystemTime::now()
174 .duration_since(std::time::UNIX_EPOCH)
175 .unwrap()
176 .as_secs()
177 + config.payments.expiration_time_secs,
178 ),
179 nonce: Uuid::new_v4().to_string(),
180 })
181 }
182
183 fn store_payment_session(&self, user_address: &str, payment_request: PaymentRequest) {
184 let session = PaymentSession {
185 user_address: user_address.to_string(),
186 payment_request,
187 created_at: std::time::SystemTime::now()
188 .duration_since(std::time::UNIX_EPOCH)
189 .unwrap()
190 .as_secs(),
191 verified: false,
192 };
193
194 let mut sessions = self.payment_sessions_cache.write().unwrap();
195 sessions.insert(session.payment_request.nonce.clone(), session);
196 }
197
198 pub async fn handle_access_request(
240 &self,
241 user_address: &str,
242 resource_path: &str,
243 payment_nonce: Option<&str>,
244 custom_amount: Option<&str>,
245 ) -> Result<VerificationResult, EngineError> {
246 if let Some(nonce) = payment_nonce {
247 if let Ok(verification) = self.verify_payment(user_address, nonce).await {
248 if verification.is_paid {
249 return Ok(VerificationResult {
250 should_serve_content: true,
251 http_status: 200,
252 x402_response: None,
253 verification: Some(verification),
254 });
255 }
256 }
257 }
258 let payment_request =
259 self.create_payment_request(user_address, resource_path, custom_amount)?;
260 let config = self.config_manager.get_config();
261 let x402_response = X402ProtocolResponse {
262 status: 402,
263 payment_required: payment_request.clone(),
264 verification_url: Some(format!(
265 "{}/{}",
266 config.service.base_verification_url, payment_request.nonce
267 )),
268 };
269 self.store_payment_session(user_address, payment_request);
270 Ok(VerificationResult {
271 should_serve_content: false,
272 http_status: 402,
273 x402_response: Some(x402_response),
274 verification: None,
275 })
276 }
277
278 pub fn config_manager(&self) -> &ConfigManager {
279 &self.config_manager
280 }
281
282 pub fn verifier_registry(&self) -> &VerifierRegistry {
283 &self.verifier_registry
284 }
285
286 pub fn verifier_registry_mut(&mut self) -> &mut VerifierRegistry {
287 &mut self.verifier_registry
288 }
289}
290
291#[derive(Debug)]
292pub enum EngineError {
293 ConfigError(ConfigError),
294 VerificationError(VerificationError),
295 InvalidSession,
296 AddressMismatch,
297 ChainNotSupported(ChainType),
298 VerificationFailed(VerificationError),
299 InvalidCurrencyConfig,
300}
301
302impl std::fmt::Display for EngineError {
303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304 match self {
305 Self::ConfigError(err) => write!(f, "Configuration error: {}", err),
306 Self::VerificationError(err) => write!(f, "Verification error: {}", err),
307 Self::InvalidSession => write!(f, "Payment session not found"),
308 Self::AddressMismatch => write!(f, "User address mismatch"),
309 Self::ChainNotSupported(chain_type) => {
310 write!(f, "Chain not supported: {:?}", chain_type)
311 }
312 Self::VerificationFailed(err) => write!(f, "Verification failed: {}", err),
313 Self::InvalidCurrencyConfig => write!(f, "Invalid currency configuration"),
314 }
315 }
316}
317
318impl std::error::Error for EngineError {}
319
320impl From<ConfigError> for EngineError {
321 fn from(err: ConfigError) -> Self {
322 Self::ConfigError(err)
323 }
324}
325
326impl From<VerificationError> for EngineError {
327 fn from(err: VerificationError) -> Self {
328 Self::VerificationError(err)
329 }
330}
331
332struct PaymentSession {
333 user_address: String,
334 payment_request: PaymentRequest,
335 created_at: u64,
336 verified: bool,
337}