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 sepolia chain.
275    #[inline]
276    pub const fn sepolia() -> Self {
277        Self::from_named(NamedChain::Sepolia)
278    }
279
280    /// Returns the optimism mainnet chain.
281    #[inline]
282    pub const fn optimism_mainnet() -> Self {
283        Self::from_named(NamedChain::Optimism)
284    }
285
286    /// Returns the optimism goerli chain.
287    #[inline]
288    pub const fn optimism_goerli() -> Self {
289        Self::from_named(NamedChain::OptimismGoerli)
290    }
291
292    /// Returns the optimism sepolia chain.
293    #[inline]
294    pub const fn optimism_sepolia() -> Self {
295        Self::from_named(NamedChain::OptimismSepolia)
296    }
297
298    /// Returns the base mainnet chain.
299    #[inline]
300    pub const fn base_mainnet() -> Self {
301        Self::from_named(NamedChain::Base)
302    }
303
304    /// Returns the base goerli chain.
305    #[inline]
306    pub const fn base_goerli() -> Self {
307        Self::from_named(NamedChain::BaseGoerli)
308    }
309
310    /// Returns the base sepolia chain.
311    #[inline]
312    pub const fn base_sepolia() -> Self {
313        Self::from_named(NamedChain::BaseSepolia)
314    }
315
316    /// Returns the arbitrum mainnet chain.
317    #[inline]
318    pub const fn arbitrum_mainnet() -> Self {
319        Self::from_named(NamedChain::Arbitrum)
320    }
321
322    /// Returns the arbitrum nova chain.
323    #[inline]
324    pub const fn arbitrum_nova() -> Self {
325        Self::from_named(NamedChain::ArbitrumNova)
326    }
327
328    /// Returns the arbitrum goerli chain.
329    #[inline]
330    pub const fn arbitrum_goerli() -> Self {
331        Self::from_named(NamedChain::ArbitrumGoerli)
332    }
333
334    /// Returns the arbitrum sepolia chain.
335    #[inline]
336    pub const fn arbitrum_sepolia() -> Self {
337        Self::from_named(NamedChain::ArbitrumSepolia)
338    }
339
340    /// Returns the arbitrum testnet chain.
341    #[inline]
342    pub const fn arbitrum_testnet() -> Self {
343        Self::from_named(NamedChain::ArbitrumTestnet)
344    }
345
346    /// Returns the syndr l3 mainnet chain.
347    #[inline]
348    pub const fn syndr() -> Self {
349        Self::from_named(NamedChain::Syndr)
350    }
351
352    /// Returns the syndr sepolia chain.
353    #[inline]
354    pub const fn syndr_sepolia() -> Self {
355        Self::from_named(NamedChain::SyndrSepolia)
356    }
357
358    /// Returns the fraxtal mainnet chain.
359    #[inline]
360    pub const fn fraxtal() -> Self {
361        Self::from_named(NamedChain::Fraxtal)
362    }
363
364    /// Returns the fraxtal testnet chain.
365    #[inline]
366    pub const fn fraxtal_testnet() -> Self {
367        Self::from_named(NamedChain::FraxtalTestnet)
368    }
369
370    /// Returns the blast chain.
371    #[inline]
372    pub const fn blast() -> Self {
373        Self::from_named(NamedChain::Blast)
374    }
375
376    /// Returns the blast sepolia chain.
377    #[inline]
378    pub const fn blast_sepolia() -> Self {
379        Self::from_named(NamedChain::BlastSepolia)
380    }
381
382    /// Returns the linea mainnet chain.
383    #[inline]
384    pub const fn linea() -> Self {
385        Self::from_named(NamedChain::Linea)
386    }
387
388    /// Returns the linea goerli chain.
389    #[inline]
390    pub const fn linea_goerli() -> Self {
391        Self::from_named(NamedChain::LineaGoerli)
392    }
393
394    /// Returns the linea sepolia chain.
395    #[inline]
396    pub const fn linea_sepolia() -> Self {
397        Self::from_named(NamedChain::LineaSepolia)
398    }
399
400    /// Returns the mode mainnet chain.
401    #[inline]
402    pub const fn mode() -> Self {
403        Self::from_named(NamedChain::Mode)
404    }
405
406    /// Returns the mode sepolia chain.
407    #[inline]
408    pub const fn mode_sepolia() -> Self {
409        Self::from_named(NamedChain::ModeSepolia)
410    }
411
412    /// Returns the elastos mainnet chain.
413    #[inline]
414    pub const fn elastos() -> Self {
415        Self::from_named(NamedChain::Elastos)
416    }
417
418    /// Returns the degen l3 mainnet chain.
419    #[inline]
420    pub const fn degen() -> Self {
421        Self::from_named(NamedChain::Degen)
422    }
423
424    /// Returns the dev chain.
425    #[inline]
426    pub const fn dev() -> Self {
427        Self::from_named(NamedChain::Dev)
428    }
429
430    /// Returns the bsc mainnet chain.
431    #[inline]
432    pub const fn bsc_mainnet() -> Self {
433        Self::from_named(NamedChain::BinanceSmartChain)
434    }
435
436    /// Returns the bsc testnet chain.
437    #[inline]
438    pub const fn bsc_testnet() -> Self {
439        Self::from_named(NamedChain::BinanceSmartChainTestnet)
440    }
441
442    /// Returns the opbnb mainnet chain.
443    #[inline]
444    pub const fn opbnb_mainnet() -> Self {
445        Self::from_named(NamedChain::OpBNBMainnet)
446    }
447
448    /// Returns the opbnb testnet chain.
449    #[inline]
450    pub const fn opbnb_testnet() -> Self {
451        Self::from_named(NamedChain::OpBNBTestnet)
452    }
453
454    /// Returns the ronin mainnet chain.
455    #[inline]
456    pub const fn ronin() -> Self {
457        Self::from_named(NamedChain::Ronin)
458    }
459
460    /// Returns the ronin testnet chain.
461    #[inline]
462    pub const fn ronin_testnet() -> Self {
463        Self::from_named(NamedChain::RoninTestnet)
464    }
465
466    /// Returns the taiko mainnet chain.
467    #[inline]
468    pub const fn taiko() -> Self {
469        Self::from_named(NamedChain::Taiko)
470    }
471
472    /// Returns the taiko hekla chain.
473    #[inline]
474    pub const fn taiko_hekla() -> Self {
475        Self::from_named(NamedChain::TaikoHekla)
476    }
477
478    /// Returns the shimmer testnet chain.
479    #[inline]
480    pub const fn shimmer() -> Self {
481        Self::from_named(NamedChain::Shimmer)
482    }
483
484    /// Returns the flare mainnet chain.
485    #[inline]
486    pub const fn flare() -> Self {
487        Self::from_named(NamedChain::Flare)
488    }
489
490    /// Returns the flare testnet chain.
491    #[inline]
492    pub const fn flare_coston2() -> Self {
493        Self::from_named(NamedChain::FlareCoston2)
494    }
495
496    /// Returns the darwinia mainnet chain.
497    #[inline]
498    pub const fn darwinia() -> Self {
499        Self::from_named(NamedChain::Darwinia)
500    }
501
502    /// Returns the crab mainnet chain.
503    #[inline]
504    pub const fn crab() -> Self {
505        Self::from_named(NamedChain::Crab)
506    }
507
508    /// Returns the koi testnet chain.
509    #[inline]
510    pub const fn koi() -> Self {
511        Self::from_named(NamedChain::Koi)
512    }
513
514    /// Returns the Immutable zkEVM mainnet chain.
515    #[inline]
516    pub const fn immutable() -> Self {
517        Self::from_named(NamedChain::Immutable)
518    }
519
520    /// Returns the Immutable zkEVM testnet chain.
521    #[inline]
522    pub const fn immutable_testnet() -> Self {
523        Self::from_named(NamedChain::ImmutableTestnet)
524    }
525
526    /// Returns the ink sepolia chain.
527    #[inline]
528    pub const fn ink_sepolia() -> Self {
529        Self::from_named(NamedChain::InkSepolia)
530    }
531
532    /// Returns the ink mainnet chain.
533    #[inline]
534    pub const fn ink_mainnet() -> Self {
535        Self::from_named(NamedChain::Ink)
536    }
537
538    /// Returns the scroll mainnet chain
539    #[inline]
540    pub const fn scroll_mainnet() -> Self {
541        Self::from_named(NamedChain::Scroll)
542    }
543
544    /// Returns the scroll sepolia chain
545    #[inline]
546    pub const fn scroll_sepolia() -> Self {
547        Self::from_named(NamedChain::ScrollSepolia)
548    }
549
550    /// Returns the Treasure mainnet chain.
551    #[inline]
552    pub const fn treasure() -> Self {
553        Self::from_named(NamedChain::Treasure)
554    }
555
556    /// Returns the Treasure Topaz testnet chain.
557    #[inline]
558    pub const fn treasure_topaz_testnet() -> Self {
559        Self::from_named(NamedChain::TreasureTopaz)
560    }
561
562    /// Returns the Superposition testnet chain.
563    #[inline]
564    pub const fn superposition_testnet() -> Self {
565        Self::from_named(NamedChain::SuperpositionTestnet)
566    }
567
568    /// Returns the Superposition mainnet chain.
569    #[inline]
570    pub const fn superposition() -> Self {
571        Self::from_named(NamedChain::Superposition)
572    }
573
574    /// Returns the Unichain mainnet chain.
575    #[inline]
576    pub const fn unichain_mainnet() -> Self {
577        Self::from_named(NamedChain::Unichain)
578    }
579
580    /// Returns the Unichain sepolia chain.
581    #[inline]
582    pub const fn unichain_sepolia() -> Self {
583        Self::from_named(NamedChain::UnichainSepolia)
584    }
585
586    /// Returns the kind of this chain.
587    #[inline]
588    pub const fn kind(&self) -> &ChainKind {
589        &self.0
590    }
591
592    /// Returns the kind of this chain.
593    #[inline]
594    pub const fn into_kind(self) -> ChainKind {
595        self.0
596    }
597
598    /// Returns `true` if this chain is Ethereum or an Ethereum testnet.
599    #[inline]
600    pub const fn is_ethereum(&self) -> bool {
601        matches!(self.named(), Some(named) if named.is_ethereum())
602    }
603
604    /// Returns true if the chain contains Optimism configuration.
605    #[inline]
606    pub const fn is_optimism(self) -> bool {
607        matches!(self.named(), Some(named) if named.is_optimism())
608    }
609
610    /// Returns true if the chain contains Arbitrum configuration.
611    #[inline]
612    pub const fn is_arbitrum(self) -> bool {
613        matches!(self.named(), Some(named) if named.is_arbitrum())
614    }
615
616    /// Attempts to convert the chain into a named chain.
617    #[inline]
618    pub const fn named(self) -> Option<NamedChain> {
619        match *self.kind() {
620            ChainKind::Named(named) => Some(named),
621            ChainKind::Id(_) => None,
622        }
623    }
624
625    /// The ID of the chain.
626    #[inline]
627    pub const fn id(self) -> u64 {
628        match *self.kind() {
629            ChainKind::Named(named) => named as u64,
630            ChainKind::Id(id) => id,
631        }
632    }
633}
634
635/// Methods delegated to `NamedChain`. Note that [`ChainKind::Id`] won't be converted because it was
636/// already done at construction.
637impl Chain {
638    /// Returns the chain's average blocktime, if applicable.
639    ///
640    /// See [`NamedChain::average_blocktime_hint`] for more info.
641    pub const fn average_blocktime_hint(self) -> Option<Duration> {
642        match self.kind() {
643            ChainKind::Named(named) => named.average_blocktime_hint(),
644            ChainKind::Id(_) => None,
645        }
646    }
647
648    /// Returns whether the chain implements EIP-1559 (with the type 2 EIP-2718 transaction type).
649    ///
650    /// See [`NamedChain::is_legacy`] for more info.
651    pub const fn is_legacy(self) -> bool {
652        match self.kind() {
653            ChainKind::Named(named) => named.is_legacy(),
654            ChainKind::Id(_) => false,
655        }
656    }
657
658    /// Returns whether the chain supports the [Shanghai hardfork][ref].
659    ///
660    /// See [`NamedChain::supports_shanghai`] for more info.
661    ///
662    /// [ref]: https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md
663    pub const fn supports_shanghai(self) -> bool {
664        match self.kind() {
665            ChainKind::Named(named) => named.supports_shanghai(),
666            ChainKind::Id(_) => false,
667        }
668    }
669
670    #[doc(hidden)]
671    #[deprecated(since = "0.1.3", note = "use `supports_shanghai` instead")]
672    pub const fn supports_push0(self) -> bool {
673        self.supports_shanghai()
674    }
675
676    /// Returns the chain's blockchain explorer and its API (Etherscan and Etherscan-like) URLs.
677    ///
678    /// See [`NamedChain::etherscan_urls`] for more info.
679    pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> {
680        match self.kind() {
681            ChainKind::Named(named) => named.etherscan_urls(),
682            ChainKind::Id(_) => None,
683        }
684    }
685
686    /// Returns the chain's blockchain explorer's API key environment variable's default name.
687    ///
688    /// See [`NamedChain::etherscan_api_key_name`] for more info.
689    pub const fn etherscan_api_key_name(self) -> Option<&'static str> {
690        match self.kind() {
691            ChainKind::Named(named) => named.etherscan_api_key_name(),
692            ChainKind::Id(_) => None,
693        }
694    }
695
696    /// Returns the chain's blockchain explorer's API key, from the environment variable with the
697    /// name specified in [`etherscan_api_key_name`](NamedChain::etherscan_api_key_name).
698    ///
699    /// See [`NamedChain::etherscan_api_key`] for more info.
700    #[cfg(feature = "std")]
701    pub fn etherscan_api_key(self) -> Option<String> {
702        match self.kind() {
703            ChainKind::Named(named) => named.etherscan_api_key(),
704            ChainKind::Id(_) => None,
705        }
706    }
707
708    /// Returns the address of the public DNS node list for the given chain.
709    ///
710    /// See [`NamedChain::public_dns_network_protocol`] for more info.
711    pub fn public_dns_network_protocol(self) -> Option<String> {
712        match self.kind() {
713            ChainKind::Named(named) => named.public_dns_network_protocol(),
714            ChainKind::Id(_) => None,
715        }
716    }
717}
718
719#[cfg(test)]
720mod tests {
721    use super::*;
722
723    #[allow(unused_imports)]
724    use alloc::string::ToString;
725
726    #[test]
727    fn test_id() {
728        assert_eq!(Chain::from_id(1234).id(), 1234);
729    }
730
731    #[test]
732    fn test_named_id() {
733        assert_eq!(Chain::from_named(NamedChain::Goerli).id(), 5);
734    }
735
736    #[test]
737    fn test_display_named_chain() {
738        assert_eq!(Chain::from_named(NamedChain::Mainnet).to_string(), "mainnet");
739    }
740
741    #[test]
742    fn test_display_id_chain() {
743        assert_eq!(Chain::from_id(1234).to_string(), "1234");
744    }
745
746    #[test]
747    fn test_from_str_named_chain() {
748        let result = Chain::from_str("mainnet");
749        let expected = Chain::from_named(NamedChain::Mainnet);
750        assert_eq!(result.unwrap(), expected);
751    }
752
753    #[test]
754    fn test_from_str_named_chain_error() {
755        let result = Chain::from_str("chain");
756        assert!(result.is_err());
757    }
758
759    #[test]
760    fn test_from_str_id_chain() {
761        let result = Chain::from_str("1234");
762        let expected = Chain::from_id(1234);
763        assert_eq!(result.unwrap(), expected);
764    }
765
766    #[test]
767    fn test_default() {
768        let default = Chain::default();
769        let expected = Chain::from_named(NamedChain::Mainnet);
770        assert_eq!(default, expected);
771    }
772
773    #[cfg(feature = "rlp")]
774    #[test]
775    fn test_id_chain_encodable_length() {
776        use alloy_rlp::Encodable;
777
778        let chain = Chain::from_id(1234);
779        assert_eq!(chain.length(), 3);
780    }
781
782    #[cfg(feature = "serde")]
783    #[test]
784    fn test_serde() {
785        let chains = r#"["mainnet",1,80001,80002,"mumbai"]"#;
786        let re = r#"["mainnet","mainnet","mumbai","amoy","mumbai"]"#;
787        let expected = [
788            Chain::mainnet(),
789            Chain::mainnet(),
790            Chain::from_named(NamedChain::PolygonMumbai),
791            Chain::from_id(80002),
792            Chain::from_named(NamedChain::PolygonMumbai),
793        ];
794        assert_eq!(serde_json::from_str::<alloc::vec::Vec<Chain>>(chains).unwrap(), expected);
795        assert_eq!(serde_json::to_string(&expected).unwrap(), re);
796    }
797}