Skip to main content

tensora/
config.rs

1//! Configuration management for Tensora SDK
2
3use crate::errors::{Result, TensoraError};
4use crate::types::chains;
5use std::env;
6use dotenvy::dotenv;
7
8/// Tensora SDK configuration
9#[derive(Debug, Clone)]
10pub struct TensoraConfig {
11    /// Tensora L2 RPC endpoint
12    pub tensora_rpc: String,
13    
14    /// Tensora L2 chain ID (must be 44444444)
15    pub tensora_chain_id: u64,
16    
17    /// BSC L1 RPC endpoint
18    pub bsc_rpc: String,
19    
20    /// BSC L1 chain ID (must be 56)
21    pub bsc_chain_id: u64,
22    
23    /// Private key for signing transactions (optional)
24    pub private_key: Option<String>,
25    
26    /// Contract addresses configuration
27    pub addresses: AddressConfig,
28}
29
30/// Contract addresses configuration
31#[derive(Debug, Clone)]
32pub struct AddressConfig {
33    /// L1 WTORA token address on BSC
34    pub l1_wtora: Option<String>,
35    
36    /// L2 WTORA token address on Tensora
37    pub l2_wtora: Option<String>,
38    
39    /// L1 Standard Bridge address
40    pub l1_bridge: Option<String>,
41    
42    /// L2 Standard Bridge address
43    pub l2_bridge: Option<String>,
44    
45    /// Staking Hub contract address
46    pub staking_hub: Option<String>,
47    
48    /// Subnet Factory contract address
49    pub subnet_factory: Option<String>,
50    
51    /// ERC-4337 EntryPoint address
52    pub entry_point: Option<String>,
53    
54    /// Paymaster contract address
55    pub paymaster: Option<String>,
56}
57
58impl Default for AddressConfig {
59    fn default() -> Self {
60        Self {
61            l1_wtora: None,
62            l2_wtora: None,
63            l1_bridge: None,
64            l2_bridge: None,
65            staking_hub: None,
66            subnet_factory: None,
67            entry_point: None,
68            paymaster: None,
69        }
70    }
71}
72
73impl TensoraConfig {
74    /// Load configuration from environment variables
75    ///
76    /// Loads `.env` file if present, then reads environment variables:
77    /// - `TENSORA_RPC` (default: https://rpc.tensora.sh)
78    /// - `TENSORA_CHAIN_ID` (default: 44444444)
79    /// - `BSC_RPC` (default: https://bsc-dataseed.binance.org)
80    /// - `BSC_CHAIN_ID` (default: 56)
81    /// - `PRIVATE_KEY` (optional)
82    /// - `L1_WTORA`, `L2_WTORA`, etc. (optional contract addresses)
83    pub fn from_env() -> Result<Self> {
84        // Load .env file if present (ignore error if not found)
85        let _ = dotenv();
86
87        let tensora_rpc = env::var("TENSORA_RPC")
88            .unwrap_or_else(|_| chains::TENSORA_RPC.to_string());
89
90        let tensora_chain_id = env::var("TENSORA_CHAIN_ID")
91            .unwrap_or_else(|_| chains::TENSORA_CHAIN_ID.to_string())
92            .parse()
93            .map_err(|_| TensoraError::Config("Invalid TENSORA_CHAIN_ID".to_string()))?;
94
95        let bsc_rpc = env::var("BSC_RPC")
96            .unwrap_or_else(|_| chains::BSC_RPC.to_string());
97
98        let bsc_chain_id = env::var("BSC_CHAIN_ID")
99            .unwrap_or_else(|_| chains::BSC_CHAIN_ID.to_string())
100            .parse()
101            .map_err(|_| TensoraError::Config("Invalid BSC_CHAIN_ID".to_string()))?;
102
103        let private_key = env::var("PRIVATE_KEY").ok();
104
105        let addresses = AddressConfig {
106            l1_wtora: env::var("L1_WTORA").ok(),
107            l2_wtora: env::var("L2_WTORA").ok(),
108            l1_bridge: env::var("L1_BRIDGE").ok(),
109            l2_bridge: env::var("L2_BRIDGE").ok(),
110            staking_hub: env::var("STAKING_HUB").ok(),
111            subnet_factory: env::var("SUBNET_FACTORY").ok(),
112            entry_point: env::var("ENTRY_POINT").ok(),
113            paymaster: env::var("PAYMASTER").ok(),
114        };
115
116        Ok(Self {
117            tensora_rpc,
118            tensora_chain_id,
119            bsc_rpc,
120            bsc_chain_id,
121            private_key,
122            addresses,
123        })
124    }
125
126    /// Create a new configuration with custom values
127    pub fn new(tensora_rpc: String, bsc_rpc: String) -> Self {
128        Self {
129            tensora_rpc,
130            tensora_chain_id: chains::TENSORA_CHAIN_ID,
131            bsc_rpc,
132            bsc_chain_id: chains::BSC_CHAIN_ID,
133            private_key: None,
134            addresses: AddressConfig::default(),
135        }
136    }
137
138    /// Set private key for signing transactions
139    pub fn with_private_key(mut self, private_key: String) -> Self {
140        self.private_key = Some(private_key);
141        self
142    }
143
144    /// Set contract addresses
145    pub fn with_addresses(mut self, addresses: AddressConfig) -> Self {
146        self.addresses = addresses;
147        self
148    }
149
150    /// Validate configuration
151    pub fn validate(&self) -> Result<()> {
152        // Validate chain IDs
153        if self.tensora_chain_id != chains::TENSORA_CHAIN_ID {
154            return Err(TensoraError::InvalidChainId {
155                expected: chains::TENSORA_CHAIN_ID,
156                actual: self.tensora_chain_id,
157            });
158        }
159
160        if self.bsc_chain_id != chains::BSC_CHAIN_ID {
161            return Err(TensoraError::InvalidChainId {
162                expected: chains::BSC_CHAIN_ID,
163                actual: self.bsc_chain_id,
164            });
165        }
166
167        // Validate RPC URLs
168        if self.tensora_rpc.is_empty() {
169            return Err(TensoraError::Config("TENSORA_RPC cannot be empty".to_string()));
170        }
171
172        if self.bsc_rpc.is_empty() {
173            return Err(TensoraError::Config("BSC_RPC cannot be empty".to_string()));
174        }
175
176        Ok(())
177    }
178}
179
180impl Default for TensoraConfig {
181    fn default() -> Self {
182        Self {
183            tensora_rpc: chains::TENSORA_RPC.to_string(),
184            tensora_chain_id: chains::TENSORA_CHAIN_ID,
185            bsc_rpc: chains::BSC_RPC.to_string(),
186            bsc_chain_id: chains::BSC_CHAIN_ID,
187            private_key: None,
188            addresses: AddressConfig::default(),
189        }
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    #[test]
198    fn test_default_config() {
199        let config = TensoraConfig::default();
200        assert_eq!(config.tensora_chain_id, chains::TENSORA_CHAIN_ID);
201        assert_eq!(config.bsc_chain_id, chains::BSC_CHAIN_ID);
202    }
203
204    #[test]
205    fn test_validate_config() {
206        let config = TensoraConfig::default();
207        assert!(config.validate().is_ok());
208    }
209
210    #[test]
211    fn test_invalid_chain_id() {
212        let mut config = TensoraConfig::default();
213        config.tensora_chain_id = 99999;
214        assert!(config.validate().is_err());
215    }
216}
217