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 fn from_str(s: &str) -> Result<Self, Self::Err> {
91 if let Ok(chain) = NamedChain::from_str(s) {
92 Ok(Self::from_named(chain))
93 } else {
94 s.parse::<u64>().map(Self::from_id)
95 }
96 }
97}
98
99impl fmt::Display for Chain {
100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101 match self.kind() {
102 ChainKind::Named(chain) => chain.fmt(f),
103 ChainKind::Id(id) => id.fmt(f),
104 }
105 }
106}
107
108impl PartialEq<u64> for Chain {
109 #[inline]
110 fn eq(&self, other: &u64) -> bool {
111 self.id().eq(other)
112 }
113}
114
115impl PartialEq<Chain> for u64 {
116 #[inline]
117 fn eq(&self, other: &Chain) -> bool {
118 other.eq(self)
119 }
120}
121
122impl PartialOrd<u64> for Chain {
123 #[inline]
124 fn partial_cmp(&self, other: &u64) -> Option<Ordering> {
125 self.id().partial_cmp(other)
126 }
127}
128
129#[cfg(feature = "serde")]
130impl serde::Serialize for Chain {
131 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
132 match self.kind() {
133 ChainKind::Named(chain) => chain.serialize(serializer),
134 ChainKind::Id(id) => id.serialize(serializer),
135 }
136 }
137}
138
139#[cfg(feature = "serde")]
140impl<'de> serde::Deserialize<'de> for Chain {
141 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
142 struct ChainVisitor;
143
144 impl serde::de::Visitor<'_> for ChainVisitor {
145 type Value = Chain;
146
147 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
148 formatter.write_str("chain name or ID")
149 }
150
151 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<Self::Value, E> {
152 if v.is_negative() {
153 Err(serde::de::Error::invalid_value(serde::de::Unexpected::Signed(v), &self))
154 } else {
155 Ok(Chain::from_id(v as u64))
156 }
157 }
158
159 fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
160 Ok(Chain::from_id(value))
161 }
162
163 fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
164 value.parse().map_err(serde::de::Error::custom)
165 }
166 }
167
168 deserializer.deserialize_any(ChainVisitor)
169 }
170}
171
172#[cfg(feature = "rlp")]
173impl alloy_rlp::Encodable for Chain {
174 #[inline]
175 fn encode(&self, out: &mut dyn alloy_rlp::BufMut) {
176 self.id().encode(out)
177 }
178
179 #[inline]
180 fn length(&self) -> usize {
181 self.id().length()
182 }
183}
184
185#[cfg(feature = "rlp")]
186impl alloy_rlp::Decodable for Chain {
187 #[inline]
188 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
189 u64::decode(buf).map(Self::from)
190 }
191}
192
193#[cfg(feature = "arbitrary")]
194impl<'a> arbitrary::Arbitrary<'a> for Chain {
195 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
196 if u.ratio(1, 2)? {
197 let chain = u.int_in_range(0..=(NamedChain::COUNT - 1))?;
198
199 return Ok(Self::from_named(NamedChain::iter().nth(chain).expect("in range")));
200 }
201
202 Ok(Self::from_id(u64::arbitrary(u)?))
203 }
204}
205
206#[cfg(feature = "arbitrary")]
207impl proptest::arbitrary::Arbitrary for Chain {
208 type Parameters = ();
209 type Strategy = TupleUnion<(
210 WA<Map<proptest::sample::SelectorStrategy, fn(proptest::sample::Selector) -> Chain>>,
211 WA<Map<proptest::num::u64::Any, fn(u64) -> Chain>>,
212 )>;
213
214 fn arbitrary_with((): ()) -> Self::Strategy {
215 use proptest::prelude::*;
216 prop_oneof![
217 any::<Selector>().prop_map(move |sel| Self::from_named(sel.select(NamedChain::iter()))),
218 any::<u64>().prop_map(Self::from_id),
219 ]
220 }
221}
222
223impl Chain {
224 #[inline]
226 pub const fn from_named(named: NamedChain) -> Self {
227 Self(ChainKind::Named(named))
228 }
229
230 #[inline]
232 pub fn from_id(id: u64) -> Self {
233 if let Ok(named) = NamedChain::try_from(id) {
234 Self::from_named(named)
235 } else {
236 Self::from_id_unchecked(id)
237 }
238 }
239
240 #[inline]
242 pub const fn is_named(self) -> bool {
243 self.kind().is_named()
244 }
245
246 #[inline]
248 pub const fn is_id(self) -> bool {
249 self.kind().is_id()
250 }
251
252 #[inline]
258 pub const fn from_id_unchecked(id: u64) -> Self {
259 Self(ChainKind::Id(id))
260 }
261
262 #[inline]
264 pub const fn mainnet() -> Self {
265 Self::from_named(NamedChain::Mainnet)
266 }
267
268 #[inline]
270 pub const fn goerli() -> Self {
271 Self::from_named(NamedChain::Goerli)
272 }
273
274 #[inline]
276 pub const fn holesky() -> Self {
277 Self::from_named(NamedChain::Holesky)
278 }
279
280 #[inline]
282 pub const fn hoodi() -> Self {
283 Self::from_named(NamedChain::Hoodi)
284 }
285
286 #[inline]
288 pub const fn sepolia() -> Self {
289 Self::from_named(NamedChain::Sepolia)
290 }
291
292 #[inline]
294 pub const fn optimism_mainnet() -> Self {
295 Self::from_named(NamedChain::Optimism)
296 }
297
298 #[inline]
300 pub const fn optimism_goerli() -> Self {
301 Self::from_named(NamedChain::OptimismGoerli)
302 }
303
304 #[inline]
306 pub const fn optimism_sepolia() -> Self {
307 Self::from_named(NamedChain::OptimismSepolia)
308 }
309
310 #[inline]
312 pub const fn base_mainnet() -> Self {
313 Self::from_named(NamedChain::Base)
314 }
315
316 #[inline]
318 pub const fn base_goerli() -> Self {
319 Self::from_named(NamedChain::BaseGoerli)
320 }
321
322 #[inline]
324 pub const fn base_sepolia() -> Self {
325 Self::from_named(NamedChain::BaseSepolia)
326 }
327
328 #[inline]
330 pub const fn arbitrum_mainnet() -> Self {
331 Self::from_named(NamedChain::Arbitrum)
332 }
333
334 #[inline]
336 pub const fn arbitrum_nova() -> Self {
337 Self::from_named(NamedChain::ArbitrumNova)
338 }
339
340 #[inline]
342 pub const fn arbitrum_goerli() -> Self {
343 Self::from_named(NamedChain::ArbitrumGoerli)
344 }
345
346 #[inline]
348 pub const fn arbitrum_sepolia() -> Self {
349 Self::from_named(NamedChain::ArbitrumSepolia)
350 }
351
352 #[inline]
354 pub const fn arbitrum_testnet() -> Self {
355 Self::from_named(NamedChain::ArbitrumTestnet)
356 }
357
358 #[inline]
360 pub const fn syndr() -> Self {
361 Self::from_named(NamedChain::Syndr)
362 }
363
364 #[inline]
366 pub const fn syndr_sepolia() -> Self {
367 Self::from_named(NamedChain::SyndrSepolia)
368 }
369
370 #[inline]
372 pub const fn fraxtal() -> Self {
373 Self::from_named(NamedChain::Fraxtal)
374 }
375
376 #[inline]
378 pub const fn fraxtal_testnet() -> Self {
379 Self::from_named(NamedChain::FraxtalTestnet)
380 }
381
382 #[inline]
384 pub const fn blast() -> Self {
385 Self::from_named(NamedChain::Blast)
386 }
387
388 #[inline]
390 pub const fn blast_sepolia() -> Self {
391 Self::from_named(NamedChain::BlastSepolia)
392 }
393
394 #[inline]
396 pub const fn linea() -> Self {
397 Self::from_named(NamedChain::Linea)
398 }
399
400 #[inline]
402 pub const fn linea_goerli() -> Self {
403 Self::from_named(NamedChain::LineaGoerli)
404 }
405
406 #[inline]
408 pub const fn linea_sepolia() -> Self {
409 Self::from_named(NamedChain::LineaSepolia)
410 }
411
412 #[inline]
414 pub const fn mode() -> Self {
415 Self::from_named(NamedChain::Mode)
416 }
417
418 #[inline]
420 pub const fn mode_sepolia() -> Self {
421 Self::from_named(NamedChain::ModeSepolia)
422 }
423
424 #[inline]
426 pub const fn elastos() -> Self {
427 Self::from_named(NamedChain::Elastos)
428 }
429
430 #[inline]
432 pub const fn degen() -> Self {
433 Self::from_named(NamedChain::Degen)
434 }
435
436 #[inline]
438 pub const fn dev() -> Self {
439 Self::from_named(NamedChain::Dev)
440 }
441
442 #[inline]
444 pub const fn bsc_mainnet() -> Self {
445 Self::from_named(NamedChain::BinanceSmartChain)
446 }
447
448 #[inline]
450 pub const fn bsc_testnet() -> Self {
451 Self::from_named(NamedChain::BinanceSmartChainTestnet)
452 }
453
454 #[inline]
456 pub const fn opbnb_mainnet() -> Self {
457 Self::from_named(NamedChain::OpBNBMainnet)
458 }
459
460 #[inline]
462 pub const fn opbnb_testnet() -> Self {
463 Self::from_named(NamedChain::OpBNBTestnet)
464 }
465
466 #[inline]
468 pub const fn ronin() -> Self {
469 Self::from_named(NamedChain::Ronin)
470 }
471
472 #[inline]
474 pub const fn ronin_testnet() -> Self {
475 Self::from_named(NamedChain::RoninTestnet)
476 }
477
478 #[inline]
480 pub const fn taiko() -> Self {
481 Self::from_named(NamedChain::Taiko)
482 }
483
484 #[inline]
486 pub const fn taiko_hekla() -> Self {
487 Self::from_named(NamedChain::TaikoHekla)
488 }
489
490 #[inline]
492 pub const fn shimmer() -> Self {
493 Self::from_named(NamedChain::Shimmer)
494 }
495
496 #[inline]
498 pub const fn flare() -> Self {
499 Self::from_named(NamedChain::Flare)
500 }
501
502 #[inline]
504 pub const fn flare_coston2() -> Self {
505 Self::from_named(NamedChain::FlareCoston2)
506 }
507
508 #[inline]
510 pub const fn darwinia() -> Self {
511 Self::from_named(NamedChain::Darwinia)
512 }
513
514 #[inline]
516 pub const fn crab() -> Self {
517 Self::from_named(NamedChain::Crab)
518 }
519
520 #[inline]
522 pub const fn koi() -> Self {
523 Self::from_named(NamedChain::Koi)
524 }
525
526 #[inline]
528 pub const fn immutable() -> Self {
529 Self::from_named(NamedChain::Immutable)
530 }
531
532 #[inline]
534 pub const fn immutable_testnet() -> Self {
535 Self::from_named(NamedChain::ImmutableTestnet)
536 }
537
538 #[inline]
540 pub const fn ink_sepolia() -> Self {
541 Self::from_named(NamedChain::InkSepolia)
542 }
543
544 #[inline]
546 pub const fn ink_mainnet() -> Self {
547 Self::from_named(NamedChain::Ink)
548 }
549
550 #[inline]
552 pub const fn scroll_mainnet() -> Self {
553 Self::from_named(NamedChain::Scroll)
554 }
555
556 #[inline]
558 pub const fn scroll_sepolia() -> Self {
559 Self::from_named(NamedChain::ScrollSepolia)
560 }
561
562 #[inline]
564 pub const fn treasure() -> Self {
565 Self::from_named(NamedChain::Treasure)
566 }
567
568 #[inline]
570 pub const fn treasure_topaz_testnet() -> Self {
571 Self::from_named(NamedChain::TreasureTopaz)
572 }
573
574 #[inline]
576 pub const fn berachain() -> Self {
577 Self::from_named(NamedChain::Berachain)
578 }
579
580 #[inline]
582 pub const fn berachain_bepolia() -> Self {
583 Self::from_named(NamedChain::BerachainBepolia)
584 }
585
586 #[inline]
588 pub const fn sonic() -> Self {
589 Self::from_named(NamedChain::Sonic)
590 }
591
592 #[inline]
594 pub const fn sonic_testnet() -> Self {
595 Self::from_named(NamedChain::SonicTestnet)
596 }
597
598 #[inline]
600 pub const fn superposition_testnet() -> Self {
601 Self::from_named(NamedChain::SuperpositionTestnet)
602 }
603
604 #[inline]
606 pub const fn superposition() -> Self {
607 Self::from_named(NamedChain::Superposition)
608 }
609
610 #[inline]
612 pub const fn unichain_mainnet() -> Self {
613 Self::from_named(NamedChain::Unichain)
614 }
615
616 #[inline]
618 pub const fn unichain_sepolia() -> Self {
619 Self::from_named(NamedChain::UnichainSepolia)
620 }
621
622 #[inline]
624 pub const fn zksync() -> Self {
625 Self::from_named(NamedChain::ZkSync)
626 }
627
628 #[inline]
630 pub const fn zksync_testnet() -> Self {
631 Self::from_named(NamedChain::ZkSyncTestnet)
632 }
633
634 #[inline]
636 pub const fn abs() -> Self {
637 Self::from_named(NamedChain::Abstract)
638 }
639
640 #[inline]
642 pub const fn abstract_testnet() -> Self {
643 Self::from_named(NamedChain::AbstractTestnet)
644 }
645
646 #[inline]
648 pub const fn sophon() -> Self {
649 Self::from_named(NamedChain::Sophon)
650 }
651
652 #[inline]
654 pub const fn sophon_testnet() -> Self {
655 Self::from_named(NamedChain::SophonTestnet)
656 }
657
658 #[inline]
660 pub const fn lens() -> Self {
661 Self::from_named(NamedChain::Lens)
662 }
663
664 #[inline]
666 pub const fn lens_testnet() -> Self {
667 Self::from_named(NamedChain::LensTestnet)
668 }
669
670 #[inline]
672 pub const fn tempo_testnet() -> Self {
673 Self::from_named(NamedChain::TempoTestnet)
674 }
675
676 #[inline]
678 pub const fn kind(&self) -> &ChainKind {
679 &self.0
680 }
681
682 #[inline]
684 pub const fn into_kind(self) -> ChainKind {
685 self.0
686 }
687
688 #[inline]
690 pub const fn is_ethereum(&self) -> bool {
691 matches!(self.named(), Some(named) if named.is_ethereum())
692 }
693
694 #[inline]
696 pub const fn is_optimism(self) -> bool {
697 matches!(self.named(), Some(named) if named.is_optimism())
698 }
699
700 #[inline]
702 pub const fn is_gnosis(self) -> bool {
703 matches!(self.named(), Some(named) if named.is_gnosis())
704 }
705
706 #[inline]
708 pub const fn is_polygon(&self) -> bool {
709 matches!(self.named(), Some(named) if named.is_polygon())
710 }
711
712 #[inline]
714 pub const fn is_arbitrum(self) -> bool {
715 matches!(self.named(), Some(named) if named.is_arbitrum())
716 }
717
718 #[inline]
720 pub const fn is_elastic(self) -> bool {
721 matches!(self.named(), Some(named) if named.is_elastic())
722 }
723
724 #[inline]
726 pub const fn is_tempo(self) -> bool {
727 matches!(self.named(), Some(named) if named.is_tempo())
728 }
729
730 #[inline]
732 pub const fn named(self) -> Option<NamedChain> {
733 match *self.kind() {
734 ChainKind::Named(named) => Some(named),
735 ChainKind::Id(_) => None,
736 }
737 }
738
739 #[inline]
741 pub const fn id(self) -> u64 {
742 match *self.kind() {
743 ChainKind::Named(named) => named as u64,
744 ChainKind::Id(id) => id,
745 }
746 }
747}
748
749impl Chain {
752 pub const fn average_blocktime_hint(self) -> Option<Duration> {
756 match self.kind() {
757 ChainKind::Named(named) => named.average_blocktime_hint(),
758 ChainKind::Id(_) => None,
759 }
760 }
761
762 pub const fn is_legacy(self) -> bool {
766 match self.kind() {
767 ChainKind::Named(named) => named.is_legacy(),
768 ChainKind::Id(_) => false,
769 }
770 }
771
772 pub const fn supports_shanghai(self) -> bool {
778 match self.kind() {
779 ChainKind::Named(named) => named.supports_shanghai(),
780 ChainKind::Id(_) => false,
781 }
782 }
783
784 pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> {
788 match self.kind() {
789 ChainKind::Named(named) => named.etherscan_urls(),
790 ChainKind::Id(_) => None,
791 }
792 }
793
794 pub const fn etherscan_api_key_name(self) -> Option<&'static str> {
798 match self.kind() {
799 ChainKind::Named(named) => named.etherscan_api_key_name(),
800 ChainKind::Id(_) => None,
801 }
802 }
803
804 #[cfg(feature = "std")]
809 pub fn etherscan_api_key(self) -> Option<String> {
810 match self.kind() {
811 ChainKind::Named(named) => named.etherscan_api_key(),
812 ChainKind::Id(_) => None,
813 }
814 }
815
816 pub fn public_dns_network_protocol(self) -> Option<String> {
820 match self.kind() {
821 ChainKind::Named(named) => named.public_dns_network_protocol(),
822 ChainKind::Id(_) => None,
823 }
824 }
825}
826
827#[cfg(test)]
828mod tests {
829 use super::*;
830
831 #[allow(unused_imports)]
832 use alloc::string::ToString;
833
834 #[test]
835 fn test_id() {
836 assert_eq!(Chain::from_id(1234).id(), 1234);
837 }
838
839 #[test]
840 fn test_named_id() {
841 assert_eq!(Chain::from_named(NamedChain::Goerli).id(), 5);
842 }
843
844 #[test]
845 fn test_display_named_chain() {
846 assert_eq!(Chain::from_named(NamedChain::Mainnet).to_string(), "mainnet");
847 }
848
849 #[test]
850 fn test_display_id_chain() {
851 assert_eq!(Chain::from_id(1234).to_string(), "1234");
852 }
853
854 #[test]
855 fn test_from_str_named_chain() {
856 let result = Chain::from_str("mainnet");
857 let expected = Chain::from_named(NamedChain::Mainnet);
858 assert_eq!(result.unwrap(), expected);
859 }
860
861 #[test]
862 fn test_from_str_named_chain_error() {
863 let result = Chain::from_str("chain");
864 assert!(result.is_err());
865 }
866
867 #[test]
868 fn test_from_str_id_chain() {
869 let result = Chain::from_str("1234");
870 let expected = Chain::from_id(1234);
871 assert_eq!(result.unwrap(), expected);
872 }
873
874 #[test]
875 fn test_default() {
876 let default = Chain::default();
877 let expected = Chain::from_named(NamedChain::Mainnet);
878 assert_eq!(default, expected);
879 }
880
881 #[cfg(feature = "rlp")]
882 #[test]
883 fn test_id_chain_encodable_length() {
884 use alloy_rlp::Encodable;
885
886 let chain = Chain::from_id(1234);
887 assert_eq!(chain.length(), 3);
888 }
889
890 #[cfg(feature = "serde")]
891 #[test]
892 fn test_serde() {
893 let chains = r#"["mainnet",1,137,80002]"#;
894 let re = r#"["mainnet","mainnet","polygon","amoy"]"#;
895 let expected = [
896 Chain::from_named(NamedChain::Mainnet),
897 Chain::mainnet(),
898 Chain::from_named(NamedChain::Polygon),
899 Chain::from_id(80002),
900 ];
901 assert_eq!(serde_json::from_str::<alloc::vec::Vec<Chain>>(chains).unwrap(), expected);
902 assert_eq!(serde_json::to_string(&expected).unwrap(), re);
903 }
904}