Skip to main content

x402_sdk/
config.rs

1/// Configuration module
2use crate::types::{AptosChain, ChainConfig, ChainType, EvmChain, SolanaChain, SuiChain};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::fmt;
6
7#[derive(Debug, Clone)]
8pub enum ConfigError {
9    FileNotFound(String),
10    InvalidConfig(String),
11    ChainMissing(ChainType),
12    IoError(String),
13    SerializationError(String),
14}
15
16impl fmt::Display for ConfigError {
17    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
18        match self {
19            ConfigError::FileNotFound(path) => write!(f, "Configuration file not found: {}", path),
20            ConfigError::InvalidConfig(msg) => write!(f, "Invalid configuration: {}", msg),
21            ConfigError::ChainMissing(chain) => {
22                write!(f, "Chain configuration missing: {:?}", chain)
23            }
24            ConfigError::IoError(err) => write!(f, "IO error: {}", err),
25            ConfigError::SerializationError(err) => write!(f, "Serialization error: {}", err),
26        }
27    }
28}
29
30impl std::error::Error for ConfigError {}
31
32impl From<std::io::Error> for ConfigError {
33    fn from(err: std::io::Error) -> Self {
34        ConfigError::IoError(err.to_string())
35    }
36}
37
38impl From<serde_json::Error> for ConfigError {
39    fn from(err: serde_json::Error) -> Self {
40        ConfigError::SerializationError(err.to_string())
41    }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct X402Config {
46    pub service: ServiceConfig,
47    pub chains: HashMap<ChainType, ChainConfig>,
48    pub payments: PaymentConfig,
49    pub cache: CacheConfig,
50    pub default_chain: ChainType,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ServiceConfig {
55    pub name: String,
56    pub description: String,
57    pub base_verification_url: String,
58    pub default_currency: CurrencyConfig,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct CurrencyConfig {
63    pub currency_type: CurrencyType,
64    pub address: Option<String>,
65    pub decimals: u8,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub enum CurrencyType {
70    Native,
71    Erc20,
72    Erc721,
73    Coin,
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct PaymentConfig {
78    pub default_amount: String,
79    pub expiration_time_secs: u64,
80    pub allowed_currencies: Vec<CurrencyConfig>,
81    pub fee_recovery_percent: f64,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct CacheConfig {
86    pub enabled: bool,
87    pub ttl_secs: u64,
88    pub max_entries: usize,
89}
90
91pub struct ConfigManager {
92    config: X402Config,
93    environment: HashMap<String, String>,
94}
95
96impl ConfigManager {
97    pub fn new() -> Result<Self, ConfigError> {
98        let default_config = Self::default_config();
99        let environment = Self::load_environment_variables();
100
101        Ok(Self {
102            config: default_config,
103            environment,
104        })
105    }
106
107    pub fn from_file(path: &str) -> Result<Self, ConfigError> {
108        let content = std::fs::read_to_string(path)
109            .map_err(|_| ConfigError::FileNotFound(path.to_string()))?;
110
111        let config: X402Config = serde_json::from_str(&content)?;
112        let environment = Self::load_environment_variables();
113
114        Ok(Self {
115            config,
116            environment,
117        })
118    }
119
120    pub fn from_config(config: X402Config) -> Self {
121        let environment = Self::load_environment_variables();
122        Self {
123            config,
124            environment,
125        }
126    }
127
128    pub fn get_config(&self) -> &X402Config {
129        &self.config
130    }
131
132    pub fn get_chain_config(&self, chain_type: &ChainType) -> Option<&ChainConfig> {
133        self.config.chains.get(chain_type)
134    }
135
136    pub fn get_default_chain_config(&self) -> Result<&ChainConfig, ConfigError> {
137        self.config
138            .chains
139            .get(&self.config.default_chain)
140            .ok_or_else(|| ConfigError::ChainMissing(self.config.default_chain.clone()))
141    }
142
143    pub fn get_service_address(&self) -> String {
144        self.environment
145            .get("X402_SERVICE_ADDRESS")
146            .cloned()
147            .unwrap_or_else(|| "0x0000000000000000000000000000000000000000".to_string())
148    }
149
150    pub fn update_config<F>(&mut self, updater: F)
151    where
152        F: FnOnce(&mut X402Config),
153    {
154        updater(&mut self.config);
155    }
156
157    fn load_environment_variables() -> HashMap<String, String> {
158        std::env::vars()
159            .filter(|(key, _)| key.starts_with("X402_") || key.starts_with("RPC_"))
160            .collect()
161    }
162
163    fn default_config() -> X402Config {
164        X402Config {
165            service: ServiceConfig {
166                name: "X402 Payment Service".to_string(),
167                description: "A service protected by x402 payment protocol".to_string(),
168                base_verification_url: "https://api.example.com/verify".to_string(),
169                default_currency: CurrencyConfig {
170                    currency_type: CurrencyType::Native,
171                    address: None,
172                    decimals: 18,
173                },
174            },
175            chains: HashMap::from([
176                (
177                    ChainType::Evm(EvmChain::Ethereum),
178                    ChainConfig::new(
179                        ChainType::Evm(EvmChain::Ethereum),
180                        Some("https://eth.llamarpc.com".to_string()),
181                    ),
182                ),
183                (
184                    ChainType::Evm(EvmChain::Polygon),
185                    ChainConfig::new(
186                        ChainType::Evm(EvmChain::Polygon),
187                        Some("https://polygon-rpc.com".to_string()),
188                    ),
189                ),
190            ]),
191            payments: PaymentConfig {
192                default_amount: "1000000000000000".to_string(),
193                expiration_time_secs: 3600,
194                allowed_currencies: vec![CurrencyConfig {
195                    currency_type: CurrencyType::Native,
196                    address: None,
197                    decimals: 18,
198                }],
199                fee_recovery_percent: 0.1,
200            },
201            cache: CacheConfig {
202                enabled: true,
203                ttl_secs: 300,
204                max_entries: 1000,
205            },
206            default_chain: ChainType::Evm(EvmChain::Ethereum),
207        }
208    }
209}
210
211pub struct ConfigBuilder {
212    config: X402Config,
213}
214
215impl ConfigBuilder {
216    pub fn new() -> Self {
217        Self {
218            config: ConfigManager::default_config(),
219        }
220    }
221
222    pub fn with_service_name(mut self, name: &str) -> Self {
223        self.config.service.name = name.to_string();
224        self
225    }
226
227    pub fn with_default_chain(mut self, chain_type: ChainType) -> Self {
228        self.config.default_chain = chain_type;
229        self
230    }
231
232    pub fn with_chain(mut self, chain_type: ChainType, chain_config: ChainConfig) -> Self {
233        self.config.chains.insert(chain_type, chain_config);
234        self
235    }
236
237    pub fn with_payment_amount(mut self, amount: &str) -> Self {
238        self.config.payments.default_amount = amount.to_string();
239        self
240    }
241
242    pub fn with_expiration_time(mut self, seconds: u64) -> Self {
243        self.config.payments.expiration_time_secs = seconds;
244        self
245    }
246
247    pub fn build(self) -> X402Config {
248        self.config
249    }
250}