Skip to main content

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