ethers_core/types/
chain.rs

1use super::{U128, U256, U512, U64};
2use serde::{Deserialize, Serialize, Serializer};
3use std::{fmt, time::Duration};
4use strum::{AsRefStr, EnumCount, EnumIter, EnumString, VariantNames};
5
6// compatibility re-export
7#[doc(hidden)]
8pub use num_enum::{TryFromPrimitive, TryFromPrimitiveError};
9#[doc(hidden)]
10pub type ParseChainError = TryFromPrimitiveError<Chain>;
11
12// When adding a new chain:
13//   1. add new variant to the Chain enum;
14//   2. add extra information in the last `impl` block (explorer URLs, block time) when applicable;
15//   3. (optional) add aliases:
16//     - Strum (in kebab-case): `#[strum(to_string = "<main>", serialize = "<aliasX>", ...)]`
17//      `to_string = "<main>"` must be present and will be used in `Display`, `Serialize`
18//      and `FromStr`, while `serialize = "<aliasX>"` will be appended to `FromStr`.
19//      More info: <https://docs.rs/strum/latest/strum/additional_attributes/index.html#attributes-on-variants>
20//     - Serde (in snake_case): `#[serde(alias = "<aliasX>", ...)]`
21//      Aliases are appended to the `Deserialize` implementation.
22//      More info: <https://serde.rs/variant-attrs.html>
23//     - Add a test at the bottom of the file
24
25// We don't derive Serialize because it is manually implemented using AsRef<str> and it would
26// break a lot of things since Serialize is `kebab-case` vs Deserialize `snake_case`.
27// This means that the Chain type is not "round-trippable", because the Serialize and Deserialize
28// implementations do not use the same case style.
29
30/// An Ethereum EIP-155 chain.
31#[derive(
32    Clone,
33    Copy,
34    Debug,
35    PartialEq,
36    Eq,
37    PartialOrd,
38    Ord,
39    Hash,
40    AsRefStr,         // AsRef<str>, fmt::Display and serde::Serialize
41    VariantNames,     // Chain::VARIANTS
42    EnumString,       // FromStr, TryFrom<&str>
43    EnumIter,         // Chain::iter
44    EnumCount,        // Chain::COUNT
45    TryFromPrimitive, // TryFrom<u64>
46    Deserialize,
47)]
48#[serde(rename_all = "snake_case")]
49#[strum(serialize_all = "kebab-case")]
50#[repr(u64)]
51pub enum Chain {
52    #[strum(to_string = "mainnet", serialize = "ethlive")]
53    #[serde(alias = "ethlive")]
54    Mainnet = 1,
55    Morden = 2,
56    Ropsten = 3,
57    Rinkeby = 4,
58    Goerli = 5,
59    Kovan = 42,
60    Holesky = 17000,
61    Sepolia = 11155111,
62
63    Optimism = 10,
64    OptimismKovan = 69,
65    OptimismGoerli = 420,
66    OptimismSepolia = 11155420,
67
68    Arbitrum = 42161,
69    ArbitrumTestnet = 421611,
70    ArbitrumGoerli = 421613,
71    ArbitrumSepolia = 421614,
72    ArbitrumNova = 42170,
73
74    Cronos = 25,
75    CronosTestnet = 338,
76
77    Rsk = 30,
78
79    #[strum(to_string = "bsc", serialize = "binance-smart-chain")]
80    #[serde(alias = "bsc")]
81    BinanceSmartChain = 56,
82    #[strum(to_string = "bsc-testnet", serialize = "binance-smart-chain-testnet")]
83    #[serde(alias = "bsc_testnet")]
84    BinanceSmartChainTestnet = 97,
85
86    Poa = 99,
87    Sokol = 77,
88
89    #[serde(alias = "scroll_sepolia", alias = "scroll_sepolia_testnet")]
90    ScrollSepolia = 534351,
91    Scroll = 534352,
92    ScrollAlphaTestnet = 534353,
93
94    Metis = 1088,
95
96    #[strum(to_string = "xdai", serialize = "gnosis", serialize = "gnosis-chain")]
97    #[serde(alias = "xdai", alias = "gnosis", alias = "gnosis_chain")]
98    Gnosis = 100,
99
100    Polygon = 137,
101    #[strum(to_string = "mumbai", serialize = "polygon-mumbai")]
102    #[serde(alias = "mumbai")]
103    PolygonMumbai = 80001,
104    #[strum(to_string = "amoy", serialize = "polygon-amoy")]
105    #[serde(alias = "amoy")]
106    PolygonAmoy = 80002,
107    #[strum(serialize = "polygon-zkevm", serialize = "zkevm")]
108    #[serde(alias = "zkevm", alias = "polygon_zkevm")]
109    PolygonZkEvm = 1101,
110    #[strum(serialize = "polygon-zkevm-testnet", serialize = "zkevm-testnet")]
111    #[serde(alias = "zkevm_testnet", alias = "polygon_zkevm_testnet")]
112    PolygonZkEvmTestnet = 1442,
113
114    Fantom = 250,
115    FantomTestnet = 4002,
116
117    Moonbeam = 1284,
118    MoonbeamDev = 1281,
119
120    Moonriver = 1285,
121
122    Moonbase = 1287,
123
124    Dev = 1337,
125    #[strum(to_string = "anvil-hardhat", serialize = "anvil", serialize = "hardhat")]
126    #[serde(alias = "anvil", alias = "hardhat")]
127    AnvilHardhat = 31337,
128
129    Evmos = 9001,
130    EvmosTestnet = 9000,
131
132    Chiado = 10200,
133
134    Oasis = 26863,
135
136    Emerald = 42262,
137    EmeraldTestnet = 42261,
138
139    FilecoinMainnet = 314,
140    FilecoinCalibrationTestnet = 314159,
141
142    Avalanche = 43114,
143    #[strum(to_string = "fuji", serialize = "avalanche-fuji")]
144    #[serde(alias = "fuji")]
145    AvalancheFuji = 43113,
146
147    Celo = 42220,
148    CeloAlfajores = 44787,
149    CeloBaklava = 62320,
150
151    Aurora = 1313161554,
152    AuroraTestnet = 1313161555,
153
154    Canto = 7700,
155    CantoTestnet = 740,
156
157    Boba = 288,
158
159    #[strum(to_string = "base")]
160    #[serde(alias = "base")]
161    Base = 8453,
162    #[strum(to_string = "base-goerli")]
163    #[serde(alias = "base_goerli")]
164    BaseGoerli = 84531,
165    #[strum(to_string = "base-sepolia")]
166    #[serde(alias = "base_sepolia")]
167    BaseSepolia = 84532,
168
169    Blast = 81457,
170    BlastSepolia = 168587773,
171
172    Linea = 59144,
173    LineaTestnet = 59140,
174
175    #[strum(to_string = "zksync")]
176    #[serde(alias = "zksync")]
177    ZkSync = 324,
178    #[strum(to_string = "zksync-testnet")]
179    #[serde(alias = "zksync_testnet")]
180    ZkSyncTestnet = 280,
181
182    #[strum(to_string = "mantle")]
183    #[serde(alias = "mantle")]
184    Mantle = 5000,
185    #[strum(to_string = "mantle-testnet")]
186    #[serde(alias = "mantle_testnet")]
187    MantleTestnet = 5001,
188
189    Viction = 88,
190
191    Zora = 7777777,
192    ZoraGoerli = 999,
193    ZoraSepolia = 999999999,
194
195    Mode = 34443,
196    ModeSepolia = 919,
197
198    Elastos = 20,
199}
200
201// === impl Chain ===
202
203// This must be implemented manually so we avoid a conflict with `TryFromPrimitive` where it treats
204// the `#[default]` attribute as its own `#[num_enum(default)]`
205impl Default for Chain {
206    fn default() -> Self {
207        Self::Mainnet
208    }
209}
210
211macro_rules! impl_into_numeric {
212    ($($ty:ty)+) => {$(
213        impl From<Chain> for $ty {
214            fn from(chain: Chain) -> Self {
215                u64::from(chain).into()
216            }
217        }
218    )+};
219}
220
221macro_rules! impl_try_from_numeric {
222    ($($native:ty)+ ; $($primitive:ty)*) => {
223        $(
224            impl TryFrom<$native> for Chain {
225                type Error = ParseChainError;
226
227                fn try_from(value: $native) -> Result<Self, Self::Error> {
228                    (value as u64).try_into()
229                }
230            }
231        )+
232
233        $(
234            impl TryFrom<$primitive> for Chain {
235                type Error = ParseChainError;
236
237                fn try_from(value: $primitive) -> Result<Self, Self::Error> {
238                    if value.bits() > 64 {
239                        // `TryFromPrimitiveError` only has a `number` field which has the same type
240                        // as the `#[repr(_)]` attribute on the enum.
241                        return Err(ParseChainError { number: value.low_u64() })
242                    }
243                    value.low_u64().try_into()
244                }
245            }
246        )*
247    };
248}
249
250impl From<Chain> for u64 {
251    fn from(chain: Chain) -> Self {
252        chain as u64
253    }
254}
255
256impl_into_numeric!(u128 U64 U128 U256 U512);
257
258impl TryFrom<U64> for Chain {
259    type Error = ParseChainError;
260
261    fn try_from(value: U64) -> Result<Self, Self::Error> {
262        value.low_u64().try_into()
263    }
264}
265
266impl_try_from_numeric!(u8 u16 u32 usize; U128 U256 U512);
267
268impl fmt::Display for Chain {
269    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
270        f.pad(self.as_ref())
271    }
272}
273
274impl Serialize for Chain {
275    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
276    where
277        S: Serializer,
278    {
279        s.serialize_str(self.as_ref())
280    }
281}
282
283// NB: all utility functions *should* be explicitly exhaustive (not use `_` matcher) so we don't
284//     forget to update them when adding a new `Chain` variant.
285#[allow(clippy::match_like_matches_macro)]
286impl Chain {
287    /// Returns the chain's average blocktime, if applicable.
288    ///
289    /// It can be beneficial to know the average blocktime to adjust the polling of an HTTP provider
290    /// for example.
291    ///
292    /// **Note:** this is not an accurate average, but is rather a sensible default derived from
293    /// blocktime charts such as [Etherscan's](https://etherscan.com/chart/blocktime)
294    /// or [Polygonscan's](https://polygonscan.com/chart/blocktime).
295    ///
296    /// # Examples
297    ///
298    /// ```
299    /// use ethers_core::types::Chain;
300    /// use std::time::Duration;
301    ///
302    /// assert_eq!(
303    ///     Chain::Mainnet.average_blocktime_hint(),
304    ///     Some(Duration::from_millis(12_000)),
305    /// );
306    /// assert_eq!(
307    ///     Chain::Optimism.average_blocktime_hint(),
308    ///     Some(Duration::from_millis(2_000)),
309    /// );
310    /// ```
311    pub const fn average_blocktime_hint(&self) -> Option<Duration> {
312        use Chain::*;
313
314        let ms = match self {
315            Mainnet => 12_000,
316            Arbitrum | ArbitrumTestnet | ArbitrumGoerli | ArbitrumSepolia | ArbitrumNova => 1_300,
317            Optimism | OptimismGoerli | OptimismSepolia => 2_000,
318            Polygon | PolygonMumbai | PolygonAmoy => 2_100,
319            Moonbeam | Moonriver => 12_500,
320            BinanceSmartChain | BinanceSmartChainTestnet => 3_000,
321            Avalanche | AvalancheFuji => 2_000,
322            Fantom | FantomTestnet => 1_200,
323            Cronos | CronosTestnet | Canto | CantoTestnet => 5_700,
324            Evmos | EvmosTestnet => 1_900,
325            Aurora | AuroraTestnet => 1_100,
326            Oasis => 5_500,
327            Emerald => 6_000,
328            Dev | AnvilHardhat => 200,
329            Celo | CeloAlfajores | CeloBaklava => 5_000,
330            FilecoinCalibrationTestnet | FilecoinMainnet => 30_000,
331            Scroll | ScrollSepolia | ScrollAlphaTestnet => 3_000,
332            Gnosis | Chiado => 5_000,
333            Viction => 2_000,
334            Mode | ModeSepolia => 2_000,
335            Elastos => 5_000,
336            // Explicitly exhaustive. See NB above.
337            Morden | Ropsten | Rinkeby | Goerli | Kovan | Sepolia | Holesky | Moonbase
338            | MoonbeamDev | OptimismKovan | Poa | Sokol | Rsk | EmeraldTestnet | Boba | Base
339            | BaseGoerli | BaseSepolia | Blast | BlastSepolia | ZkSync | ZkSyncTestnet
340            | PolygonZkEvm | PolygonZkEvmTestnet | Metis | Linea | LineaTestnet | Mantle
341            | MantleTestnet | Zora | ZoraGoerli | ZoraSepolia => return None,
342        };
343
344        Some(Duration::from_millis(ms))
345    }
346
347    /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
348    ///
349    /// # Examples
350    ///
351    /// ```
352    /// use ethers_core::types::Chain;
353    ///
354    /// assert!(!Chain::Mainnet.is_legacy());
355    /// assert!(Chain::Celo.is_legacy());
356    /// ```
357    pub const fn is_legacy(&self) -> bool {
358        use Chain::*;
359
360        match self {
361            // Known legacy chains / non EIP-1559 compliant
362            OptimismKovan
363            | Fantom
364            | FantomTestnet
365            | BinanceSmartChain
366            | BinanceSmartChainTestnet
367            | ArbitrumTestnet
368            | Rsk
369            | Oasis
370            | Emerald
371            | EmeraldTestnet
372            | Celo
373            | CeloAlfajores
374            | CeloBaklava
375            | Boba
376            | ZkSync
377            | ZkSyncTestnet
378            | Mantle
379            | MantleTestnet
380            | PolygonZkEvm
381            | PolygonZkEvmTestnet
382            | Metis
383            | Viction
384            | Scroll
385            | ScrollSepolia
386            | Elastos => true,
387
388            // Known EIP-1559 chains
389            Mainnet
390            | Goerli
391            | Sepolia
392            | Holesky
393            | Base
394            | BaseGoerli
395            | BaseSepolia
396            | Blast
397            | BlastSepolia
398            | Optimism
399            | OptimismGoerli
400            | OptimismSepolia
401            | Polygon
402            | PolygonMumbai
403            | PolygonAmoy
404            | Avalanche
405            | AvalancheFuji
406            | Arbitrum
407            | ArbitrumGoerli
408            | ArbitrumSepolia
409            | ArbitrumNova
410            | FilecoinMainnet
411            | Linea
412            | LineaTestnet
413            | FilecoinCalibrationTestnet
414            | Gnosis
415            | Chiado
416            | Mode
417            | ModeSepolia
418            | Zora
419            | ZoraGoerli
420            | ZoraSepolia => false,
421
422            // Unknown / not applicable, default to false for backwards compatibility
423            Dev | AnvilHardhat | Morden | Ropsten | Rinkeby | Cronos | CronosTestnet | Kovan
424            | Sokol | Poa | Moonbeam | MoonbeamDev | Moonriver | Moonbase | Evmos
425            | EvmosTestnet | Aurora | AuroraTestnet | Canto | CantoTestnet | ScrollAlphaTestnet => {
426                false
427            }
428        }
429    }
430
431    /// Returns whether the chain supports the `PUSH0` opcode or not.
432    ///
433    /// For more information, see EIP-3855:
434    /// `<https://eips.ethereum.org/EIPS/eip-3855>`
435    pub const fn supports_push0(&self) -> bool {
436        match self {
437            Chain::Mainnet | Chain::Goerli | Chain::Sepolia | Chain::Gnosis | Chain::Chiado => true,
438            _ => false,
439        }
440    }
441
442    /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
443    ///
444    /// Returns `(API_URL, BASE_URL)`
445    ///
446    /// # Examples
447    ///
448    /// ```
449    /// use ethers_core::types::Chain;
450    ///
451    /// assert_eq!(
452    ///     Chain::Mainnet.etherscan_urls(),
453    ///     Some(("https://api.etherscan.io/api", "https://etherscan.io"))
454    /// );
455    /// assert_eq!(
456    ///     Chain::Avalanche.etherscan_urls(),
457    ///     Some(("https://api.snowtrace.io/api", "https://snowtrace.io"))
458    /// );
459    /// assert_eq!(Chain::AnvilHardhat.etherscan_urls(), None);
460    /// ```
461    pub const fn etherscan_urls(&self) -> Option<(&'static str, &'static str)> {
462        use Chain::*;
463
464        let urls = match self {
465            Mainnet => ("https://api.etherscan.io/api", "https://etherscan.io"),
466            Ropsten => ("https://api-ropsten.etherscan.io/api", "https://ropsten.etherscan.io"),
467            Kovan => ("https://api-kovan.etherscan.io/api", "https://kovan.etherscan.io"),
468            Rinkeby => ("https://api-rinkeby.etherscan.io/api", "https://rinkeby.etherscan.io"),
469            Goerli => ("https://api-goerli.etherscan.io/api", "https://goerli.etherscan.io"),
470            Sepolia => ("https://api-sepolia.etherscan.io/api", "https://sepolia.etherscan.io"),
471            Holesky => ("https://api-holesky.etherscan.io/api", "https://holesky.etherscan.io"),
472
473            Polygon => ("https://api.polygonscan.com/api", "https://polygonscan.com"),
474            PolygonMumbai => {
475                ("https://api-testnet.polygonscan.com/api", "https://mumbai.polygonscan.com")
476            }
477            PolygonAmoy => ("https://rpc-amoy.polygon.technology", "https://www.oklink.com/amoy"),
478
479            PolygonZkEvm => {
480                ("https://api-zkevm.polygonscan.com/api", "https://zkevm.polygonscan.com")
481            }
482            PolygonZkEvmTestnet => (
483                "https://api-testnet-zkevm.polygonscan.com/api",
484                "https://testnet-zkevm.polygonscan.com",
485            ),
486
487            Avalanche => ("https://api.snowtrace.io/api", "https://snowtrace.io"),
488            AvalancheFuji => {
489                ("https://api-testnet.snowtrace.io/api", "https://testnet.snowtrace.io")
490            }
491
492            Optimism => {
493                ("https://api-optimistic.etherscan.io/api", "https://optimistic.etherscan.io")
494            }
495            OptimismGoerli => (
496                "https://api-goerli-optimistic.etherscan.io/api",
497                "https://goerli-optimism.etherscan.io",
498            ),
499            OptimismKovan => (
500                "https://api-kovan-optimistic.etherscan.io/api",
501                "https://kovan-optimistic.etherscan.io",
502            ),
503            OptimismSepolia => (
504                "https://api-sepolia-optimistic.etherscan.io/api",
505                "https://sepolia-optimism.etherscan.io",
506            ),
507
508            Fantom => ("https://api.ftmscan.com/api", "https://ftmscan.com"),
509            FantomTestnet => ("https://api-testnet.ftmscan.com/api", "https://testnet.ftmscan.com"),
510
511            BinanceSmartChain => ("https://api.bscscan.com/api", "https://bscscan.com"),
512            BinanceSmartChainTestnet => {
513                ("https://api-testnet.bscscan.com/api", "https://testnet.bscscan.com")
514            }
515
516            Arbitrum => ("https://api.arbiscan.io/api", "https://arbiscan.io"),
517            ArbitrumTestnet => {
518                ("https://api-testnet.arbiscan.io/api", "https://testnet.arbiscan.io")
519            }
520            ArbitrumGoerli => ("https://api-goerli.arbiscan.io/api", "https://goerli.arbiscan.io"),
521            ArbitrumSepolia => {
522                ("https://api-sepolia.arbiscan.io/api", "https://sepolia.arbiscan.io")
523            }
524            ArbitrumNova => ("https://api-nova.arbiscan.io/api", "https://nova.arbiscan.io/"),
525
526            Cronos => ("https://api.cronoscan.com/api", "https://cronoscan.com"),
527            CronosTestnet => {
528                ("https://api-testnet.cronoscan.com/api", "https://testnet.cronoscan.com")
529            }
530
531            Moonbeam => ("https://api-moonbeam.moonscan.io/api", "https://moonbeam.moonscan.io/"),
532            Moonbase => ("https://api-moonbase.moonscan.io/api", "https://moonbase.moonscan.io/"),
533            Moonriver => ("https://api-moonriver.moonscan.io/api", "https://moonriver.moonscan.io"),
534
535            Gnosis => ("https://api.gnosisscan.io/api", "https://gnosisscan.io"),
536
537            Scroll => ("https://api.scrollscan.com/api", "https://scrollscan.com"),
538            ScrollSepolia => {
539                ("https://api-sepolia.scrollscan.com/api", "https://sepolia.scrollscan.com")
540            }
541            ScrollAlphaTestnet => {
542                ("https://alpha-blockscout.scroll.io/api", "https://alpha-blockscout.scroll.io/")
543            }
544
545            Metis => (
546                "https://api.routescan.io/v2/network/mainnet/evm/1088/etherscan/api",
547                "https://explorer.metis.io/",
548            ),
549
550            Chiado => {
551                ("https://blockscout.chiadochain.net/api", "https://blockscout.chiadochain.net")
552            }
553
554            FilecoinCalibrationTestnet => (
555                "https://api.calibration.node.glif.io/rpc/v1",
556                "https://calibration.filfox.info/en",
557            ),
558
559            Sokol => ("https://blockscout.com/poa/sokol/api", "https://blockscout.com/poa/sokol"),
560
561            Poa => ("https://blockscout.com/poa/core/api", "https://blockscout.com/poa/core"),
562
563            Rsk => ("https://blockscout.com/rsk/mainnet/api", "https://blockscout.com/rsk/mainnet"),
564
565            Oasis => ("https://scan.oasischain.io/api", "https://scan.oasischain.io/"),
566
567            Emerald => {
568                ("https://explorer.emerald.oasis.dev/api", "https://explorer.emerald.oasis.dev/")
569            }
570            EmeraldTestnet => (
571                "https://testnet.explorer.emerald.oasis.dev/api",
572                "https://testnet.explorer.emerald.oasis.dev/",
573            ),
574
575            Aurora => ("https://api.aurorascan.dev/api", "https://aurorascan.dev"),
576            AuroraTestnet => {
577                ("https://testnet.aurorascan.dev/api", "https://testnet.aurorascan.dev")
578            }
579
580            Evmos => ("https://evm.evmos.org/api", "https://evm.evmos.org/"),
581            EvmosTestnet => ("https://evm.evmos.dev/api", "https://evm.evmos.dev/"),
582
583            Celo => ("https://explorer.celo.org/mainnet/api", "https://explorer.celo.org/mainnet"),
584            CeloAlfajores => {
585                ("https://explorer.celo.org/alfajores/api", "https://explorer.celo.org/alfajores")
586            }
587            CeloBaklava => {
588                ("https://explorer.celo.org/baklava/api", "https://explorer.celo.org/baklava")
589            }
590
591            Canto => ("https://evm.explorer.canto.io/api", "https://evm.explorer.canto.io/"),
592            CantoTestnet => (
593                "https://testnet-explorer.canto.neobase.one/api",
594                "https://testnet-explorer.canto.neobase.one/",
595            ),
596
597            Boba => ("https://api.bobascan.com/api", "https://bobascan.com"),
598
599            Base => ("https://api.basescan.org/api", "https://basescan.org"),
600
601            BaseGoerli => ("https://api-goerli.basescan.org/api", "https://goerli.basescan.org"),
602            BaseSepolia => ("https://api-sepolia.basescan.org/api", "https://sepolia.basescan.org"),
603
604            Blast => (
605                "https://api.routescan.io/v2/network/mainnet/evm/81457/etherscan",
606                "https://blastscan.io",
607            ),
608            BlastSepolia => (
609                "https://api.routescan.io/v2/network/testnet/evm/168587773/etherscan",
610                "https://testnet.blastscan.io",
611            ),
612
613            ZkSync => {
614                ("https://zksync2-mainnet-explorer.zksync.io/", "https://explorer.zksync.io/")
615            }
616            ZkSyncTestnet => (
617                "https://zksync2-testnet-explorer.zksync.dev/",
618                "https://goerli.explorer.zksync.io/",
619            ),
620            Linea => ("https://api.lineascan.build/api", "https://lineascan.build/"),
621            LineaTestnet => {
622                ("https://explorer.goerli.linea.build/api", "https://explorer.goerli.linea.build/")
623            }
624            Mantle => ("https://explorer.mantle.xyz/api", "https://explorer.mantle.xyz"),
625            MantleTestnet => {
626                ("https://explorer.testnet.mantle.xyz/api", "https://explorer.testnet.mantle.xyz")
627            }
628
629            Zora => ("https://explorer.zora.energy/api", "https://explorer.zora.energy"),
630            ZoraGoerli => {
631                ("https://testnet.explorer.zora.energy/api", "https://testnet.explorer.zora.energy")
632            }
633            ZoraSepolia => {
634                ("https://sepolia.explorer.zora.energy/api", "https://sepolia.explorer.zora.energy")
635            }
636
637            AnvilHardhat | Dev | Morden | MoonbeamDev | FilecoinMainnet => {
638                // this is explicitly exhaustive so we don't forget to add new urls when adding a
639                // new chain
640                return None;
641            }
642            Viction => ("https://www.vicscan.xyz/api", "https://www.vicscan.xyz"),
643
644            Mode => ("https://explorer.mode.network/api", "https://explorer.mode.network"),
645            ModeSepolia => (
646                "https://sepolia.explorer.mode.network/api",
647                "https://sepolia.explorer.mode.network",
648            ),
649            Elastos => ("https://api.elastos.io/eth", "https://esc.elastos.io/"),
650        };
651
652        Some(urls)
653    }
654
655    /// Returns the chain's blockchain explorer's API key environment variable's default name.
656    ///
657    /// # Examples
658    ///
659    /// ```
660    /// use ethers_core::types::Chain;
661    ///
662    /// assert_eq!(Chain::Mainnet.etherscan_api_key_name(), Some("ETHERSCAN_API_KEY"));
663    /// assert_eq!(Chain::AnvilHardhat.etherscan_api_key_name(), None);
664    /// ```
665    pub const fn etherscan_api_key_name(&self) -> Option<&'static str> {
666        use Chain::*;
667
668        let api_key_name = match self {
669            Mainnet
670            | Morden
671            | Ropsten
672            | Kovan
673            | Rinkeby
674            | Goerli
675            | Holesky
676            | Optimism
677            | OptimismGoerli
678            | OptimismKovan
679            | OptimismSepolia
680            | BinanceSmartChain
681            | BinanceSmartChainTestnet
682            | Arbitrum
683            | ArbitrumTestnet
684            | ArbitrumGoerli
685            | ArbitrumSepolia
686            | ArbitrumNova
687            | Cronos
688            | CronosTestnet
689            | Aurora
690            | AuroraTestnet
691            | Celo
692            | CeloAlfajores
693            | CeloBaklava
694            | Base
695            | Linea
696            | Mantle
697            | MantleTestnet
698            | BaseGoerli
699            | BaseSepolia
700            | Blast
701            | BlastSepolia
702            | Gnosis
703            | Scroll
704            | ScrollSepolia => "ETHERSCAN_API_KEY",
705
706            Avalanche | AvalancheFuji => "SNOWTRACE_API_KEY",
707
708            Polygon | PolygonMumbai | PolygonZkEvm | PolygonZkEvmTestnet | PolygonAmoy => {
709                "POLYGONSCAN_API_KEY"
710            }
711
712            Fantom | FantomTestnet => "FTMSCAN_API_KEY",
713
714            Moonbeam | Moonbase | MoonbeamDev | Moonriver => "MOONSCAN_API_KEY",
715
716            Canto | CantoTestnet | Zora | ZoraGoerli | ZoraSepolia | Mode | ModeSepolia => {
717                "BLOCKSCOUT_API_KEY"
718            }
719
720            Boba => "BOBASCAN_API_KEY",
721
722            // Explicitly exhaustive. See NB above.
723            ScrollAlphaTestnet
724            | Metis
725            | Chiado
726            | Sepolia
727            | Rsk
728            | Sokol
729            | Poa
730            | Oasis
731            | Emerald
732            | EmeraldTestnet
733            | Evmos
734            | EvmosTestnet
735            | AnvilHardhat
736            | Dev
737            | ZkSync
738            | ZkSyncTestnet
739            | FilecoinMainnet
740            | LineaTestnet
741            | Viction
742            | FilecoinCalibrationTestnet
743            | Elastos => return None,
744        };
745
746        Some(api_key_name)
747    }
748
749    /// Returns the chain's blockchain explorer's API key, from the environment variable with the
750    /// name specified in [`etherscan_api_key_name`](Chain::etherscan_api_key_name).
751    ///
752    /// # Examples
753    ///
754    /// ```
755    /// use ethers_core::types::Chain;
756    ///
757    /// let chain = Chain::Mainnet;
758    /// std::env::set_var(chain.etherscan_api_key_name().unwrap(), "KEY");
759    /// assert_eq!(chain.etherscan_api_key().as_deref(), Some("KEY"));
760    /// ```
761    pub fn etherscan_api_key(&self) -> Option<String> {
762        self.etherscan_api_key_name().and_then(|name| std::env::var(name).ok())
763    }
764}
765
766#[cfg(test)]
767mod tests {
768    use super::*;
769    use strum::IntoEnumIterator;
770
771    #[test]
772    fn default() {
773        assert_eq!(serde_json::to_string(&Chain::default()).unwrap(), "\"mainnet\"");
774    }
775
776    #[test]
777    fn enum_iter() {
778        assert_eq!(Chain::COUNT, Chain::iter().size_hint().0);
779    }
780
781    #[test]
782    fn roundtrip_string() {
783        for chain in Chain::iter() {
784            let chain_string = chain.to_string();
785            assert_eq!(chain_string, format!("{chain}"));
786            assert_eq!(chain_string.as_str(), chain.as_ref());
787            assert_eq!(serde_json::to_string(&chain).unwrap(), format!("\"{chain_string}\""));
788
789            assert_eq!(chain_string.parse::<Chain>().unwrap(), chain);
790        }
791    }
792
793    #[test]
794    fn roundtrip_serde() {
795        for chain in Chain::iter() {
796            let chain_string = serde_json::to_string(&chain).unwrap();
797            let chain_string = chain_string.replace('-', "_");
798            assert_eq!(serde_json::from_str::<'_, Chain>(&chain_string).unwrap(), chain);
799        }
800    }
801
802    #[test]
803    fn aliases() {
804        use Chain::*;
805
806        // kebab-case
807        const ALIASES: &[(Chain, &[&str])] = &[
808            (Mainnet, &["ethlive"]),
809            (BinanceSmartChain, &["bsc", "binance-smart-chain"]),
810            (BinanceSmartChainTestnet, &["bsc-testnet", "binance-smart-chain-testnet"]),
811            (Gnosis, &["gnosis", "gnosis-chain"]),
812            (PolygonMumbai, &["mumbai"]),
813            (PolygonZkEvm, &["zkevm", "polygon-zkevm"]),
814            (PolygonZkEvmTestnet, &["zkevm-testnet", "polygon-zkevm-testnet"]),
815            (AnvilHardhat, &["anvil", "hardhat"]),
816            (AvalancheFuji, &["fuji"]),
817            (ZkSync, &["zksync"]),
818            (Mantle, &["mantle"]),
819            (MantleTestnet, &["mantle-testnet"]),
820            (Viction, &["viction"]),
821            (Base, &["base"]),
822            (BaseGoerli, &["base-goerli"]),
823            (BaseSepolia, &["base-sepolia"]),
824        ];
825
826        for &(chain, aliases) in ALIASES {
827            for &alias in aliases {
828                assert_eq!(alias.parse::<Chain>().unwrap(), chain);
829                let s = alias.to_string().replace('-', "_");
830                assert_eq!(serde_json::from_str::<Chain>(&format!("\"{s}\"")).unwrap(), chain);
831            }
832        }
833    }
834
835    #[test]
836    fn serde_to_string_match() {
837        for chain in Chain::iter() {
838            let chain_serde = serde_json::to_string(&chain).unwrap();
839            let chain_string = format!("\"{chain}\"");
840            assert_eq!(chain_serde, chain_string);
841        }
842    }
843}