1use crate::core::{ChainId, ForgeGuardError};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ChainInfo {
10 pub name: String,
11 pub chain_id: u64,
12 pub currency: String,
13 pub explorer_url: String,
14 pub rpc_urls: Vec<String>,
15 pub is_evm: bool,
16 pub supported: bool,
17}
18
19pub trait ChainAnalyzer: Send + Sync {
21 fn chain_id(&self) -> ChainId;
23
24 fn info(&self) -> ChainInfo;
26
27 fn analyze_deployment(&self, _contract_address: &str) -> Result<Vec<String>, ForgeGuardError> {
29 Ok(Vec::new())
30 }
31
32 fn estimate_gas(&self, _gas_used: u64) -> Result<f64, ForgeGuardError> {
34 Ok(0.0)
35 }
36}
37
38#[derive(Clone)]
40pub struct ChainRegistry {
41 chains: HashMap<ChainId, ChainInfo>,
42}
43
44impl Default for ChainRegistry {
45 fn default() -> Self {
46 let mut registry = Self {
47 chains: HashMap::new(),
48 };
49 registry.register_defaults();
50 registry
51 }
52}
53
54impl ChainRegistry {
55 fn register_defaults(&mut self) {
57 let chains = vec![
58 ChainInfo {
59 name: "Ethereum".into(),
60 chain_id: 1,
61 currency: "ETH".into(),
62 explorer_url: "https://etherscan.io".into(),
63 rpc_urls: vec!["https://eth.llamarpc.com".into()],
64 is_evm: true,
65 supported: true,
66 },
67 ChainInfo {
68 name: "Base".into(),
69 chain_id: 8453,
70 currency: "ETH".into(),
71 explorer_url: "https://basescan.org".into(),
72 rpc_urls: vec!["https://base.llamarpc.com".into()],
73 is_evm: true,
74 supported: true,
75 },
76 ChainInfo {
77 name: "Arbitrum".into(),
78 chain_id: 42161,
79 currency: "ETH".into(),
80 explorer_url: "https://arbiscan.io".into(),
81 rpc_urls: vec!["https://arbitrum.llamarpc.com".into()],
82 is_evm: true,
83 supported: true,
84 },
85 ChainInfo {
86 name: "Optimism".into(),
87 chain_id: 10,
88 currency: "ETH".into(),
89 explorer_url: "https://optimistic.etherscan.io".into(),
90 rpc_urls: vec!["https://optimism.llamarpc.com".into()],
91 is_evm: true,
92 supported: true,
93 },
94 ChainInfo {
95 name: "Polygon".into(),
96 chain_id: 137,
97 currency: "MATIC".into(),
98 explorer_url: "https://polygonscan.com".into(),
99 rpc_urls: vec!["https://polygon.llamarpc.com".into()],
100 is_evm: true,
101 supported: true,
102 },
103 ChainInfo {
104 name: "BNB Chain".into(),
105 chain_id: 56,
106 currency: "BNB".into(),
107 explorer_url: "https://bscscan.com".into(),
108 rpc_urls: vec!["https://bsc.llamarpc.com".into()],
109 is_evm: true,
110 supported: true,
111 },
112 ChainInfo {
113 name: "Avalanche".into(),
114 chain_id: 43114,
115 currency: "AVAX".into(),
116 explorer_url: "https://snowtrace.io".into(),
117 rpc_urls: vec!["https://avalanche.llamarpc.com".into()],
118 is_evm: true,
119 supported: true,
120 },
121 ChainInfo {
122 name: "Scroll".into(),
123 chain_id: 534352,
124 currency: "ETH".into(),
125 explorer_url: "https://scrollscan.com".into(),
126 rpc_urls: vec!["https://scroll.llamarpc.com".into()],
127 is_evm: true,
128 supported: true,
129 },
130 ChainInfo {
131 name: "Linea".into(),
132 chain_id: 59144,
133 currency: "ETH".into(),
134 explorer_url: "https://lineascan.build".into(),
135 rpc_urls: vec!["https://linea.llamarpc.com".into()],
136 is_evm: true,
137 supported: true,
138 },
139 ChainInfo {
140 name: "Unichain".into(),
141 chain_id: 130,
142 currency: "ETH".into(),
143 explorer_url: "https://uniscan.xyz".into(),
144 rpc_urls: vec!["https://unichain.llamarpc.com".into()],
145 is_evm: true,
146 supported: true,
147 },
148 ChainInfo {
149 name: "ZKSync".into(),
150 chain_id: 324,
151 currency: "ETH".into(),
152 explorer_url: "https://explorer.zksync.io".into(),
153 rpc_urls: vec!["https://zksync.llamarpc.com".into()],
154 is_evm: true,
155 supported: true,
156 },
157 ChainInfo {
158 name: "HyperEVM".into(),
159 chain_id: 999,
160 currency: "HYPE".into(),
161 explorer_url: "https://explorer.hyperliquid.xyz".into(),
162 rpc_urls: vec!["https://hyperliquid.llamarpc.com".into()],
163 is_evm: true,
164 supported: true,
165 },
166 ChainInfo {
167 name: "Monad".into(),
168 chain_id: 10143,
169 currency: "MON".into(),
170 explorer_url: "https://explorer.monad.xyz".into(),
171 rpc_urls: vec!["https://monad.llamarpc.com".into()],
172 is_evm: true,
173 supported: true,
174 },
175 ChainInfo {
176 name: "Sonic".into(),
177 chain_id: 146,
178 currency: "S".into(),
179 explorer_url: "https://sonicscan.org".into(),
180 rpc_urls: vec!["https://sonic.llamarpc.com".into()],
181 is_evm: true,
182 supported: true,
183 },
184 ChainInfo {
185 name: "Blast".into(),
186 chain_id: 81457,
187 currency: "ETH".into(),
188 explorer_url: "https://blastscan.io".into(),
189 rpc_urls: vec!["https://blast.llamarpc.com".into()],
190 is_evm: true,
191 supported: true,
192 },
193 ChainInfo {
194 name: "Mantle".into(),
195 chain_id: 5000,
196 currency: "MNT".into(),
197 explorer_url: "https://mantlescan.xyz".into(),
198 rpc_urls: vec!["https://mantle.llamarpc.com".into()],
199 is_evm: true,
200 supported: true,
201 },
202 ChainInfo {
203 name: "Robinhood".into(),
204 chain_id: 31753,
205 currency: "ETH".into(),
206 explorer_url: "https://explorer.robinhood.com".into(),
207 rpc_urls: vec!["https://robinhood.llamarpc.com".into()],
208 is_evm: true,
209 supported: true,
210 },
211 ];
212
213 for chain in chains {
214 let id = ChainId::from_name(&chain.name);
215 self.chains.insert(id, chain);
216 }
217 }
218
219 pub fn list_chains(&self) -> Result<Vec<ChainInfo>, ForgeGuardError> {
221 let mut chains: Vec<ChainInfo> = self.chains.values().cloned().collect();
222 chains.sort_by(|a, b| a.name.cmp(&b.name));
223 Ok(chains)
224 }
225
226 pub fn get_chain(&self, name: &str) -> Option<&ChainInfo> {
228 let id = ChainId::from_name(name);
229 self.chains.get(&id)
230 }
231
232 pub fn register_chain(&mut self, info: ChainInfo) {
234 let id = ChainId::from_name(&info.name);
235 self.chains.insert(id, info);
236 }
237
238 pub fn evm_chains(&self) -> Vec<&ChainInfo> {
240 self.chains.values().filter(|c| c.is_evm).collect()
241 }
242
243 pub fn get_by_chain_id(&self, chain_id: u64) -> Option<&ChainInfo> {
245 self.chains.values().find(|c| c.chain_id == chain_id)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn test_registry_default() {
255 let registry = ChainRegistry::default();
256 let chains = registry.list_chains().unwrap();
257 assert_eq!(chains.len(), 17, "Should have 17 supported chains");
258 }
259
260 #[test]
261 fn test_get_chain_by_name() {
262 let registry = ChainRegistry::default();
263 let eth = registry.get_chain("ethereum").unwrap();
264 assert_eq!(eth.chain_id, 1);
265 assert_eq!(eth.currency, "ETH");
266 assert!(eth.is_evm);
267 assert!(eth.supported);
268
269 let base = registry.get_chain("base").unwrap();
270 assert_eq!(base.chain_id, 8453);
271 }
272
273 #[test]
274 fn test_get_chain_not_found() {
275 let registry = ChainRegistry::default();
276 assert!(registry.get_chain("nonexistent-chain").is_none());
277 }
278
279 #[test]
280 fn test_get_by_chain_id() {
281 let registry = ChainRegistry::default();
282 let chain = registry.get_by_chain_id(1).unwrap();
283 assert_eq!(chain.name, "Ethereum");
284
285 let chain = registry.get_by_chain_id(8453).unwrap();
286 assert_eq!(chain.name, "Base");
287 }
288
289 #[test]
290 fn test_evm_chains() {
291 let registry = ChainRegistry::default();
292 let evm = registry.evm_chains();
293 assert_eq!(evm.len(), 17, "All 17 chains should be EVM");
294 assert!(evm.iter().all(|c| c.is_evm));
295 }
296
297 #[test]
298 fn test_register_custom_chain() {
299 let mut registry = ChainRegistry::default();
300 let custom = ChainInfo {
301 name: "CustomChain".into(),
302 chain_id: 99999,
303 currency: "CST".into(),
304 explorer_url: "https://example.com".into(),
305 rpc_urls: vec!["https://rpc.example.com".into()],
306 is_evm: true,
307 supported: true,
308 };
309 registry.register_chain(custom.clone());
310
311 let retrieved = registry.get_chain("CustomChain").unwrap();
312 assert_eq!(retrieved.chain_id, 99999);
313 }
314
315 #[test]
316 fn test_chain_name_display() {
317 let registry = ChainRegistry::default();
318 let eth = registry.get_chain("ethereum").unwrap();
319 assert_eq!(eth.name, "Ethereum");
320 }
321}