1use alloy_primitives::{address, Address};
9use core::fmt;
10
11#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
16#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
17pub enum Chain {
18 #[cfg_attr(feature = "clap", value(name = "ethereum"))]
20 Ethereum,
21 #[cfg_attr(feature = "clap", value(name = "arbitrum"))]
23 Arbitrum,
24 #[cfg_attr(feature = "clap", value(name = "optimism"))]
26 Optimism,
27 #[cfg_attr(feature = "clap", value(name = "polygon"))]
29 Polygon,
30 #[cfg_attr(feature = "clap", value(name = "base"))]
32 Base,
33 #[cfg_attr(feature = "clap", value(name = "bsc"))]
35 Bsc,
36 #[cfg_attr(feature = "clap", value(name = "sonic"))]
38 Sonic,
39 #[cfg_attr(feature = "clap", value(name = "avalanche"))]
41 Avalanche,
42 #[cfg_attr(feature = "clap", value(name = "celo"))]
44 Celo,
45 #[cfg_attr(feature = "clap", value(name = "hyperevm"))]
48 HyperEvm,
49}
50
51impl Chain {
52 pub const ALL: &'static [Self] = &[
54 Self::Ethereum,
55 Self::Arbitrum,
56 Self::Optimism,
57 Self::Polygon,
58 Self::Base,
59 Self::Bsc,
60 Self::Sonic,
61 Self::Avalanche,
62 Self::Celo,
63 Self::HyperEvm,
64 ];
65
66 pub const fn id(self) -> u64 {
68 match self {
69 Self::Ethereum => 1,
70 Self::Optimism => 10,
71 Self::Bsc => 56,
72 Self::Polygon => 137,
73 Self::Sonic => 146,
74 Self::HyperEvm => 999,
75 Self::Base => 8_453,
76 Self::Arbitrum => 42_161,
77 Self::Celo => 42_220,
78 Self::Avalanche => 43_114,
79 }
80 }
81
82 pub const fn name(self) -> &'static str {
84 match self {
85 Self::Ethereum => "ethereum",
86 Self::Arbitrum => "arbitrum",
87 Self::Optimism => "optimism",
88 Self::Polygon => "polygon",
89 Self::Base => "base",
90 Self::Bsc => "bsc",
91 Self::Sonic => "sonic",
92 Self::Avalanche => "avalanche",
93 Self::Celo => "celo",
94 Self::HyperEvm => "hyperevm",
95 }
96 }
97
98 pub const fn wrapped_native(self) -> Option<Address> {
110 match self {
111 Self::Ethereum => Some(address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")),
113 Self::Arbitrum => Some(address!("82aF49447D8a07e3bd95BD0d56f35241523fBab1")),
115 Self::Optimism => Some(address!("4200000000000000000000000000000000000006")),
117 Self::Polygon => Some(address!("0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270")),
120 Self::Base => Some(address!("4200000000000000000000000000000000000006")),
122 Self::Bsc => Some(address!("bb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c")),
125 Self::Sonic => Some(address!("039e2fB66102314Ce7b64Ce5Ce3E5183bc94aD38")),
128 Self::Avalanche => Some(address!("B31f66AA3C1e785363F0875A1B74E27b85FD66c7")),
131 Self::Celo => None,
133 Self::HyperEvm => Some(address!("5555555555555555555555555555555555555555")),
137 }
138 }
139}
140
141impl TryFrom<u64> for Chain {
142 type Error = u64;
143
144 fn try_from(id: u64) -> Result<Self, u64> {
145 Ok(match id {
146 1 => Self::Ethereum,
147 10 => Self::Optimism,
148 56 => Self::Bsc,
149 137 => Self::Polygon,
150 146 => Self::Sonic,
151 999 => Self::HyperEvm,
152 8_453 => Self::Base,
153 42_161 => Self::Arbitrum,
154 42_220 => Self::Celo,
155 43_114 => Self::Avalanche,
156 other => return Err(other),
157 })
158 }
159}
160
161impl fmt::Display for Chain {
162 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
163 f.write_str(self.name())
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn id_round_trip() {
173 for &chain in Chain::ALL {
174 let id = chain.id();
175 let back = Chain::try_from(id).expect("known chain id");
176 assert_eq!(back, chain, "round-trip failed for {chain}");
177 }
178 }
179
180 #[test]
181 fn avalanche_chain_id_is_43114() {
182 assert_eq!(Chain::Avalanche.id(), 43_114);
183 assert_eq!(Chain::try_from(43_114).unwrap(), Chain::Avalanche);
184 }
185
186 #[test]
187 fn sonic_chain_id_is_146() {
188 assert_eq!(Chain::Sonic.id(), 146);
189 assert_eq!(Chain::try_from(146).unwrap(), Chain::Sonic);
190 }
191
192 #[test]
193 fn hyperevm_chain_id_is_999() {
194 assert_eq!(Chain::HyperEvm.id(), 999);
195 assert_eq!(Chain::try_from(999).unwrap(), Chain::HyperEvm);
196 }
197
198 #[test]
199 fn try_from_unknown_returns_id() {
200 let unknown = 999_999u64;
201 assert_eq!(Chain::try_from(unknown), Err(unknown));
202 }
203
204 #[test]
205 fn name_is_lowercase() {
206 assert_eq!(Chain::Ethereum.name(), "ethereum");
207 assert_eq!(Chain::Bsc.name(), "bsc");
208 assert_eq!(Chain::Arbitrum.name(), "arbitrum");
209 }
210
211 #[test]
212 fn display_matches_name() {
213 assert_eq!(format!("{}", Chain::Ethereum), "ethereum");
214 assert_eq!(format!("{}", Chain::Celo), "celo");
215 }
216
217 #[test]
218 fn wrapped_native_round_trip() {
219 for chain in Chain::ALL {
224 let _ = chain.wrapped_native(); }
226 }
227
228 #[test]
229 fn wrapped_native_celo_is_none() {
230 assert_eq!(Chain::Celo.wrapped_native(), None);
231 }
232
233 #[test]
234 fn wrapped_native_hyperevm_is_whype() {
235 assert_eq!(
236 Chain::HyperEvm.wrapped_native(),
237 Some(address!("5555555555555555555555555555555555555555"))
238 );
239 }
240
241 #[test]
242 fn wrapped_native_distinct_per_evm_chain() {
243 assert_ne!(Chain::Ethereum.wrapped_native(), Chain::Polygon.wrapped_native(),);
247 assert_ne!(Chain::Ethereum.wrapped_native(), Chain::Avalanche.wrapped_native(),);
248 assert_ne!(Chain::Polygon.wrapped_native(), Chain::Bsc.wrapped_native(),);
249 }
250}