alloy_chains/
chain.rs

1use crate::NamedChain;
2use core::{cmp::Ordering, fmt, str::FromStr, time::Duration};
3
4#[allow(unused_imports)]
5use alloc::string::String;
6
7#[cfg(feature = "arbitrary")]
8use proptest::{
9    sample::Selector,
10    strategy::{Map, TupleUnion, WA},
11};
12#[cfg(feature = "arbitrary")]
13use strum::{EnumCount, IntoEnumIterator};
14
15/// Either a known [`NamedChain`] or a EIP-155 chain ID.
16#[derive(Clone, Copy, PartialEq, Eq, Hash)]
17pub struct Chain(ChainKind);
18
19/// The kind of chain. Returned by [`Chain::kind`]. Prefer using [`Chain`] instead.
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub enum ChainKind {
22    /// Known chain.
23    Named(NamedChain),
24    /// EIP-155 chain ID.
25    Id(u64),
26}
27
28impl ChainKind {
29    /// Returns true if this a named variant.
30    pub const fn is_named(self) -> bool {
31        matches!(self, Self::Named(_))
32    }
33
34    /// Returns true if this an Id variant.
35    pub const fn is_id(self) -> bool {
36        matches!(self, Self::Id(_))
37    }
38}
39
40impl fmt::Debug for Chain {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str("Chain::")?;
43        self.kind().fmt(f)
44    }
45}
46
47impl Default for Chain {
48    #[inline]
49    fn default() -> Self {
50        Self::from_named(NamedChain::default())
51    }
52}
53
54impl From<NamedChain> for Chain {
55    #[inline]
56    fn from(id: NamedChain) -> Self {
57        Self::from_named(id)
58    }
59}
60
61impl From<u64> for Chain {
62    #[inline]
63    fn from(id: u64) -> Self {
64        Self::from_id(id)
65    }
66}
67
68impl From<Chain> for u64 {
69    #[inline]
70    fn from(chain: Chain) -> Self {
71        chain.id()
72    }
73}
74
75impl TryFrom<Chain> for NamedChain {
76    type Error = <NamedChain as TryFrom<u64>>::Error;
77
78    #[inline]
79    fn try_from(chain: Chain) -> Result<Self, Self::Error> {
80        match *chain.kind() {
81            ChainKind::Named(chain) => Ok(chain),
82            ChainKind::Id(id) => id.try_into(),
83        }
84    }
85}
86
87impl FromStr for Chain {
88    type Err = core::num::ParseIntError;
89
90    #[inline]
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        if let Ok(chain) = NamedChain::from_str(s) {
93            Ok(Self::from_named(chain))
94        } else {
95            s.parse::<u64>().map(Self::from_id)
96        }
97    }
98}
99
100impl fmt::Display for Chain {
101    #[inline]
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        match self.kind() {
104            ChainKind::Named(chain) => chain.fmt(f),
105            ChainKind::Id(id) => id.fmt(f),
106        }
107    }
108}
109
110impl PartialEq<u64> for Chain {
111    #[inline]
112    fn eq(&self, other: &u64) -> bool {
113        self.id().eq(other)
114    }
115}
116
117impl PartialEq<Chain> for u64 {
118    #[inline]
119    fn eq(&self, other: &Chain) -> bool {
120        other.eq(self)
121    }
122}
123
124impl PartialOrd<u64> for Chain {
125    #[inline]
126    fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
127        self.id().partial_cmp(other)
128    }
129}
130
131#[cfg(feature = "serde")]
132impl serde::Serialize for Chain {
133    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
134        match self.kind() {
135            ChainKind::Named(chain) => chain.serialize(serializer),
136            ChainKind::Id(id) => id.serialize(serializer),
137        }
138    }
139}
140
141#[cfg(feature = "serde")]
142impl<'de> serde::Deserialize<'de> for Chain {
143    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
144        struct ChainVisitor;
145
146        impl serde::de::Visitor<'_> for ChainVisitor {
147            type Value = Chain;
148
149            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
150                formatter.write_str("chain name or ID")
151            }
152
153            fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
154                if v.is_negative() {
155                    Err(serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self))
156                } else {
157                    Ok(Chain::from_id(v as u64))
158                }
159            }
160
161            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
162                Ok(Chain::from_id(value))
163            }
164
165            fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
166                value.parse().map_err(serde::de::Error::custom)
167            }
168        }
169
170        deserializer.deserialize_any(ChainVisitor)
171    }
172}
173
174#[cfg(feature = "rlp")]
175impl alloy_rlp::Encodable for Chain {
176    #[inline]
177    fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
178        self.id().encode(out)
179    }
180
181    #[inline]
182    fn length(&self) -> usize {
183        self.id().length()
184    }
185}
186
187#[cfg(feature = "rlp")]
188impl alloy_rlp::Decodable for Chain {
189    #[inline]
190    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
191        u64::decode(buf).map(Self::from)
192    }
193}
194
195#[cfg(feature = "arbitrary")]
196impl<'a> arbitrary::Arbitrary<'a> for Chain {
197    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
198        if u.ratio(1, 2)? {
199            let chain = u.int_in_range(0..=(NamedChain::COUNT - 1))?;
200
201            return Ok(Self::from_named(NamedChain::iter().nth(chain).expect("in range")));
202        }
203
204        Ok(Self::from_id(u64::arbitrary(u)?))
205    }
206}
207
208#[cfg(feature = "arbitrary")]
209impl proptest::arbitrary::Arbitrary for Chain {
210    type Parameters = ();
211    type Strategy = TupleUnion<(
212        WA<Map<proptest::sample::SelectorStrategy, fn(proptest::sample::Selector) -> Chain>>,
213        WA<Map<proptest::num::u64::Any, fn(u64) -> Chain>>,
214    )>;
215
216    fn arbitrary_with((): ()) -> Self::Strategy {
217        use proptest::prelude::*;
218        prop_oneof![
219            any::<Selector>().prop_map(move |sel| Self::from_named(sel.select(NamedChain::iter()))),
220            any::<u64>().prop_map(Self::from_id),
221        ]
222    }
223}
224
225impl Chain {
226    #[allow(non_snake_case)]
227    #[doc(hidden)]
228    #[deprecated(since = "0.1.0", note = "use `Self::from_named()` instead")]
229    #[inline]
230    pub const fn Named(named: NamedChain) -> Self {
231        Self::from_named(named)
232    }
233
234    #[allow(non_snake_case)]
235    #[doc(hidden)]
236    #[deprecated(since = "0.1.0", note = "use `Self::from_id()` instead")]
237    #[inline]
238    pub const fn Id(id: u64) -> Self {
239        Self::from_id_unchecked(id)
240    }
241
242    /// Creates a new [`Chain`] by wrapping a [`NamedChain`].
243    #[inline]
244    pub const fn from_named(named: NamedChain) -> Self {
245        Self(ChainKind::Named(named))
246    }
247
248    /// Creates a new [`Chain`] by wrapping a [`NamedChain`].
249    #[inline]
250    pub fn from_id(id: u64) -> Self {
251        if let Ok(named) = NamedChain::try_from(id) {
252            Self::from_named(named)
253        } else {
254            Self::from_id_unchecked(id)
255        }
256    }
257
258    /// Returns true if this a named variant.
259    pub const fn is_named(self) -> bool {
260        self.kind().is_named()
261    }
262
263    /// Returns true if this an Id variant.
264    pub const fn is_id(self) -> bool {
265        self.kind().is_id()
266    }
267
268    /// Creates a new [`Chain`] from the given ID, without checking if an associated [`NamedChain`]
269    /// exists.
270    ///
271    /// This is discouraged, as other methods assume that the chain ID is not known, but it is not
272    /// unsafe.
273    #[inline]
274    pub const fn from_id_unchecked(id: u64) -> Self {
275        Self(ChainKind::Id(id))
276    }
277
278    /// Returns the mainnet chain.
279    #[inline]
280    pub const fn mainnet() -> Self {
281        Self::from_named(NamedChain::Mainnet)
282    }
283
284    /// Returns the goerli chain.
285    #[inline]
286    pub const fn goerli() -> Self {
287        Self::from_named(NamedChain::Goerli)
288    }
289
290    /// Returns the holesky chain.
291    #[inline]
292    pub const fn holesky() -> Self {
293        Self::from_named(NamedChain::Holesky)
294    }
295
296    /// Returns the hoodi chain.
297    #[inline]
298    pub const fn hoodi() -> Self {
299        Self::from_named(NamedChain::Hoodi)
300    }
301
302    /// Returns the sepolia chain.
303    #[inline]
304    pub const fn sepolia() -> Self {
305        Self::from_named(NamedChain::Sepolia)
306    }
307
308    /// Returns the optimism mainnet chain.
309    #[inline]
310    pub const fn optimism_mainnet() -> Self {
311        Self::from_named(NamedChain::Optimism)
312    }
313
314    /// Returns the optimism goerli chain.
315    #[inline]
316    pub const fn optimism_goerli() -> Self {
317        Self::from_named(NamedChain::OptimismGoerli)
318    }
319
320    /// Returns the optimism sepolia chain.
321    #[inline]
322    pub const fn optimism_sepolia() -> Self {
323        Self::from_named(NamedChain::OptimismSepolia)
324    }
325
326    /// Returns the base mainnet chain.
327    #[inline]
328    pub const fn base_mainnet() -> Self {
329        Self::from_named(NamedChain::Base)
330    }
331
332    /// Returns the base goerli chain.
333    #[inline]
334    pub const fn base_goerli() -> Self {
335        Self::from_named(NamedChain::BaseGoerli)
336    }
337
338    /// Returns the base sepolia chain.
339    #[inline]
340    pub const fn base_sepolia() -> Self {
341        Self::from_named(NamedChain::BaseSepolia)
342    }
343
344    /// Returns the arbitrum mainnet chain.
345    #[inline]
346    pub const fn arbitrum_mainnet() -> Self {
347        Self::from_named(NamedChain::Arbitrum)
348    }
349
350    /// Returns the arbitrum nova chain.
351    #[inline]
352    pub const fn arbitrum_nova() -> Self {
353        Self::from_named(NamedChain::ArbitrumNova)
354    }
355
356    /// Returns the arbitrum goerli chain.
357    #[inline]
358    pub const fn arbitrum_goerli() -> Self {
359        Self::from_named(NamedChain::ArbitrumGoerli)
360    }
361
362    /// Returns the arbitrum sepolia chain.
363    #[inline]
364    pub const fn arbitrum_sepolia() -> Self {
365        Self::from_named(NamedChain::ArbitrumSepolia)
366    }
367
368    /// Returns the arbitrum testnet chain.
369    #[inline]
370    pub const fn arbitrum_testnet() -> Self {
371        Self::from_named(NamedChain::ArbitrumTestnet)
372    }
373
374    /// Returns the syndr l3 mainnet chain.
375    #[inline]
376    pub const fn syndr() -> Self {
377        Self::from_named(NamedChain::Syndr)
378    }
379
380    /// Returns the syndr sepolia chain.
381    #[inline]
382    pub const fn syndr_sepolia() -> Self {
383        Self::from_named(NamedChain::SyndrSepolia)
384    }
385
386    /// Returns the fraxtal mainnet chain.
387    #[inline]
388    pub const fn fraxtal() -> Self {
389        Self::from_named(NamedChain::Fraxtal)
390    }
391
392    /// Returns the fraxtal testnet chain.
393    #[inline]
394    pub const fn fraxtal_testnet() -> Self {
395        Self::from_named(NamedChain::FraxtalTestnet)
396    }
397
398    /// Returns the blast chain.
399    #[inline]
400    pub const fn blast() -> Self {
401        Self::from_named(NamedChain::Blast)
402    }
403
404    /// Returns the blast sepolia chain.
405    #[inline]
406    pub const fn blast_sepolia() -> Self {
407        Self::from_named(NamedChain::BlastSepolia)
408    }
409
410    /// Returns the linea mainnet chain.
411    #[inline]
412    pub const fn linea() -> Self {
413        Self::from_named(NamedChain::Linea)
414    }
415
416    /// Returns the linea goerli chain.
417    #[inline]
418    pub const fn linea_goerli() -> Self {
419        Self::from_named(NamedChain::LineaGoerli)
420    }
421
422    /// Returns the linea sepolia chain.
423    #[inline]
424    pub const fn linea_sepolia() -> Self {
425        Self::from_named(NamedChain::LineaSepolia)
426    }
427
428    /// Returns the mode mainnet chain.
429    #[inline]
430    pub const fn mode() -> Self {
431        Self::from_named(NamedChain::Mode)
432    }
433
434    /// Returns the mode sepolia chain.
435    #[inline]
436    pub const fn mode_sepolia() -> Self {
437        Self::from_named(NamedChain::ModeSepolia)
438    }
439
440    /// Returns the elastos mainnet chain.
441    #[inline]
442    pub const fn elastos() -> Self {
443        Self::from_named(NamedChain::Elastos)
444    }
445
446    /// Returns the degen l3 mainnet chain.
447    #[inline]
448    pub const fn degen() -> Self {
449        Self::from_named(NamedChain::Degen)
450    }
451
452    /// Returns the dev chain.
453    #[inline]
454    pub const fn dev() -> Self {
455        Self::from_named(NamedChain::Dev)
456    }
457
458    /// Returns the bsc mainnet chain.
459    #[inline]
460    pub const fn bsc_mainnet() -> Self {
461        Self::from_named(NamedChain::BinanceSmartChain)
462    }
463
464    /// Returns the bsc testnet chain.
465    #[inline]
466    pub const fn bsc_testnet() -> Self {
467        Self::from_named(NamedChain::BinanceSmartChainTestnet)
468    }
469
470    /// Returns the opbnb mainnet chain.
471    #[inline]
472    pub const fn opbnb_mainnet() -> Self {
473        Self::from_named(NamedChain::OpBNBMainnet)
474    }
475
476    /// Returns the opbnb testnet chain.
477    #[inline]
478    pub const fn opbnb_testnet() -> Self {
479        Self::from_named(NamedChain::OpBNBTestnet)
480    }
481
482    /// Returns the ronin mainnet chain.
483    #[inline]
484    pub const fn ronin() -> Self {
485        Self::from_named(NamedChain::Ronin)
486    }
487
488    /// Returns the ronin testnet chain.
489    #[inline]
490    pub const fn ronin_testnet() -> Self {
491        Self::from_named(NamedChain::RoninTestnet)
492    }
493
494    /// Returns the taiko mainnet chain.
495    #[inline]
496    pub const fn taiko() -> Self {
497        Self::from_named(NamedChain::Taiko)
498    }
499
500    /// Returns the taiko hekla chain.
501    #[inline]
502    pub const fn taiko_hekla() -> Self {
503        Self::from_named(NamedChain::TaikoHekla)
504    }
505
506    /// Returns the shimmer testnet chain.
507    #[inline]
508    pub const fn shimmer() -> Self {
509        Self::from_named(NamedChain::Shimmer)
510    }
511
512    /// Returns the flare mainnet chain.
513    #[inline]
514    pub const fn flare() -> Self {
515        Self::from_named(NamedChain::Flare)
516    }
517
518    /// Returns the flare testnet chain.
519    #[inline]
520    pub const fn flare_coston2() -> Self {
521        Self::from_named(NamedChain::FlareCoston2)
522    }
523
524    /// Returns the darwinia mainnet chain.
525    #[inline]
526    pub const fn darwinia() -> Self {
527        Self::from_named(NamedChain::Darwinia)
528    }
529
530    /// Returns the crab mainnet chain.
531    #[inline]
532    pub const fn crab() -> Self {
533        Self::from_named(NamedChain::Crab)
534    }
535
536    /// Returns the koi testnet chain.
537    #[inline]
538    pub const fn koi() -> Self {
539        Self::from_named(NamedChain::Koi)
540    }
541
542    /// Returns the Immutable zkEVM mainnet chain.
543    #[inline]
544    pub const fn immutable() -> Self {
545        Self::from_named(NamedChain::Immutable)
546    }
547
548    /// Returns the Immutable zkEVM testnet chain.
549    #[inline]
550    pub const fn immutable_testnet() -> Self {
551        Self::from_named(NamedChain::ImmutableTestnet)
552    }
553
554    /// Returns the ink sepolia chain.
555    #[inline]
556    pub const fn ink_sepolia() -> Self {
557        Self::from_named(NamedChain::InkSepolia)
558    }
559
560    /// Returns the ink mainnet chain.
561    #[inline]
562    pub const fn ink_mainnet() -> Self {
563        Self::from_named(NamedChain::Ink)
564    }
565
566    /// Returns the scroll mainnet chain
567    #[inline]
568    pub const fn scroll_mainnet() -> Self {
569        Self::from_named(NamedChain::Scroll)
570    }
571
572    /// Returns the scroll sepolia chain
573    #[inline]
574    pub const fn scroll_sepolia() -> Self {
575        Self::from_named(NamedChain::ScrollSepolia)
576    }
577
578    /// Returns the Treasure mainnet chain.
579    #[inline]
580    pub const fn treasure() -> Self {
581        Self::from_named(NamedChain::Treasure)
582    }
583
584    /// Returns the Treasure Topaz testnet chain.
585    #[inline]
586    pub const fn treasure_topaz_testnet() -> Self {
587        Self::from_named(NamedChain::TreasureTopaz)
588    }
589
590    /// Returns the Berachain mainnet chain.
591    #[inline]
592    pub const fn berachain() -> Self {
593        Self::from_named(NamedChain::Berachain)
594    }
595
596    /// Returns the Berachain Bepolia testnet chain.
597    #[inline]
598    pub const fn berachain_bepolia() -> Self {
599        Self::from_named(NamedChain::BerachainBepolia)
600    }
601
602    /// Returns the Sonic mainnet chain.
603    #[inline]
604    pub const fn sonic() -> Self {
605        Self::from_named(NamedChain::Sonic)
606    }
607
608    /// Returns the Sonic Blaze testnet chain.
609    #[inline]
610    pub const fn sonic_blaze() -> Self {
611        Self::from_named(NamedChain::SonicBlaze)
612    }
613
614    /// Returns the Superposition testnet chain.
615    #[inline]
616    pub const fn superposition_testnet() -> Self {
617        Self::from_named(NamedChain::SuperpositionTestnet)
618    }
619
620    /// Returns the Superposition mainnet chain.
621    #[inline]
622    pub const fn superposition() -> Self {
623        Self::from_named(NamedChain::Superposition)
624    }
625
626    /// Returns the Unichain mainnet chain.
627    #[inline]
628    pub const fn unichain_mainnet() -> Self {
629        Self::from_named(NamedChain::Unichain)
630    }
631
632    /// Returns the Unichain sepolia chain.
633    #[inline]
634    pub const fn unichain_sepolia() -> Self {
635        Self::from_named(NamedChain::UnichainSepolia)
636    }
637
638    /// Returns the ZKSync mainnet chain.
639    #[inline]
640    pub const fn zksync() -> Self {
641        Self::from_named(NamedChain::ZkSync)
642    }
643
644    /// Returns the ZKSync testnet chain.
645    #[inline]
646    pub const fn zksync_testnet() -> Self {
647        Self::from_named(NamedChain::ZkSyncTestnet)
648    }
649
650    /// Returns the Abstract mainnet chain.
651    #[inline]
652    pub const fn abs() -> Self {
653        Self::from_named(NamedChain::Abstract)
654    }
655
656    /// Returns the Abstract testnet chain.
657    #[inline]
658    pub const fn abstract_testnet() -> Self {
659        Self::from_named(NamedChain::AbstractTestnet)
660    }
661
662    /// Returns the Sophon mainnet chain.
663    #[inline]
664    pub const fn sophon() -> Self {
665        Self::from_named(NamedChain::Sophon)
666    }
667
668    /// Returns the Sophon testnet chain.
669    #[inline]
670    pub const fn sophon_testnet() -> Self {
671        Self::from_named(NamedChain::SophonTestnet)
672    }
673
674    /// Returns the Lens mainnet chain.
675    #[inline]
676    pub const fn lens() -> Self {
677        Self::from_named(NamedChain::Lens)
678    }
679
680    /// Returns the Lens testnet chain.
681    #[inline]
682    pub const fn lens_testnet() -> Self {
683        Self::from_named(NamedChain::LensTestnet)
684    }
685
686    /// Returns the kind of this chain.
687    #[inline]
688    pub const fn kind(&self) -> &ChainKind {
689        &self.0
690    }
691
692    /// Returns the kind of this chain.
693    #[inline]
694    pub const fn into_kind(self) -> ChainKind {
695        self.0
696    }
697
698    /// Returns `true` if this chain is Ethereum or an Ethereum testnet.
699    #[inline]
700    pub const fn is_ethereum(&self) -> bool {
701        matches!(self.named(), Some(named) if named.is_ethereum())
702    }
703
704    /// Returns true if the chain contains Optimism configuration.
705    #[inline]
706    pub const fn is_optimism(self) -> bool {
707        matches!(self.named(), Some(named) if named.is_optimism())
708    }
709
710    /// Returns true if the chain contains Arbitrum configuration.
711    #[inline]
712    pub const fn is_arbitrum(self) -> bool {
713        matches!(self.named(), Some(named) if named.is_arbitrum())
714    }
715
716    /// Returns true if the chain contains Elastic Network configuration.
717    #[inline]
718    pub const fn is_elastic(self) -> bool {
719        matches!(self.named(), Some(named) if named.is_elastic())
720    }
721
722    /// Attempts to convert the chain into a named chain.
723    #[inline]
724    pub const fn named(self) -> Option<NamedChain> {
725        match *self.kind() {
726            ChainKind::Named(named) => Some(named),
727            ChainKind::Id(_) => None,
728        }
729    }
730
731    /// The ID of the chain.
732    #[inline]
733    pub const fn id(self) -> u64 {
734        match *self.kind() {
735            ChainKind::Named(named) => named as u64,
736            ChainKind::Id(id) => id,
737        }
738    }
739}
740
741/// Methods delegated to `NamedChain`. Note that [`ChainKind::Id`] won't be converted because it was
742/// already done at construction.
743impl Chain {
744    /// Returns the chain's average blocktime, if applicable.
745    ///
746    /// See [`NamedChain::average_blocktime_hint`] for more info.
747    pub const fn average_blocktime_hint(self) -> Option<Duration> {
748        match self.kind() {
749            ChainKind::Named(named) => named.average_blocktime_hint(),
750            ChainKind::Id(_) => None,
751        }
752    }
753
754    /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
755    ///
756    /// See [`NamedChain::is_legacy`] for more info.
757    pub const fn is_legacy(self) -> bool {
758        match self.kind() {
759            ChainKind::Named(named) => named.is_legacy(),
760            ChainKind::Id(_) => false,
761        }
762    }
763
764    /// Returns whether the chain supports the [Shanghai hardfork][ref].
765    ///
766    /// See [`NamedChain::supports_shanghai`] for more info.
767    ///
768    /// [ref]: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md
769    pub const fn supports_shanghai(self) -> bool {
770        match self.kind() {
771            ChainKind::Named(named) => named.supports_shanghai(),
772            ChainKind::Id(_) => false,
773        }
774    }
775
776    #[doc(hidden)]
777    #[deprecated(since = "0.1.3", note = "use `supports_shanghai` instead")]
778    pub const fn supports_push0(self) -> bool {
779        self.supports_shanghai()
780    }
781
782    /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
783    ///
784    /// See [`NamedChain::etherscan_urls`] for more info.
785    pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> {
786        match self.kind() {
787            ChainKind::Named(named) => named.etherscan_urls(),
788            ChainKind::Id(_) => None,
789        }
790    }
791
792    /// Returns the chain's blockchain explorer's API key environment variable's default name.
793    ///
794    /// See [`NamedChain::etherscan_api_key_name`] for more info.
795    pub const fn etherscan_api_key_name(self) -> Option<&'static str> {
796        match self.kind() {
797            ChainKind::Named(named) => named.etherscan_api_key_name(),
798            ChainKind::Id(_) => None,
799        }
800    }
801
802    /// Returns the chain's blockchain explorer's API key, from the environment variable with the
803    /// name specified in [`etherscan_api_key_name`](NamedChain::etherscan_api_key_name).
804    ///
805    /// See [`NamedChain::etherscan_api_key`] for more info.
806    #[cfg(feature = "std")]
807    pub fn etherscan_api_key(self) -> Option<String> {
808        match self.kind() {
809            ChainKind::Named(named) => named.etherscan_api_key(),
810            ChainKind::Id(_) => None,
811        }
812    }
813
814    /// Returns the address of the public DNS node list for the given chain.
815    ///
816    /// See [`NamedChain::public_dns_network_protocol`] for more info.
817    pub fn public_dns_network_protocol(self) -> Option<String> {
818        match self.kind() {
819            ChainKind::Named(named) => named.public_dns_network_protocol(),
820            ChainKind::Id(_) => None,
821        }
822    }
823}
824
825#[cfg(test)]
826mod tests {
827    use super::*;
828
829    #[allow(unused_imports)]
830    use alloc::string::ToString;
831
832    #[test]
833    fn test_id() {
834        assert_eq!(Chain::from_id(1234).id(), 1234);
835    }
836
837    #[test]
838    fn test_named_id() {
839        assert_eq!(Chain::from_named(NamedChain::Goerli).id(), 5);
840    }
841
842    #[test]
843    fn test_display_named_chain() {
844        assert_eq!(Chain::from_named(NamedChain::Mainnet).to_string(), "mainnet");
845    }
846
847    #[test]
848    fn test_display_id_chain() {
849        assert_eq!(Chain::from_id(1234).to_string(), "1234");
850    }
851
852    #[test]
853    fn test_from_str_named_chain() {
854        let result = Chain::from_str("mainnet");
855        let expected = Chain::from_named(NamedChain::Mainnet);
856        assert_eq!(result.unwrap(), expected);
857    }
858
859    #[test]
860    fn test_from_str_named_chain_error() {
861        let result = Chain::from_str("chain");
862        assert!(result.is_err());
863    }
864
865    #[test]
866    fn test_from_str_id_chain() {
867        let result = Chain::from_str("1234");
868        let expected = Chain::from_id(1234);
869        assert_eq!(result.unwrap(), expected);
870    }
871
872    #[test]
873    fn test_default() {
874        let default = Chain::default();
875        let expected = Chain::from_named(NamedChain::Mainnet);
876        assert_eq!(default, expected);
877    }
878
879    #[cfg(feature = "rlp")]
880    #[test]
881    fn test_id_chain_encodable_length() {
882        use alloy_rlp::Encodable;
883
884        let chain = Chain::from_id(1234);
885        assert_eq!(chain.length(), 3);
886    }
887
888    #[cfg(feature = "serde")]
889    #[test]
890    fn test_serde() {
891        let chains = r#"["mainnet",1,80001,80002,"mumbai"]"#;
892        let re = r#"["mainnet","mainnet","mumbai","amoy","mumbai"]"#;
893        let expected = [
894            Chain::mainnet(),
895            Chain::mainnet(),
896            Chain::from_named(NamedChain::PolygonMumbai),
897            Chain::from_id(80002),
898            Chain::from_named(NamedChain::PolygonMumbai),
899        ];
900        assert_eq!(serde_json::from_str::<alloc::vec::Vec<Chain>>(chains).unwrap(), expected);
901        assert_eq!(serde_json::to_string(&expected).unwrap(), re);
902    }
903}