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 Superposition testnet chain.
569    #[inline]
570    pub const fn superposition_testnet() -> Self {
571        Self::from_named(NamedChain::SuperpositionTestnet)
572    }
573
574    /// Returns the Superposition mainnet chain.
575    #[inline]
576    pub const fn superposition() -> Self {
577        Self::from_named(NamedChain::Superposition)
578    }
579
580    /// Returns the Unichain mainnet chain.
581    #[inline]
582    pub const fn unichain_mainnet() -> Self {
583        Self::from_named(NamedChain::Unichain)
584    }
585
586    /// Returns the Unichain sepolia chain.
587    #[inline]
588    pub const fn unichain_sepolia() -> Self {
589        Self::from_named(NamedChain::UnichainSepolia)
590    }
591
592    /// Returns the kind of this chain.
593    #[inline]
594    pub const fn kind(&self) -> &ChainKind {
595        &self.0
596    }
597
598    /// Returns the kind of this chain.
599    #[inline]
600    pub const fn into_kind(self) -> ChainKind {
601        self.0
602    }
603
604    /// Returns `true` if this chain is Ethereum or an Ethereum testnet.
605    #[inline]
606    pub const fn is_ethereum(&self) -> bool {
607        matches!(self.named(), Some(named) if named.is_ethereum())
608    }
609
610    /// Returns true if the chain contains Optimism configuration.
611    #[inline]
612    pub const fn is_optimism(self) -> bool {
613        matches!(self.named(), Some(named) if named.is_optimism())
614    }
615
616    /// Returns true if the chain contains Arbitrum configuration.
617    #[inline]
618    pub const fn is_arbitrum(self) -> bool {
619        matches!(self.named(), Some(named) if named.is_arbitrum())
620    }
621
622    /// Attempts to convert the chain into a named chain.
623    #[inline]
624    pub const fn named(self) -> Option<NamedChain> {
625        match *self.kind() {
626            ChainKind::Named(named) => Some(named),
627            ChainKind::Id(_) => None,
628        }
629    }
630
631    /// The ID of the chain.
632    #[inline]
633    pub const fn id(self) -> u64 {
634        match *self.kind() {
635            ChainKind::Named(named) => named as u64,
636            ChainKind::Id(id) => id,
637        }
638    }
639}
640
641/// Methods delegated to `NamedChain`. Note that [`ChainKind::Id`] won't be converted because it was
642/// already done at construction.
643impl Chain {
644    /// Returns the chain's average blocktime, if applicable.
645    ///
646    /// See [`NamedChain::average_blocktime_hint`] for more info.
647    pub const fn average_blocktime_hint(self) -> Option<Duration> {
648        match self.kind() {
649            ChainKind::Named(named) => named.average_blocktime_hint(),
650            ChainKind::Id(_) => None,
651        }
652    }
653
654    /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
655    ///
656    /// See [`NamedChain::is_legacy`] for more info.
657    pub const fn is_legacy(self) -> bool {
658        match self.kind() {
659            ChainKind::Named(named) => named.is_legacy(),
660            ChainKind::Id(_) => false,
661        }
662    }
663
664    /// Returns whether the chain supports the [Shanghai hardfork][ref].
665    ///
666    /// See [`NamedChain::supports_shanghai`] for more info.
667    ///
668    /// [ref]: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md
669    pub const fn supports_shanghai(self) -> bool {
670        match self.kind() {
671            ChainKind::Named(named) => named.supports_shanghai(),
672            ChainKind::Id(_) => false,
673        }
674    }
675
676    #[doc(hidden)]
677    #[deprecated(since = "0.1.3", note = "use `supports_shanghai` instead")]
678    pub const fn supports_push0(self) -> bool {
679        self.supports_shanghai()
680    }
681
682    /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
683    ///
684    /// See [`NamedChain::etherscan_urls`] for more info.
685    pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> {
686        match self.kind() {
687            ChainKind::Named(named) => named.etherscan_urls(),
688            ChainKind::Id(_) => None,
689        }
690    }
691
692    /// Returns the chain's blockchain explorer's API key environment variable's default name.
693    ///
694    /// See [`NamedChain::etherscan_api_key_name`] for more info.
695    pub const fn etherscan_api_key_name(self) -> Option<&'static str> {
696        match self.kind() {
697            ChainKind::Named(named) => named.etherscan_api_key_name(),
698            ChainKind::Id(_) => None,
699        }
700    }
701
702    /// Returns the chain's blockchain explorer's API key, from the environment variable with the
703    /// name specified in [`etherscan_api_key_name`](NamedChain::etherscan_api_key_name).
704    ///
705    /// See [`NamedChain::etherscan_api_key`] for more info.
706    #[cfg(feature = "std")]
707    pub fn etherscan_api_key(self) -> Option<String> {
708        match self.kind() {
709            ChainKind::Named(named) => named.etherscan_api_key(),
710            ChainKind::Id(_) => None,
711        }
712    }
713
714    /// Returns the address of the public DNS node list for the given chain.
715    ///
716    /// See [`NamedChain::public_dns_network_protocol`] for more info.
717    pub fn public_dns_network_protocol(self) -> Option<String> {
718        match self.kind() {
719            ChainKind::Named(named) => named.public_dns_network_protocol(),
720            ChainKind::Id(_) => None,
721        }
722    }
723}
724
725#[cfg(test)]
726mod tests {
727    use super::*;
728
729    #[allow(unused_imports)]
730    use alloc::string::ToString;
731
732    #[test]
733    fn test_id() {
734        assert_eq!(Chain::from_id(1234).id(), 1234);
735    }
736
737    #[test]
738    fn test_named_id() {
739        assert_eq!(Chain::from_named(NamedChain::Goerli).id(), 5);
740    }
741
742    #[test]
743    fn test_display_named_chain() {
744        assert_eq!(Chain::from_named(NamedChain::Mainnet).to_string(), "mainnet");
745    }
746
747    #[test]
748    fn test_display_id_chain() {
749        assert_eq!(Chain::from_id(1234).to_string(), "1234");
750    }
751
752    #[test]
753    fn test_from_str_named_chain() {
754        let result = Chain::from_str("mainnet");
755        let expected = Chain::from_named(NamedChain::Mainnet);
756        assert_eq!(result.unwrap(), expected);
757    }
758
759    #[test]
760    fn test_from_str_named_chain_error() {
761        let result = Chain::from_str("chain");
762        assert!(result.is_err());
763    }
764
765    #[test]
766    fn test_from_str_id_chain() {
767        let result = Chain::from_str("1234");
768        let expected = Chain::from_id(1234);
769        assert_eq!(result.unwrap(), expected);
770    }
771
772    #[test]
773    fn test_default() {
774        let default = Chain::default();
775        let expected = Chain::from_named(NamedChain::Mainnet);
776        assert_eq!(default, expected);
777    }
778
779    #[cfg(feature = "rlp")]
780    #[test]
781    fn test_id_chain_encodable_length() {
782        use alloy_rlp::Encodable;
783
784        let chain = Chain::from_id(1234);
785        assert_eq!(chain.length(), 3);
786    }
787
788    #[cfg(feature = "serde")]
789    #[test]
790    fn test_serde() {
791        let chains = r#"["mainnet",1,80001,80002,"mumbai"]"#;
792        let re = r#"["mainnet","mainnet","mumbai","amoy","mumbai"]"#;
793        let expected = [
794            Chain::mainnet(),
795            Chain::mainnet(),
796            Chain::from_named(NamedChain::PolygonMumbai),
797            Chain::from_id(80002),
798            Chain::from_named(NamedChain::PolygonMumbai),
799        ];
800        assert_eq!(serde_json::from_str::<alloc::vec::Vec<Chain>>(chains).unwrap(), expected);
801        assert_eq!(serde_json::to_string(&expected).unwrap(), re);
802    }
803}