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#[derive(Clone, Copy, PartialEq, Eq, Hash)]
17pub struct Chain(ChainKind);
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
21pub enum ChainKind {
22 Named(NamedChain),
24 Id(u64),
26}
27
28impl ChainKind {
29 pub const fn is_named(self) -> bool {
31 matches!(self, Self::Named(_))
32 }
33
34 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 #[inline]
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 if let Ok(chain) = NamedChain::from_str(s) {
93 Ok(Self::from_named(chain))
94 } else {
95 s.parse::<u64>().map(Self::from_id)
96 }
97 }
98}
99
100impl fmt::Display for Chain {
101 #[inline]
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 match self.kind() {
104 ChainKind::Named(chain) => chain.fmt(f),
105 ChainKind::Id(id) => id.fmt(f),
106 }
107 }
108}
109
110impl PartialEq<u64> for Chain {
111 #[inline]
112 fn eq(&self, other: &u64) -> bool {
113 self.id().eq(other)
114 }
115}
116
117impl PartialEq<Chain> for u64 {
118 #[inline]
119 fn eq(&self, other: &Chain) -> bool {
120 other.eq(self)
121 }
122}
123
124impl PartialOrd<u64> for Chain {
125 #[inline]
126 fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
127 self.id().partial_cmp(other)
128 }
129}
130
131#[cfg(feature = "serde")]
132impl serde::Serialize for Chain {
133 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
134 match self.kind() {
135 ChainKind::Named(chain) => chain.serialize(serializer),
136 ChainKind::Id(id) => id.serialize(serializer),
137 }
138 }
139}
140
141#[cfg(feature = "serde")]
142impl<'de> serde::Deserialize<'de> for Chain {
143 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
144 struct ChainVisitor;
145
146 impl serde::de::Visitor<'_> for ChainVisitor {
147 type Value = Chain;
148
149 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
150 formatter.write_str("chain name or ID")
151 }
152
153 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
154 if v.is_negative() {
155 Err(serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self))
156 } else {
157 Ok(Chain::from_id(v as u64))
158 }
159 }
160
161 fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
162 Ok(Chain::from_id(value))
163 }
164
165 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
166 value.parse().map_err(serde::de::Error::custom)
167 }
168 }
169
170 deserializer.deserialize_any(ChainVisitor)
171 }
172}
173
174#[cfg(feature = "rlp")]
175impl alloy_rlp::Encodable for Chain {
176 #[inline]
177 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
178 self.id().encode(out)
179 }
180
181 #[inline]
182 fn length(&self) -> usize {
183 self.id().length()
184 }
185}
186
187#[cfg(feature = "rlp")]
188impl alloy_rlp::Decodable for Chain {
189 #[inline]
190 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
191 u64::decode(buf).map(Self::from)
192 }
193}
194
195#[cfg(feature = "arbitrary")]
196impl<'a> arbitrary::Arbitrary<'a> for Chain {
197 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
198 if u.ratio(1, 2)? {
199 let chain = u.int_in_range(0..=(NamedChain::COUNT - 1))?;
200
201 return Ok(Self::from_named(NamedChain::iter().nth(chain).expect("in range")));
202 }
203
204 Ok(Self::from_id(u64::arbitrary(u)?))
205 }
206}
207
208#[cfg(feature = "arbitrary")]
209impl proptest::arbitrary::Arbitrary for Chain {
210 type Parameters = ();
211 type Strategy = TupleUnion<(
212 WA<Map<proptest::sample::SelectorStrategy, fn(proptest::sample::Selector) -> Chain>>,
213 WA<Map<proptest::num::u64::Any, fn(u64) -> Chain>>,
214 )>;
215
216 fn arbitrary_with((): ()) -> Self::Strategy {
217 use proptest::prelude::*;
218 prop_oneof![
219 any::<Selector>().prop_map(move |sel| Self::from_named(sel.select(NamedChain::iter()))),
220 any::<u64>().prop_map(Self::from_id),
221 ]
222 }
223}
224
225impl Chain {
226 #[allow(non_snake_case)]
227 #[doc(hidden)]
228 #[deprecated(since = "0.1.0", note = "use `Self::from_named()` instead")]
229 #[inline]
230 pub const fn Named(named: NamedChain) -> Self {
231 Self::from_named(named)
232 }
233
234 #[allow(non_snake_case)]
235 #[doc(hidden)]
236 #[deprecated(since = "0.1.0", note = "use `Self::from_id()` instead")]
237 #[inline]
238 pub const fn Id(id: u64) -> Self {
239 Self::from_id_unchecked(id)
240 }
241
242 #[inline]
244 pub const fn from_named(named: NamedChain) -> Self {
245 Self(ChainKind::Named(named))
246 }
247
248 #[inline]
250 pub fn from_id(id: u64) -> Self {
251 if let Ok(named) = NamedChain::try_from(id) {
252 Self::from_named(named)
253 } else {
254 Self::from_id_unchecked(id)
255 }
256 }
257
258 pub const fn is_named(self) -> bool {
260 self.kind().is_named()
261 }
262
263 pub const fn is_id(self) -> bool {
265 self.kind().is_id()
266 }
267
268 #[inline]
274 pub const fn from_id_unchecked(id: u64) -> Self {
275 Self(ChainKind::Id(id))
276 }
277
278 #[inline]
280 pub const fn mainnet() -> Self {
281 Self::from_named(NamedChain::Mainnet)
282 }
283
284 #[inline]
286 pub const fn goerli() -> Self {
287 Self::from_named(NamedChain::Goerli)
288 }
289
290 #[inline]
292 pub const fn holesky() -> Self {
293 Self::from_named(NamedChain::Holesky)
294 }
295
296 #[inline]
298 pub const fn hoodi() -> Self {
299 Self::from_named(NamedChain::Hoodi)
300 }
301
302 #[inline]
304 pub const fn sepolia() -> Self {
305 Self::from_named(NamedChain::Sepolia)
306 }
307
308 #[inline]
310 pub const fn optimism_mainnet() -> Self {
311 Self::from_named(NamedChain::Optimism)
312 }
313
314 #[inline]
316 pub const fn optimism_goerli() -> Self {
317 Self::from_named(NamedChain::OptimismGoerli)
318 }
319
320 #[inline]
322 pub const fn optimism_sepolia() -> Self {
323 Self::from_named(NamedChain::OptimismSepolia)
324 }
325
326 #[inline]
328 pub const fn base_mainnet() -> Self {
329 Self::from_named(NamedChain::Base)
330 }
331
332 #[inline]
334 pub const fn base_goerli() -> Self {
335 Self::from_named(NamedChain::BaseGoerli)
336 }
337
338 #[inline]
340 pub const fn base_sepolia() -> Self {
341 Self::from_named(NamedChain::BaseSepolia)
342 }
343
344 #[inline]
346 pub const fn arbitrum_mainnet() -> Self {
347 Self::from_named(NamedChain::Arbitrum)
348 }
349
350 #[inline]
352 pub const fn arbitrum_nova() -> Self {
353 Self::from_named(NamedChain::ArbitrumNova)
354 }
355
356 #[inline]
358 pub const fn arbitrum_goerli() -> Self {
359 Self::from_named(NamedChain::ArbitrumGoerli)
360 }
361
362 #[inline]
364 pub const fn arbitrum_sepolia() -> Self {
365 Self::from_named(NamedChain::ArbitrumSepolia)
366 }
367
368 #[inline]
370 pub const fn arbitrum_testnet() -> Self {
371 Self::from_named(NamedChain::ArbitrumTestnet)
372 }
373
374 #[inline]
376 pub const fn syndr() -> Self {
377 Self::from_named(NamedChain::Syndr)
378 }
379
380 #[inline]
382 pub const fn syndr_sepolia() -> Self {
383 Self::from_named(NamedChain::SyndrSepolia)
384 }
385
386 #[inline]
388 pub const fn fraxtal() -> Self {
389 Self::from_named(NamedChain::Fraxtal)
390 }
391
392 #[inline]
394 pub const fn fraxtal_testnet() -> Self {
395 Self::from_named(NamedChain::FraxtalTestnet)
396 }
397
398 #[inline]
400 pub const fn blast() -> Self {
401 Self::from_named(NamedChain::Blast)
402 }
403
404 #[inline]
406 pub const fn blast_sepolia() -> Self {
407 Self::from_named(NamedChain::BlastSepolia)
408 }
409
410 #[inline]
412 pub const fn linea() -> Self {
413 Self::from_named(NamedChain::Linea)
414 }
415
416 #[inline]
418 pub const fn linea_goerli() -> Self {
419 Self::from_named(NamedChain::LineaGoerli)
420 }
421
422 #[inline]
424 pub const fn linea_sepolia() -> Self {
425 Self::from_named(NamedChain::LineaSepolia)
426 }
427
428 #[inline]
430 pub const fn mode() -> Self {
431 Self::from_named(NamedChain::Mode)
432 }
433
434 #[inline]
436 pub const fn mode_sepolia() -> Self {
437 Self::from_named(NamedChain::ModeSepolia)
438 }
439
440 #[inline]
442 pub const fn elastos() -> Self {
443 Self::from_named(NamedChain::Elastos)
444 }
445
446 #[inline]
448 pub const fn degen() -> Self {
449 Self::from_named(NamedChain::Degen)
450 }
451
452 #[inline]
454 pub const fn dev() -> Self {
455 Self::from_named(NamedChain::Dev)
456 }
457
458 #[inline]
460 pub const fn bsc_mainnet() -> Self {
461 Self::from_named(NamedChain::BinanceSmartChain)
462 }
463
464 #[inline]
466 pub const fn bsc_testnet() -> Self {
467 Self::from_named(NamedChain::BinanceSmartChainTestnet)
468 }
469
470 #[inline]
472 pub const fn opbnb_mainnet() -> Self {
473 Self::from_named(NamedChain::OpBNBMainnet)
474 }
475
476 #[inline]
478 pub const fn opbnb_testnet() -> Self {
479 Self::from_named(NamedChain::OpBNBTestnet)
480 }
481
482 #[inline]
484 pub const fn ronin() -> Self {
485 Self::from_named(NamedChain::Ronin)
486 }
487
488 #[inline]
490 pub const fn ronin_testnet() -> Self {
491 Self::from_named(NamedChain::RoninTestnet)
492 }
493
494 #[inline]
496 pub const fn taiko() -> Self {
497 Self::from_named(NamedChain::Taiko)
498 }
499
500 #[inline]
502 pub const fn taiko_hekla() -> Self {
503 Self::from_named(NamedChain::TaikoHekla)
504 }
505
506 #[inline]
508 pub const fn shimmer() -> Self {
509 Self::from_named(NamedChain::Shimmer)
510 }
511
512 #[inline]
514 pub const fn flare() -> Self {
515 Self::from_named(NamedChain::Flare)
516 }
517
518 #[inline]
520 pub const fn flare_coston2() -> Self {
521 Self::from_named(NamedChain::FlareCoston2)
522 }
523
524 #[inline]
526 pub const fn darwinia() -> Self {
527 Self::from_named(NamedChain::Darwinia)
528 }
529
530 #[inline]
532 pub const fn crab() -> Self {
533 Self::from_named(NamedChain::Crab)
534 }
535
536 #[inline]
538 pub const fn koi() -> Self {
539 Self::from_named(NamedChain::Koi)
540 }
541
542 #[inline]
544 pub const fn immutable() -> Self {
545 Self::from_named(NamedChain::Immutable)
546 }
547
548 #[inline]
550 pub const fn immutable_testnet() -> Self {
551 Self::from_named(NamedChain::ImmutableTestnet)
552 }
553
554 #[inline]
556 pub const fn ink_sepolia() -> Self {
557 Self::from_named(NamedChain::InkSepolia)
558 }
559
560 #[inline]
562 pub const fn ink_mainnet() -> Self {
563 Self::from_named(NamedChain::Ink)
564 }
565
566 #[inline]
568 pub const fn scroll_mainnet() -> Self {
569 Self::from_named(NamedChain::Scroll)
570 }
571
572 #[inline]
574 pub const fn scroll_sepolia() -> Self {
575 Self::from_named(NamedChain::ScrollSepolia)
576 }
577
578 #[inline]
580 pub const fn treasure() -> Self {
581 Self::from_named(NamedChain::Treasure)
582 }
583
584 #[inline]
586 pub const fn treasure_topaz_testnet() -> Self {
587 Self::from_named(NamedChain::TreasureTopaz)
588 }
589
590 #[inline]
592 pub const fn berachain() -> Self {
593 Self::from_named(NamedChain::Berachain)
594 }
595
596 #[inline]
598 pub const fn berachain_bepolia() -> Self {
599 Self::from_named(NamedChain::BerachainBepolia)
600 }
601
602 #[inline]
604 pub const fn sonic() -> Self {
605 Self::from_named(NamedChain::Sonic)
606 }
607
608 #[inline]
610 pub const fn sonic_blaze() -> Self {
611 Self::from_named(NamedChain::SonicBlaze)
612 }
613
614 #[inline]
616 pub const fn superposition_testnet() -> Self {
617 Self::from_named(NamedChain::SuperpositionTestnet)
618 }
619
620 #[inline]
622 pub const fn superposition() -> Self {
623 Self::from_named(NamedChain::Superposition)
624 }
625
626 #[inline]
628 pub const fn unichain_mainnet() -> Self {
629 Self::from_named(NamedChain::Unichain)
630 }
631
632 #[inline]
634 pub const fn unichain_sepolia() -> Self {
635 Self::from_named(NamedChain::UnichainSepolia)
636 }
637
638 #[inline]
640 pub const fn zksync() -> Self {
641 Self::from_named(NamedChain::ZkSync)
642 }
643
644 #[inline]
646 pub const fn zksync_testnet() -> Self {
647 Self::from_named(NamedChain::ZkSyncTestnet)
648 }
649
650 #[inline]
652 pub const fn abs() -> Self {
653 Self::from_named(NamedChain::Abstract)
654 }
655
656 #[inline]
658 pub const fn abstract_testnet() -> Self {
659 Self::from_named(NamedChain::AbstractTestnet)
660 }
661
662 #[inline]
664 pub const fn sophon() -> Self {
665 Self::from_named(NamedChain::Sophon)
666 }
667
668 #[inline]
670 pub const fn sophon_testnet() -> Self {
671 Self::from_named(NamedChain::SophonTestnet)
672 }
673
674 #[inline]
676 pub const fn lens() -> Self {
677 Self::from_named(NamedChain::Lens)
678 }
679
680 #[inline]
682 pub const fn lens_testnet() -> Self {
683 Self::from_named(NamedChain::LensTestnet)
684 }
685
686 #[inline]
688 pub const fn kind(&self) -> &ChainKind {
689 &self.0
690 }
691
692 #[inline]
694 pub const fn into_kind(self) -> ChainKind {
695 self.0
696 }
697
698 #[inline]
700 pub const fn is_ethereum(&self) -> bool {
701 matches!(self.named(), Some(named) if named.is_ethereum())
702 }
703
704 #[inline]
706 pub const fn is_optimism(self) -> bool {
707 matches!(self.named(), Some(named) if named.is_optimism())
708 }
709
710 #[inline]
712 pub const fn is_arbitrum(self) -> bool {
713 matches!(self.named(), Some(named) if named.is_arbitrum())
714 }
715
716 #[inline]
718 pub const fn is_elastic(self) -> bool {
719 matches!(self.named(), Some(named) if named.is_elastic())
720 }
721
722 #[inline]
724 pub const fn named(self) -> Option<NamedChain> {
725 match *self.kind() {
726 ChainKind::Named(named) => Some(named),
727 ChainKind::Id(_) => None,
728 }
729 }
730
731 #[inline]
733 pub const fn id(self) -> u64 {
734 match *self.kind() {
735 ChainKind::Named(named) => named as u64,
736 ChainKind::Id(id) => id,
737 }
738 }
739}
740
741impl Chain {
744 pub const fn average_blocktime_hint(self) -> Option<Duration> {
748 match self.kind() {
749 ChainKind::Named(named) => named.average_blocktime_hint(),
750 ChainKind::Id(_) => None,
751 }
752 }
753
754 pub const fn is_legacy(self) -> bool {
758 match self.kind() {
759 ChainKind::Named(named) => named.is_legacy(),
760 ChainKind::Id(_) => false,
761 }
762 }
763
764 pub const fn supports_shanghai(self) -> bool {
770 match self.kind() {
771 ChainKind::Named(named) => named.supports_shanghai(),
772 ChainKind::Id(_) => false,
773 }
774 }
775
776 #[doc(hidden)]
777 #[deprecated(since = "0.1.3", note = "use `supports_shanghai` instead")]
778 pub const fn supports_push0(self) -> bool {
779 self.supports_shanghai()
780 }
781
782 pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> {
786 match self.kind() {
787 ChainKind::Named(named) => named.etherscan_urls(),
788 ChainKind::Id(_) => None,
789 }
790 }
791
792 pub const fn etherscan_api_key_name(self) -> Option<&'static str> {
796 match self.kind() {
797 ChainKind::Named(named) => named.etherscan_api_key_name(),
798 ChainKind::Id(_) => None,
799 }
800 }
801
802 #[cfg(feature = "std")]
807 pub fn etherscan_api_key(self) -> Option<String> {
808 match self.kind() {
809 ChainKind::Named(named) => named.etherscan_api_key(),
810 ChainKind::Id(_) => None,
811 }
812 }
813
814 pub fn public_dns_network_protocol(self) -> Option<String> {
818 match self.kind() {
819 ChainKind::Named(named) => named.public_dns_network_protocol(),
820 ChainKind::Id(_) => None,
821 }
822 }
823}
824
825#[cfg(test)]
826mod tests {
827 use super::*;
828
829 #[allow(unused_imports)]
830 use alloc::string::ToString;
831
832 #[test]
833 fn test_id() {
834 assert_eq!(Chain::from_id(1234).id(), 1234);
835 }
836
837 #[test]
838 fn test_named_id() {
839 assert_eq!(Chain::from_named(NamedChain::Goerli).id(), 5);
840 }
841
842 #[test]
843 fn test_display_named_chain() {
844 assert_eq!(Chain::from_named(NamedChain::Mainnet).to_string(), "mainnet");
845 }
846
847 #[test]
848 fn test_display_id_chain() {
849 assert_eq!(Chain::from_id(1234).to_string(), "1234");
850 }
851
852 #[test]
853 fn test_from_str_named_chain() {
854 let result = Chain::from_str("mainnet");
855 let expected = Chain::from_named(NamedChain::Mainnet);
856 assert_eq!(result.unwrap(), expected);
857 }
858
859 #[test]
860 fn test_from_str_named_chain_error() {
861 let result = Chain::from_str("chain");
862 assert!(result.is_err());
863 }
864
865 #[test]
866 fn test_from_str_id_chain() {
867 let result = Chain::from_str("1234");
868 let expected = Chain::from_id(1234);
869 assert_eq!(result.unwrap(), expected);
870 }
871
872 #[test]
873 fn test_default() {
874 let default = Chain::default();
875 let expected = Chain::from_named(NamedChain::Mainnet);
876 assert_eq!(default, expected);
877 }
878
879 #[cfg(feature = "rlp")]
880 #[test]
881 fn test_id_chain_encodable_length() {
882 use alloy_rlp::Encodable;
883
884 let chain = Chain::from_id(1234);
885 assert_eq!(chain.length(), 3);
886 }
887
888 #[cfg(feature = "serde")]
889 #[test]
890 fn test_serde() {
891 let chains = r#"["mainnet",1,80001,80002,"mumbai"]"#;
892 let re = r#"["mainnet","mainnet","mumbai","amoy","mumbai"]"#;
893 let expected = [
894 Chain::mainnet(),
895 Chain::mainnet(),
896 Chain::from_named(NamedChain::PolygonMumbai),
897 Chain::from_id(80002),
898 Chain::from_named(NamedChain::PolygonMumbai),
899 ];
900 assert_eq!(serde_json::from_str::<alloc::vec::Vec<Chain>>(chains).unwrap(), expected);
901 assert_eq!(serde_json::to_string(&expected).unwrap(), re);
902 }
903}