1use 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}