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 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 #[inline]
232 pub const fn from_named(named: NamedChain) -> Self {
233 Self(ChainKind::Named(named))
234 }
235
236 #[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 #[inline]
252 pub const fn from_id_unchecked(id: u64) -> Self {
253 Self(ChainKind::Id(id))
254 }
255
256 #[inline]
258 pub const fn mainnet() -> Self {
259 Self::from_named(NamedChain::Mainnet)
260 }
261
262 #[inline]
264 pub const fn goerli() -> Self {
265 Self::from_named(NamedChain::Goerli)
266 }
267
268 #[inline]
270 pub const fn holesky() -> Self {
271 Self::from_named(NamedChain::Holesky)
272 }
273
274 #[inline]
276 pub const fn hoodi() -> Self {
277 Self::from_named(NamedChain::Hoodi)
278 }
279
280 #[inline]
282 pub const fn sepolia() -> Self {
283 Self::from_named(NamedChain::Sepolia)
284 }
285
286 #[inline]
288 pub const fn optimism_mainnet() -> Self {
289 Self::from_named(NamedChain::Optimism)
290 }
291
292 #[inline]
294 pub const fn optimism_goerli() -> Self {
295 Self::from_named(NamedChain::OptimismGoerli)
296 }
297
298 #[inline]
300 pub const fn optimism_sepolia() -> Self {
301 Self::from_named(NamedChain::OptimismSepolia)
302 }
303
304 #[inline]
306 pub const fn base_mainnet() -> Self {
307 Self::from_named(NamedChain::Base)
308 }
309
310 #[inline]
312 pub const fn base_goerli() -> Self {
313 Self::from_named(NamedChain::BaseGoerli)
314 }
315
316 #[inline]
318 pub const fn base_sepolia() -> Self {
319 Self::from_named(NamedChain::BaseSepolia)
320 }
321
322 #[inline]
324 pub const fn arbitrum_mainnet() -> Self {
325 Self::from_named(NamedChain::Arbitrum)
326 }
327
328 #[inline]
330 pub const fn arbitrum_nova() -> Self {
331 Self::from_named(NamedChain::ArbitrumNova)
332 }
333
334 #[inline]
336 pub const fn arbitrum_goerli() -> Self {
337 Self::from_named(NamedChain::ArbitrumGoerli)
338 }
339
340 #[inline]
342 pub const fn arbitrum_sepolia() -> Self {
343 Self::from_named(NamedChain::ArbitrumSepolia)
344 }
345
346 #[inline]
348 pub const fn arbitrum_testnet() -> Self {
349 Self::from_named(NamedChain::ArbitrumTestnet)
350 }
351
352 #[inline]
354 pub const fn syndr() -> Self {
355 Self::from_named(NamedChain::Syndr)
356 }
357
358 #[inline]
360 pub const fn syndr_sepolia() -> Self {
361 Self::from_named(NamedChain::SyndrSepolia)
362 }
363
364 #[inline]
366 pub const fn fraxtal() -> Self {
367 Self::from_named(NamedChain::Fraxtal)
368 }
369
370 #[inline]
372 pub const fn fraxtal_testnet() -> Self {
373 Self::from_named(NamedChain::FraxtalTestnet)
374 }
375
376 #[inline]
378 pub const fn blast() -> Self {
379 Self::from_named(NamedChain::Blast)
380 }
381
382 #[inline]
384 pub const fn blast_sepolia() -> Self {
385 Self::from_named(NamedChain::BlastSepolia)
386 }
387
388 #[inline]
390 pub const fn linea() -> Self {
391 Self::from_named(NamedChain::Linea)
392 }
393
394 #[inline]
396 pub const fn linea_goerli() -> Self {
397 Self::from_named(NamedChain::LineaGoerli)
398 }
399
400 #[inline]
402 pub const fn linea_sepolia() -> Self {
403 Self::from_named(NamedChain::LineaSepolia)
404 }
405
406 #[inline]
408 pub const fn mode() -> Self {
409 Self::from_named(NamedChain::Mode)
410 }
411
412 #[inline]
414 pub const fn mode_sepolia() -> Self {
415 Self::from_named(NamedChain::ModeSepolia)
416 }
417
418 #[inline]
420 pub const fn elastos() -> Self {
421 Self::from_named(NamedChain::Elastos)
422 }
423
424 #[inline]
426 pub const fn degen() -> Self {
427 Self::from_named(NamedChain::Degen)
428 }
429
430 #[inline]
432 pub const fn dev() -> Self {
433 Self::from_named(NamedChain::Dev)
434 }
435
436 #[inline]
438 pub const fn bsc_mainnet() -> Self {
439 Self::from_named(NamedChain::BinanceSmartChain)
440 }
441
442 #[inline]
444 pub const fn bsc_testnet() -> Self {
445 Self::from_named(NamedChain::BinanceSmartChainTestnet)
446 }
447
448 #[inline]
450 pub const fn opbnb_mainnet() -> Self {
451 Self::from_named(NamedChain::OpBNBMainnet)
452 }
453
454 #[inline]
456 pub const fn opbnb_testnet() -> Self {
457 Self::from_named(NamedChain::OpBNBTestnet)
458 }
459
460 #[inline]
462 pub const fn ronin() -> Self {
463 Self::from_named(NamedChain::Ronin)
464 }
465
466 #[inline]
468 pub const fn ronin_testnet() -> Self {
469 Self::from_named(NamedChain::RoninTestnet)
470 }
471
472 #[inline]
474 pub const fn taiko() -> Self {
475 Self::from_named(NamedChain::Taiko)
476 }
477
478 #[inline]
480 pub const fn taiko_hekla() -> Self {
481 Self::from_named(NamedChain::TaikoHekla)
482 }
483
484 #[inline]
486 pub const fn shimmer() -> Self {
487 Self::from_named(NamedChain::Shimmer)
488 }
489
490 #[inline]
492 pub const fn flare() -> Self {
493 Self::from_named(NamedChain::Flare)
494 }
495
496 #[inline]
498 pub const fn flare_coston2() -> Self {
499 Self::from_named(NamedChain::FlareCoston2)
500 }
501
502 #[inline]
504 pub const fn darwinia() -> Self {
505 Self::from_named(NamedChain::Darwinia)
506 }
507
508 #[inline]
510 pub const fn crab() -> Self {
511 Self::from_named(NamedChain::Crab)
512 }
513
514 #[inline]
516 pub const fn koi() -> Self {
517 Self::from_named(NamedChain::Koi)
518 }
519
520 #[inline]
522 pub const fn immutable() -> Self {
523 Self::from_named(NamedChain::Immutable)
524 }
525
526 #[inline]
528 pub const fn immutable_testnet() -> Self {
529 Self::from_named(NamedChain::ImmutableTestnet)
530 }
531
532 #[inline]
534 pub const fn ink_sepolia() -> Self {
535 Self::from_named(NamedChain::InkSepolia)
536 }
537
538 #[inline]
540 pub const fn ink_mainnet() -> Self {
541 Self::from_named(NamedChain::Ink)
542 }
543
544 #[inline]
546 pub const fn scroll_mainnet() -> Self {
547 Self::from_named(NamedChain::Scroll)
548 }
549
550 #[inline]
552 pub const fn scroll_sepolia() -> Self {
553 Self::from_named(NamedChain::ScrollSepolia)
554 }
555
556 #[inline]
558 pub const fn treasure() -> Self {
559 Self::from_named(NamedChain::Treasure)
560 }
561
562 #[inline]
564 pub const fn treasure_topaz_testnet() -> Self {
565 Self::from_named(NamedChain::TreasureTopaz)
566 }
567
568 #[inline]
570 pub const fn berachain() -> Self {
571 Self::from_named(NamedChain::Berachain)
572 }
573
574 #[inline]
576 pub const fn berachain_bepolia() -> Self {
577 Self::from_named(NamedChain::BerachainBepolia)
578 }
579
580 #[inline]
582 pub const fn sonic() -> Self {
583 Self::from_named(NamedChain::Sonic)
584 }
585
586 #[inline]
588 pub const fn sonic_blaze() -> Self {
589 Self::from_named(NamedChain::SonicBlaze)
590 }
591
592 #[inline]
594 pub const fn superposition_testnet() -> Self {
595 Self::from_named(NamedChain::SuperpositionTestnet)
596 }
597
598 #[inline]
600 pub const fn superposition() -> Self {
601 Self::from_named(NamedChain::Superposition)
602 }
603
604 #[inline]
606 pub const fn unichain_mainnet() -> Self {
607 Self::from_named(NamedChain::Unichain)
608 }
609
610 #[inline]
612 pub const fn unichain_sepolia() -> Self {
613 Self::from_named(NamedChain::UnichainSepolia)
614 }
615
616 #[inline]
618 pub const fn kind(&self) -> &ChainKind {
619 &self.0
620 }
621
622 #[inline]
624 pub const fn into_kind(self) -> ChainKind {
625 self.0
626 }
627
628 #[inline]
630 pub const fn is_ethereum(&self) -> bool {
631 matches!(self.named(), Some(named) if named.is_ethereum())
632 }
633
634 #[inline]
636 pub const fn is_optimism(self) -> bool {
637 matches!(self.named(), Some(named) if named.is_optimism())
638 }
639
640 #[inline]
642 pub const fn is_arbitrum(self) -> bool {
643 matches!(self.named(), Some(named) if named.is_arbitrum())
644 }
645
646 #[inline]
648 pub const fn named(self) -> Option<NamedChain> {
649 match *self.kind() {
650 ChainKind::Named(named) => Some(named),
651 ChainKind::Id(_) => None,
652 }
653 }
654
655 #[inline]
657 pub const fn id(self) -> u64 {
658 match *self.kind() {
659 ChainKind::Named(named) => named as u64,
660 ChainKind::Id(id) => id,
661 }
662 }
663}
664
665impl Chain {
668 pub const fn average_blocktime_hint(self) -> Option<Duration> {
672 match self.kind() {
673 ChainKind::Named(named) => named.average_blocktime_hint(),
674 ChainKind::Id(_) => None,
675 }
676 }
677
678 pub const fn is_legacy(self) -> bool {
682 match self.kind() {
683 ChainKind::Named(named) => named.is_legacy(),
684 ChainKind::Id(_) => false,
685 }
686 }
687
688 pub const fn supports_shanghai(self) -> bool {
694 match self.kind() {
695 ChainKind::Named(named) => named.supports_shanghai(),
696 ChainKind::Id(_) => false,
697 }
698 }
699
700 #[doc(hidden)]
701 #[deprecated(since = "0.1.3", note = "use `supports_shanghai` instead")]
702 pub const fn supports_push0(self) -> bool {
703 self.supports_shanghai()
704 }
705
706 pub const fn etherscan_urls(self) -> Option<(&'static str, &'static str)> {
710 match self.kind() {
711 ChainKind::Named(named) => named.etherscan_urls(),
712 ChainKind::Id(_) => None,
713 }
714 }
715
716 pub const fn etherscan_api_key_name(self) -> Option<&'static str> {
720 match self.kind() {
721 ChainKind::Named(named) => named.etherscan_api_key_name(),
722 ChainKind::Id(_) => None,
723 }
724 }
725
726 #[cfg(feature = "std")]
731 pub fn etherscan_api_key(self) -> Option<String> {
732 match self.kind() {
733 ChainKind::Named(named) => named.etherscan_api_key(),
734 ChainKind::Id(_) => None,
735 }
736 }
737
738 pub fn public_dns_network_protocol(self) -> Option<String> {
742 match self.kind() {
743 ChainKind::Named(named) => named.public_dns_network_protocol(),
744 ChainKind::Id(_) => None,
745 }
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use super::*;
752
753 #[allow(unused_imports)]
754 use alloc::string::ToString;
755
756 #[test]
757 fn test_id() {
758 assert_eq!(Chain::from_id(1234).id(), 1234);
759 }
760
761 #[test]
762 fn test_named_id() {
763 assert_eq!(Chain::from_named(NamedChain::Goerli).id(), 5);
764 }
765
766 #[test]
767 fn test_display_named_chain() {
768 assert_eq!(Chain::from_named(NamedChain::Mainnet).to_string(), "mainnet");
769 }
770
771 #[test]
772 fn test_display_id_chain() {
773 assert_eq!(Chain::from_id(1234).to_string(), "1234");
774 }
775
776 #[test]
777 fn test_from_str_named_chain() {
778 let result = Chain::from_str("mainnet");
779 let expected = Chain::from_named(NamedChain::Mainnet);
780 assert_eq!(result.unwrap(), expected);
781 }
782
783 #[test]
784 fn test_from_str_named_chain_error() {
785 let result = Chain::from_str("chain");
786 assert!(result.is_err());
787 }
788
789 #[test]
790 fn test_from_str_id_chain() {
791 let result = Chain::from_str("1234");
792 let expected = Chain::from_id(1234);
793 assert_eq!(result.unwrap(), expected);
794 }
795
796 #[test]
797 fn test_default() {
798 let default = Chain::default();
799 let expected = Chain::from_named(NamedChain::Mainnet);
800 assert_eq!(default, expected);
801 }
802
803 #[cfg(feature = "rlp")]
804 #[test]
805 fn test_id_chain_encodable_length() {
806 use alloy_rlp::Encodable;
807
808 let chain = Chain::from_id(1234);
809 assert_eq!(chain.length(), 3);
810 }
811
812 #[cfg(feature = "serde")]
813 #[test]
814 fn test_serde() {
815 let chains = r#"["mainnet",1,80001,80002,"mumbai"]"#;
816 let re = r#"["mainnet","mainnet","mumbai","amoy","mumbai"]"#;
817 let expected = [
818 Chain::mainnet(),
819 Chain::mainnet(),
820 Chain::from_named(NamedChain::PolygonMumbai),
821 Chain::from_id(80002),
822 Chain::from_named(NamedChain::PolygonMumbai),
823 ];
824 assert_eq!(serde_json::from_str::<alloc::vec::Vec<Chain>>(chains).unwrap(), expected);
825 assert_eq!(serde_json::to_string(&expected).unwrap(), re);
826 }
827}