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