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 sepolia() -> Self {
277 Self::from_named(NamedChain::Sepolia)
278 }
279
280 #[inline]
282 pub const fn optimism_mainnet() -> Self {
283 Self::from_named(NamedChain::Optimism)
284 }
285
286 #[inline]
288 pub const fn optimism_goerli() -> Self {
289 Self::from_named(NamedChain::OptimismGoerli)
290 }
291
292 #[inline]
294 pub const fn optimism_sepolia() -> Self {
295 Self::from_named(NamedChain::OptimismSepolia)
296 }
297
298 #[inline]
300 pub const fn base_mainnet() -> Self {
301 Self::from_named(NamedChain::Base)
302 }
303
304 #[inline]
306 pub const fn base_goerli() -> Self {
307 Self::from_named(NamedChain::BaseGoerli)
308 }
309
310 #[inline]
312 pub const fn base_sepolia() -> Self {
313 Self::from_named(NamedChain::BaseSepolia)
314 }
315
316 #[inline]
318 pub const fn arbitrum_mainnet() -> Self {
319 Self::from_named(NamedChain::Arbitrum)
320 }
321
322 #[inline]
324 pub const fn arbitrum_nova() -> Self {
325 Self::from_named(NamedChain::ArbitrumNova)
326 }
327
328 #[inline]
330 pub const fn arbitrum_goerli() -> Self {
331 Self::from_named(NamedChain::ArbitrumGoerli)
332 }
333
334 #[inline]
336 pub const fn arbitrum_sepolia() -> Self {
337 Self::from_named(NamedChain::ArbitrumSepolia)
338 }
339
340 #[inline]
342 pub const fn arbitrum_testnet() -> Self {
343 Self::from_named(NamedChain::ArbitrumTestnet)
344 }
345
346 #[inline]
348 pub const fn syndr() -> Self {
349 Self::from_named(NamedChain::Syndr)
350 }
351
352 #[inline]
354 pub const fn syndr_sepolia() -> Self {
355 Self::from_named(NamedChain::SyndrSepolia)
356 }
357
358 #[inline]
360 pub const fn fraxtal() -> Self {
361 Self::from_named(NamedChain::Fraxtal)
362 }
363
364 #[inline]
366 pub const fn fraxtal_testnet() -> Self {
367 Self::from_named(NamedChain::FraxtalTestnet)
368 }
369
370 #[inline]
372 pub const fn blast() -> Self {
373 Self::from_named(NamedChain::Blast)
374 }
375
376 #[inline]
378 pub const fn blast_sepolia() -> Self {
379 Self::from_named(NamedChain::BlastSepolia)
380 }
381
382 #[inline]
384 pub const fn linea() -> Self {
385 Self::from_named(NamedChain::Linea)
386 }
387
388 #[inline]
390 pub const fn linea_goerli() -> Self {
391 Self::from_named(NamedChain::LineaGoerli)
392 }
393
394 #[inline]
396 pub const fn linea_sepolia() -> Self {
397 Self::from_named(NamedChain::LineaSepolia)
398 }
399
400 #[inline]
402 pub const fn mode() -> Self {
403 Self::from_named(NamedChain::Mode)
404 }
405
406 #[inline]
408 pub const fn mode_sepolia() -> Self {
409 Self::from_named(NamedChain::ModeSepolia)
410 }
411
412 #[inline]
414 pub const fn elastos() -> Self {
415 Self::from_named(NamedChain::Elastos)
416 }
417
418 #[inline]
420 pub const fn degen() -> Self {
421 Self::from_named(NamedChain::Degen)
422 }
423
424 #[inline]
426 pub const fn dev() -> Self {
427 Self::from_named(NamedChain::Dev)
428 }
429
430 #[inline]
432 pub const fn bsc_mainnet() -> Self {
433 Self::from_named(NamedChain::BinanceSmartChain)
434 }
435
436 #[inline]
438 pub const fn bsc_testnet() -> Self {
439 Self::from_named(NamedChain::BinanceSmartChainTestnet)
440 }
441
442 #[inline]
444 pub const fn opbnb_mainnet() -> Self {
445 Self::from_named(NamedChain::OpBNBMainnet)
446 }
447
448 #[inline]
450 pub const fn opbnb_testnet() -> Self {
451 Self::from_named(NamedChain::OpBNBTestnet)
452 }
453
454 #[inline]
456 pub const fn ronin() -> Self {
457 Self::from_named(NamedChain::Ronin)
458 }
459
460 #[inline]
462 pub const fn ronin_testnet() -> Self {
463 Self::from_named(NamedChain::RoninTestnet)
464 }
465
466 #[inline]
468 pub const fn taiko() -> Self {
469 Self::from_named(NamedChain::Taiko)
470 }
471
472 #[inline]
474 pub const fn taiko_hekla() -> Self {
475 Self::from_named(NamedChain::TaikoHekla)
476 }
477
478 #[inline]
480 pub const fn shimmer() -> Self {
481 Self::from_named(NamedChain::Shimmer)
482 }
483
484 #[inline]
486 pub const fn flare() -> Self {
487 Self::from_named(NamedChain::Flare)
488 }
489
490 #[inline]
492 pub const fn flare_coston2() -> Self {
493 Self::from_named(NamedChain::FlareCoston2)
494 }
495
496 #[inline]
498 pub const fn darwinia() -> Self {
499 Self::from_named(NamedChain::Darwinia)
500 }
501
502 #[inline]
504 pub const fn crab() -> Self {
505 Self::from_named(NamedChain::Crab)
506 }
507
508 #[inline]
510 pub const fn koi() -> Self {
511 Self::from_named(NamedChain::Koi)
512 }
513
514 #[inline]
516 pub const fn immutable() -> Self {
517 Self::from_named(NamedChain::Immutable)
518 }
519
520 #[inline]
522 pub const fn immutable_testnet() -> Self {
523 Self::from_named(NamedChain::ImmutableTestnet)
524 }
525
526 #[inline]
528 pub const fn ink_sepolia() -> Self {
529 Self::from_named(NamedChain::InkSepolia)
530 }
531
532 #[inline]
534 pub const fn ink_mainnet() -> Self {
535 Self::from_named(NamedChain::Ink)
536 }
537
538 #[inline]
540 pub const fn scroll_mainnet() -> Self {
541 Self::from_named(NamedChain::Scroll)
542 }
543
544 #[inline]
546 pub const fn scroll_sepolia() -> Self {
547 Self::from_named(NamedChain::ScrollSepolia)
548 }
549
550 #[inline]
552 pub const fn treasure() -> Self {
553 Self::from_named(NamedChain::Treasure)
554 }
555
556 #[inline]
558 pub const fn treasure_topaz_testnet() -> Self {
559 Self::from_named(NamedChain::TreasureTopaz)
560 }
561
562 #[inline]
564 pub const fn superposition_testnet() -> Self {
565 Self::from_named(NamedChain::SuperpositionTestnet)
566 }
567
568 #[inline]
570 pub const fn superposition() -> Self {
571 Self::from_named(NamedChain::Superposition)
572 }
573
574 #[inline]
576 pub const fn unichain_mainnet() -> Self {
577 Self::from_named(NamedChain::Unichain)
578 }
579
580 #[inline]
582 pub const fn unichain_sepolia() -> Self {
583 Self::from_named(NamedChain::UnichainSepolia)
584 }
585
586 #[inline]
588 pub const fn kind(&self) -> &ChainKind {
589 &self.0
590 }
591
592 #[inline]
594 pub const fn into_kind(self) -> ChainKind {
595 self.0
596 }
597
598 #[inline]
600 pub const fn is_ethereum(&self) -> bool {
601 matches!(self.named(), Some(named) if named.is_ethereum())
602 }
603
604 #[inline]
606 pub const fn is_optimism(self) -> bool {
607 matches!(self.named(), Some(named) if named.is_optimism())
608 }
609
610 #[inline]
612 pub const fn is_arbitrum(self) -> bool {
613 matches!(self.named(), Some(named) if named.is_arbitrum())
614 }
615
616 #[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 #[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
635impl Chain {
638 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 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 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 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 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 #[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 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}