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