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