Skip to main content

yldfi_common/
chains.rs

1//! Chain ID and name mappings for EVM-compatible networks
2
3use std::fmt;
4
5/// Common EVM chain identifiers
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[non_exhaustive]
8pub enum Chain {
9    /// Ethereum Mainnet (1)
10    Ethereum,
11    /// Goerli Testnet (5) - deprecated
12    Goerli,
13    /// Sepolia Testnet (11155111)
14    Sepolia,
15    /// Holesky Testnet (17000)
16    Holesky,
17    /// Optimism (10)
18    Optimism,
19    /// Optimism Sepolia (11155420)
20    OptimismSepolia,
21    /// BNB Smart Chain (56)
22    Bsc,
23    /// BNB Smart Chain Testnet (97)
24    BscTestnet,
25    /// Gnosis/xDai (100)
26    Gnosis,
27    /// Polygon/Matic (137)
28    Polygon,
29    /// Polygon Mumbai (80001) - deprecated
30    Mumbai,
31    /// Polygon Amoy (80002)
32    Amoy,
33    /// Fantom Opera (250)
34    Fantom,
35    /// Fantom Testnet (4002)
36    FantomTestnet,
37    /// Moonbeam (1284)
38    Moonbeam,
39    /// Moonriver (1285)
40    Moonriver,
41    /// Arbitrum One (42161)
42    Arbitrum,
43    /// Arbitrum Nova (42170)
44    ArbitrumNova,
45    /// Arbitrum Sepolia (421614)
46    ArbitrumSepolia,
47    /// Avalanche C-Chain (43114)
48    Avalanche,
49    /// Avalanche Fuji (43113)
50    AvalancheFuji,
51    /// Celo (42220)
52    Celo,
53    /// Base (8453)
54    Base,
55    /// Base Sepolia (84532)
56    BaseSepolia,
57    /// Linea (59144)
58    Linea,
59    /// Linea Testnet (59140)
60    LineaTestnet,
61    /// zkSync Era (324)
62    ZkSync,
63    /// zkSync Sepolia (300)
64    ZkSyncSepolia,
65    /// Scroll (534352)
66    Scroll,
67    /// Scroll Sepolia (534351)
68    ScrollSepolia,
69    /// Blast (81457)
70    Blast,
71    /// Blast Sepolia (168587773)
72    BlastSepolia,
73    /// Mantle (5000)
74    Mantle,
75    /// Mode (34443)
76    Mode,
77    /// Fraxtal (252)
78    Fraxtal,
79    /// Klaytn (8217)
80    Klaytn,
81    /// Aurora (1313161554)
82    Aurora,
83    /// Polygon zkEVM (1101)
84    PolygonZkEvm,
85    /// Unknown chain with custom ID
86    Other(u64),
87}
88
89impl Chain {
90    /// Get the chain ID
91    #[must_use]
92    pub const fn id(&self) -> u64 {
93        match self {
94            Self::Ethereum => 1,
95            Self::Goerli => 5,
96            Self::Sepolia => 11_155_111,
97            Self::Holesky => 17000,
98            Self::Optimism => 10,
99            Self::OptimismSepolia => 11_155_420,
100            Self::Bsc => 56,
101            Self::BscTestnet => 97,
102            Self::Gnosis => 100,
103            Self::Polygon => 137,
104            Self::Mumbai => 80001,
105            Self::Amoy => 80002,
106            Self::Fantom => 250,
107            Self::FantomTestnet => 4002,
108            Self::Moonbeam => 1284,
109            Self::Moonriver => 1285,
110            Self::Arbitrum => 42161,
111            Self::ArbitrumNova => 42170,
112            Self::ArbitrumSepolia => 421_614,
113            Self::Avalanche => 43114,
114            Self::AvalancheFuji => 43113,
115            Self::Celo => 42220,
116            Self::Base => 8453,
117            Self::BaseSepolia => 84532,
118            Self::Linea => 59144,
119            Self::LineaTestnet => 59140,
120            Self::ZkSync => 324,
121            Self::ZkSyncSepolia => 300,
122            Self::Scroll => 534_352,
123            Self::ScrollSepolia => 534_351,
124            Self::Blast => 81457,
125            Self::BlastSepolia => 168_587_773,
126            Self::Mantle => 5000,
127            Self::Mode => 34443,
128            Self::Fraxtal => 252,
129            Self::Klaytn => 8217,
130            Self::Aurora => 1_313_161_554,
131            Self::PolygonZkEvm => 1101,
132            Self::Other(id) => *id,
133        }
134    }
135
136    /// Get the chain name (lowercase, API-friendly)
137    #[must_use]
138    pub const fn name(&self) -> &'static str {
139        match self {
140            Self::Ethereum => "ethereum",
141            Self::Goerli => "goerli",
142            Self::Sepolia => "sepolia",
143            Self::Holesky => "holesky",
144            Self::Optimism => "optimism",
145            Self::OptimismSepolia => "optimism-sepolia",
146            Self::Bsc => "bsc",
147            Self::BscTestnet => "bsc-testnet",
148            Self::Gnosis => "gnosis",
149            Self::Polygon => "polygon",
150            Self::Mumbai => "mumbai",
151            Self::Amoy => "amoy",
152            Self::Fantom => "fantom",
153            Self::FantomTestnet => "fantom-testnet",
154            Self::Moonbeam => "moonbeam",
155            Self::Moonriver => "moonriver",
156            Self::Arbitrum => "arbitrum",
157            Self::ArbitrumNova => "arbitrum-nova",
158            Self::ArbitrumSepolia => "arbitrum-sepolia",
159            Self::Avalanche => "avalanche",
160            Self::AvalancheFuji => "avalanche-fuji",
161            Self::Celo => "celo",
162            Self::Base => "base",
163            Self::BaseSepolia => "base-sepolia",
164            Self::Linea => "linea",
165            Self::LineaTestnet => "linea-testnet",
166            Self::ZkSync => "zksync",
167            Self::ZkSyncSepolia => "zksync-sepolia",
168            Self::Scroll => "scroll",
169            Self::ScrollSepolia => "scroll-sepolia",
170            Self::Blast => "blast",
171            Self::BlastSepolia => "blast-sepolia",
172            Self::Mantle => "mantle",
173            Self::Mode => "mode",
174            Self::Fraxtal => "fraxtal",
175            Self::Klaytn => "klaytn",
176            Self::Aurora => "aurora",
177            Self::PolygonZkEvm => "polygon-zkevm",
178            Self::Other(_) => "unknown",
179        }
180    }
181
182    /// Get the display name (human-readable)
183    #[must_use]
184    pub const fn display_name(&self) -> &'static str {
185        match self {
186            Self::Ethereum => "Ethereum",
187            Self::Goerli => "Goerli",
188            Self::Sepolia => "Sepolia",
189            Self::Holesky => "Holesky",
190            Self::Optimism => "Optimism",
191            Self::OptimismSepolia => "Optimism Sepolia",
192            Self::Bsc => "BNB Smart Chain",
193            Self::BscTestnet => "BNB Smart Chain Testnet",
194            Self::Gnosis => "Gnosis",
195            Self::Polygon => "Polygon",
196            Self::Mumbai => "Mumbai",
197            Self::Amoy => "Amoy",
198            Self::Fantom => "Fantom",
199            Self::FantomTestnet => "Fantom Testnet",
200            Self::Moonbeam => "Moonbeam",
201            Self::Moonriver => "Moonriver",
202            Self::Arbitrum => "Arbitrum One",
203            Self::ArbitrumNova => "Arbitrum Nova",
204            Self::ArbitrumSepolia => "Arbitrum Sepolia",
205            Self::Avalanche => "Avalanche",
206            Self::AvalancheFuji => "Avalanche Fuji",
207            Self::Celo => "Celo",
208            Self::Base => "Base",
209            Self::BaseSepolia => "Base Sepolia",
210            Self::Linea => "Linea",
211            Self::LineaTestnet => "Linea Testnet",
212            Self::ZkSync => "zkSync Era",
213            Self::ZkSyncSepolia => "zkSync Sepolia",
214            Self::Scroll => "Scroll",
215            Self::ScrollSepolia => "Scroll Sepolia",
216            Self::Blast => "Blast",
217            Self::BlastSepolia => "Blast Sepolia",
218            Self::Mantle => "Mantle",
219            Self::Mode => "Mode",
220            Self::Fraxtal => "Fraxtal",
221            Self::Klaytn => "Klaytn",
222            Self::Aurora => "Aurora",
223            Self::PolygonZkEvm => "Polygon zkEVM",
224            Self::Other(_) => "Unknown",
225        }
226    }
227
228    /// Get the native currency symbol
229    #[must_use]
230    pub const fn native_currency(&self) -> &'static str {
231        match self {
232            Self::Ethereum | Self::Goerli | Self::Sepolia | Self::Holesky => "ETH",
233            Self::Optimism | Self::OptimismSepolia => "ETH",
234            Self::Bsc | Self::BscTestnet => "BNB",
235            Self::Gnosis => "xDAI",
236            Self::Polygon | Self::Mumbai | Self::Amoy => "MATIC",
237            Self::Fantom | Self::FantomTestnet => "FTM",
238            Self::Moonbeam => "GLMR",
239            Self::Moonriver => "MOVR",
240            Self::Arbitrum | Self::ArbitrumNova | Self::ArbitrumSepolia => "ETH",
241            Self::Avalanche | Self::AvalancheFuji => "AVAX",
242            Self::Celo => "CELO",
243            Self::Base | Self::BaseSepolia => "ETH",
244            Self::Linea | Self::LineaTestnet => "ETH",
245            Self::ZkSync | Self::ZkSyncSepolia => "ETH",
246            Self::Scroll | Self::ScrollSepolia => "ETH",
247            Self::Blast | Self::BlastSepolia => "ETH",
248            Self::Mantle => "MNT",
249            Self::Mode => "ETH",
250            Self::Fraxtal => "frxETH",
251            Self::Klaytn => "KLAY",
252            Self::Aurora => "ETH",
253            Self::PolygonZkEvm => "ETH",
254            Self::Other(_) => "ETH",
255        }
256    }
257
258    /// Check if this is a testnet
259    #[must_use]
260    pub const fn is_testnet(&self) -> bool {
261        matches!(
262            self,
263            Self::Goerli
264                | Self::Sepolia
265                | Self::Holesky
266                | Self::OptimismSepolia
267                | Self::BscTestnet
268                | Self::Mumbai
269                | Self::Amoy
270                | Self::FantomTestnet
271                | Self::ArbitrumSepolia
272                | Self::AvalancheFuji
273                | Self::BaseSepolia
274                | Self::LineaTestnet
275                | Self::ZkSyncSepolia
276                | Self::ScrollSepolia
277                | Self::BlastSepolia
278        )
279    }
280
281    /// Check if this is a mainnet
282    #[must_use]
283    pub const fn is_mainnet(&self) -> bool {
284        !self.is_testnet()
285    }
286
287    /// Get chain from ID
288    #[must_use]
289    pub const fn from_id(id: u64) -> Self {
290        match id {
291            1 => Self::Ethereum,
292            5 => Self::Goerli,
293            11_155_111 => Self::Sepolia,
294            17000 => Self::Holesky,
295            10 => Self::Optimism,
296            11_155_420 => Self::OptimismSepolia,
297            56 => Self::Bsc,
298            97 => Self::BscTestnet,
299            100 => Self::Gnosis,
300            137 => Self::Polygon,
301            80001 => Self::Mumbai,
302            80002 => Self::Amoy,
303            250 => Self::Fantom,
304            4002 => Self::FantomTestnet,
305            1284 => Self::Moonbeam,
306            1285 => Self::Moonriver,
307            42161 => Self::Arbitrum,
308            42170 => Self::ArbitrumNova,
309            421_614 => Self::ArbitrumSepolia,
310            43114 => Self::Avalanche,
311            43113 => Self::AvalancheFuji,
312            42220 => Self::Celo,
313            8453 => Self::Base,
314            84532 => Self::BaseSepolia,
315            59144 => Self::Linea,
316            59140 => Self::LineaTestnet,
317            324 => Self::ZkSync,
318            300 => Self::ZkSyncSepolia,
319            534_352 => Self::Scroll,
320            534_351 => Self::ScrollSepolia,
321            81457 => Self::Blast,
322            168_587_773 => Self::BlastSepolia,
323            5000 => Self::Mantle,
324            34443 => Self::Mode,
325            252 => Self::Fraxtal,
326            8217 => Self::Klaytn,
327            1_313_161_554 => Self::Aurora,
328            1101 => Self::PolygonZkEvm,
329            _ => Self::Other(id),
330        }
331    }
332
333    /// Get chain from name (case-insensitive)
334    #[must_use]
335    pub fn from_name(name: &str) -> Option<Self> {
336        let name_lower = name.to_lowercase();
337        match name_lower.as_str() {
338            "ethereum" | "eth" | "mainnet" => Some(Self::Ethereum),
339            "goerli" => Some(Self::Goerli),
340            "sepolia" => Some(Self::Sepolia),
341            "holesky" => Some(Self::Holesky),
342            "optimism" | "op" => Some(Self::Optimism),
343            "optimism-sepolia" | "op-sepolia" => Some(Self::OptimismSepolia),
344            "bsc" | "bnb" | "binance" => Some(Self::Bsc),
345            "bsc-testnet" | "bnb-testnet" => Some(Self::BscTestnet),
346            "gnosis" | "xdai" => Some(Self::Gnosis),
347            "polygon" | "matic" => Some(Self::Polygon),
348            "mumbai" => Some(Self::Mumbai),
349            "amoy" => Some(Self::Amoy),
350            "fantom" | "ftm" => Some(Self::Fantom),
351            "fantom-testnet" => Some(Self::FantomTestnet),
352            "moonbeam" => Some(Self::Moonbeam),
353            "moonriver" => Some(Self::Moonriver),
354            "arbitrum" | "arb" => Some(Self::Arbitrum),
355            "arbitrum-nova" | "arb-nova" => Some(Self::ArbitrumNova),
356            "arbitrum-sepolia" | "arb-sepolia" => Some(Self::ArbitrumSepolia),
357            "avalanche" | "avax" => Some(Self::Avalanche),
358            "avalanche-fuji" | "fuji" => Some(Self::AvalancheFuji),
359            "celo" => Some(Self::Celo),
360            "base" => Some(Self::Base),
361            "base-sepolia" => Some(Self::BaseSepolia),
362            "linea" => Some(Self::Linea),
363            "linea-testnet" => Some(Self::LineaTestnet),
364            "zksync" | "zksync-era" | "era" => Some(Self::ZkSync),
365            "zksync-sepolia" => Some(Self::ZkSyncSepolia),
366            "scroll" => Some(Self::Scroll),
367            "scroll-sepolia" => Some(Self::ScrollSepolia),
368            "blast" => Some(Self::Blast),
369            "blast-sepolia" => Some(Self::BlastSepolia),
370            "mantle" => Some(Self::Mantle),
371            "mode" => Some(Self::Mode),
372            "fraxtal" => Some(Self::Fraxtal),
373            "klaytn" | "klay" => Some(Self::Klaytn),
374            "aurora" => Some(Self::Aurora),
375            "polygon-zkevm" | "polygonzkevm" | "zkevm" => Some(Self::PolygonZkEvm),
376            _ => None,
377        }
378    }
379
380    /// List all known mainnets
381    #[must_use]
382    pub const fn mainnets() -> &'static [Chain] {
383        &[
384            Self::Ethereum,
385            Self::Optimism,
386            Self::Bsc,
387            Self::Gnosis,
388            Self::Polygon,
389            Self::Fantom,
390            Self::Moonbeam,
391            Self::Moonriver,
392            Self::Arbitrum,
393            Self::ArbitrumNova,
394            Self::Avalanche,
395            Self::Celo,
396            Self::Base,
397            Self::Linea,
398            Self::ZkSync,
399            Self::Scroll,
400            Self::Blast,
401            Self::Mantle,
402            Self::Mode,
403            Self::Fraxtal,
404            Self::Klaytn,
405            Self::Aurora,
406            Self::PolygonZkEvm,
407        ]
408    }
409}
410
411impl fmt::Display for Chain {
412    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
413        write!(f, "{}", self.display_name())
414    }
415}
416
417impl From<u64> for Chain {
418    fn from(id: u64) -> Self {
419        Self::from_id(id)
420    }
421}
422
423impl From<Chain> for u64 {
424    fn from(chain: Chain) -> Self {
425        chain.id()
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432
433    #[test]
434    fn test_chain_id_roundtrip() {
435        assert_eq!(Chain::from_id(1), Chain::Ethereum);
436        assert_eq!(Chain::Ethereum.id(), 1);
437
438        assert_eq!(Chain::from_id(137), Chain::Polygon);
439        assert_eq!(Chain::Polygon.id(), 137);
440
441        assert_eq!(Chain::from_id(8453), Chain::Base);
442        assert_eq!(Chain::Base.id(), 8453);
443    }
444
445    #[test]
446    fn test_chain_from_name() {
447        assert_eq!(Chain::from_name("ethereum"), Some(Chain::Ethereum));
448        assert_eq!(Chain::from_name("ETH"), Some(Chain::Ethereum));
449        assert_eq!(Chain::from_name("mainnet"), Some(Chain::Ethereum));
450
451        assert_eq!(Chain::from_name("polygon"), Some(Chain::Polygon));
452        assert_eq!(Chain::from_name("MATIC"), Some(Chain::Polygon));
453
454        assert_eq!(Chain::from_name("base"), Some(Chain::Base));
455        assert_eq!(Chain::from_name("unknown-chain"), None);
456    }
457
458    #[test]
459    fn test_is_testnet() {
460        assert!(!Chain::Ethereum.is_testnet());
461        assert!(Chain::Sepolia.is_testnet());
462        assert!(!Chain::Base.is_testnet());
463        assert!(Chain::BaseSepolia.is_testnet());
464    }
465
466    #[test]
467    fn test_native_currency() {
468        assert_eq!(Chain::Ethereum.native_currency(), "ETH");
469        assert_eq!(Chain::Polygon.native_currency(), "MATIC");
470        assert_eq!(Chain::Bsc.native_currency(), "BNB");
471        assert_eq!(Chain::Avalanche.native_currency(), "AVAX");
472    }
473
474    #[test]
475    fn test_unknown_chain() {
476        let chain = Chain::from_id(999999);
477        assert_eq!(chain, Chain::Other(999999));
478        assert_eq!(chain.id(), 999999);
479        assert_eq!(chain.name(), "unknown");
480    }
481}