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 BNB Smart Chain (BSC) network.
747    #[inline]
748    pub const fn is_bsc(&self) -> bool {
749        matches!(self.named(), Some(named) if named.is_bsc())
750    }
751
752    /// Returns `true` if this chain is a Polygon chain.
753    #[inline]
754    pub const fn is_polygon(&self) -> bool {
755        matches!(self.named(), Some(named) if named.is_polygon())
756    }
757
758    /// Returns true if the chain contains Arbitrum configuration.
759    #[inline]
760    pub const fn is_arbitrum(self) -> bool {
761        matches!(self.named(), Some(named) if named.is_arbitrum())
762    }
763
764    /// Returns true if the chain contains Elastic Network configuration.
765    #[inline]
766    pub const fn is_elastic(self) -> bool {
767        matches!(self.named(), Some(named) if named.is_elastic())
768    }
769
770    /// Returns true if the chain contains Tempo configuration.
771    #[inline]
772    pub const fn is_tempo(self) -> bool {
773        matches!(self.named(), Some(named) if named.is_tempo())
774    }
775
776    /// Returns true if the chain uses a custom Sourcify-compatible API for contract verification.
777    ///
778    /// These chains have their verification URL registered in
779    /// [`etherscan_urls`](Self::etherscan_urls) but the API is Sourcify-compatible rather than
780    /// Etherscan-compatible.
781    #[inline]
782    pub const fn is_custom_sourcify(self) -> bool {
783        matches!(self.named(), Some(named) if named.is_custom_sourcify())
784    }
785
786    /// Attempts to convert the chain into a named chain.
787    #[inline]
788    pub const fn named(self) -> Option<NamedChain> {
789        match *self.kind() {
790            ChainKind::Named(named) => Some(named),
791            ChainKind::Id(_) => None,
792        }
793    }
794
795    /// The ID of the chain.
796    #[inline]
797    pub const fn id(self) -> u64 {
798        match *self.kind() {
799            ChainKind::Named(named) => named as u64,
800            ChainKind::Id(id) => id,
801        }
802    }
803}
804
805/// Methods delegated to `NamedChain`. Note that [`ChainKind::Id`] won't be converted because it was
806/// already done at construction.
807impl Chain {
808    /// Returns the chain's average blocktime, if applicable.
809    ///
810    /// See [`NamedChain::average_blocktime_hint`] for more info.
811    pub const fn average_blocktime_hint(self) -> Option<Duration> {
812        match self.kind() {
813            ChainKind::Named(named) => named.average_blocktime_hint(),
814            ChainKind::Id(_) => None,
815        }
816    }
817
818    /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
819    ///
820    /// See [`NamedChain::is_legacy`] for more info.
821    pub const fn is_legacy(self) -> bool {
822        match self.kind() {
823            ChainKind::Named(named) => named.is_legacy(),
824            ChainKind::Id(_) => false,
825        }
826    }
827
828    /// Returns whether the chain supports the [Shanghai hardfork][ref].
829    ///
830    /// See [`NamedChain::supports_shanghai`] for more info.
831    ///
832    /// [ref]: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md
833    pub const fn supports_shanghai(self) -> bool {
834        match self.kind() {
835            ChainKind::Named(named) => named.supports_shanghai(),
836            ChainKind::Id(_) => false,
837        }
838    }
839
840    /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
841    ///
842    /// See [`NamedChain::etherscan_urls`] for more info.
843    pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> {
844        match self.kind() {
845            ChainKind::Named(named) => named.etherscan_urls(),
846            ChainKind::Id(_) => None,
847        }
848    }
849
850    /// Returns the chain's blockchain explorer's API key environment variable's default name.
851    ///
852    /// See [`NamedChain::etherscan_api_key_name`] for more info.
853    pub const fn etherscan_api_key_name(self) -> Option<&'static str> {
854        match self.kind() {
855            ChainKind::Named(named) => named.etherscan_api_key_name(),
856            ChainKind::Id(_) => None,
857        }
858    }
859
860    /// Returns the chain's blockchain explorer's API key, from the environment variable with the
861    /// name specified in [`etherscan_api_key_name`](NamedChain::etherscan_api_key_name).
862    ///
863    /// See [`NamedChain::etherscan_api_key`] for more info.
864    #[cfg(feature = "std")]
865    pub fn etherscan_api_key(self) -> Option<String> {
866        match self.kind() {
867            ChainKind::Named(named) => named.etherscan_api_key(),
868            ChainKind::Id(_) => None,
869        }
870    }
871
872    /// Returns the address of the public DNS node list for the given chain.
873    ///
874    /// See [`NamedChain::public_dns_network_protocol`] for more info.
875    pub fn public_dns_network_protocol(self) -> Option<String> {
876        match self.kind() {
877            ChainKind::Named(named) => named.public_dns_network_protocol(),
878            ChainKind::Id(_) => None,
879        }
880    }
881}
882
883#[cfg(test)]
884mod tests {
885    use super::*;
886
887    #[allow(unused_imports)]
888    use alloc::string::ToString;
889
890    #[test]
891    fn test_id() {
892        assert_eq!(Chain::from_id(1234).id(), 1234);
893    }
894
895    #[test]
896    fn test_named_id() {
897        assert_eq!(Chain::from_named(NamedChain::Goerli).id(), 5);
898    }
899
900    #[test]
901    fn test_display_named_chain() {
902        assert_eq!(Chain::from_named(NamedChain::Mainnet).to_string(), "mainnet");
903    }
904
905    #[test]
906    fn test_display_id_chain() {
907        assert_eq!(Chain::from_id(1234).to_string(), "1234");
908    }
909
910    #[test]
911    fn test_from_str_named_chain() {
912        let result = Chain::from_str("mainnet");
913        let expected = Chain::from_named(NamedChain::Mainnet);
914        assert_eq!(result.unwrap(), expected);
915    }
916
917    #[test]
918    fn test_from_str_named_chain_error() {
919        let result = Chain::from_str("chain");
920        assert!(result.is_err());
921    }
922
923    #[test]
924    fn test_from_str_id_chain() {
925        let result = Chain::from_str("1234");
926        let expected = Chain::from_id(1234);
927        assert_eq!(result.unwrap(), expected);
928    }
929
930    #[test]
931    fn test_default() {
932        let default = Chain::default();
933        let expected = Chain::from_named(NamedChain::Mainnet);
934        assert_eq!(default, expected);
935    }
936
937    #[test]
938    fn test_is_bsc() {
939        assert!(NamedChain::BinanceSmartChain.is_bsc());
940        assert!(NamedChain::BinanceSmartChainTestnet.is_bsc());
941        assert!(Chain::bsc_mainnet().is_bsc());
942        assert!(Chain::bsc_testnet().is_bsc());
943
944        assert!(!NamedChain::Mainnet.is_bsc());
945        assert!(!Chain::mainnet().is_bsc());
946    }
947
948    #[test]
949    fn test_tempo_devnet_metadata() {
950        let named = NamedChain::TempoDevnet;
951        let chain = Chain::from_named(named);
952
953        assert_eq!(chain.id(), 31318);
954        assert_eq!(chain.average_blocktime_hint(), Some(Duration::from_millis(500)));
955        assert!(chain.supports_shanghai());
956        assert!(named.is_testnet());
957        assert!(chain.is_tempo());
958        assert!(chain.is_custom_sourcify());
959        assert_eq!(named.native_currency_symbol(), Some("USD"));
960        assert_eq!(
961            chain.etherscan_urls(),
962            Some(("https://contracts.tempo.xyz", "https://explore.devnet.tempo.xyz"))
963        );
964    }
965
966    #[cfg(feature = "rlp")]
967    #[test]
968    fn test_id_chain_encodable_length() {
969        use alloy_rlp::Encodable;
970
971        let chain = Chain::from_id(1234);
972        assert_eq!(chain.length(), 3);
973    }
974
975    #[cfg(feature = "serde")]
976    #[test]
977    fn test_serde() {
978        let chains = r#"["mainnet",1,137,80002]"#;
979        let re = r#"["mainnet","mainnet","polygon","amoy"]"#;
980        let expected = [
981            Chain::from_named(NamedChain::Mainnet),
982            Chain::mainnet(),
983            Chain::from_named(NamedChain::Polygon),
984            Chain::from_id(80002),
985        ];
986        assert_eq!(serde_json::from_str::<alloc::vec::Vec<Chain>>(chains).unwrap(), expected);
987        assert_eq!(serde_json::to_string(&expected).unwrap(), re);
988    }
989}