odos_sdk/types/chain.rs
1use std::fmt;
2
3use alloy_chains::NamedChain;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5
6use crate::{OdosChain, OdosChainError, OdosChainResult};
7
8/// Type-safe chain identifier with convenient constructors
9///
10/// Provides ergonomic helpers for accessing supported chains while
11/// maintaining full compatibility with `alloy_chains::NamedChain`.
12///
13/// # Examples
14///
15/// ```rust
16/// use odos_sdk::{Chain, OdosChain};
17///
18/// // Convenient constructors
19/// let chain = Chain::ethereum();
20/// let chain = Chain::arbitrum();
21/// let chain = Chain::base();
22///
23/// // From chain ID
24/// let chain = Chain::from_chain_id(1)?; // Ethereum
25/// let chain = Chain::from_chain_id(42161)?; // Arbitrum
26///
27/// // Access inner NamedChain
28/// let named = chain.inner();
29///
30/// // Use OdosChain trait methods
31/// let router = chain.v3_router_address()?;
32/// # Ok::<(), Box<dyn std::error::Error>>(())
33/// ```
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub struct Chain(NamedChain);
36
37impl Chain {
38 /// Ethereum Mainnet (Chain ID: 1)
39 ///
40 /// # Examples
41 ///
42 /// ```rust
43 /// use odos_sdk::Chain;
44 ///
45 /// let chain = Chain::ethereum();
46 /// assert_eq!(chain.id(), 1);
47 /// ```
48 pub const fn ethereum() -> Self {
49 Self(NamedChain::Mainnet)
50 }
51
52 /// Arbitrum One (Chain ID: 42161)
53 ///
54 /// # Examples
55 ///
56 /// ```rust
57 /// use odos_sdk::Chain;
58 ///
59 /// let chain = Chain::arbitrum();
60 /// assert_eq!(chain.id(), 42161);
61 /// ```
62 pub const fn arbitrum() -> Self {
63 Self(NamedChain::Arbitrum)
64 }
65
66 /// Optimism (Chain ID: 10)
67 ///
68 /// # Examples
69 ///
70 /// ```rust
71 /// use odos_sdk::Chain;
72 ///
73 /// let chain = Chain::optimism();
74 /// assert_eq!(chain.id(), 10);
75 /// ```
76 pub const fn optimism() -> Self {
77 Self(NamedChain::Optimism)
78 }
79
80 /// Polygon (Chain ID: 137)
81 ///
82 /// # Examples
83 ///
84 /// ```rust
85 /// use odos_sdk::Chain;
86 ///
87 /// let chain = Chain::polygon();
88 /// assert_eq!(chain.id(), 137);
89 /// ```
90 pub const fn polygon() -> Self {
91 Self(NamedChain::Polygon)
92 }
93
94 /// Base (Chain ID: 8453)
95 ///
96 /// # Examples
97 ///
98 /// ```rust
99 /// use odos_sdk::Chain;
100 ///
101 /// let chain = Chain::base();
102 /// assert_eq!(chain.id(), 8453);
103 /// ```
104 pub const fn base() -> Self {
105 Self(NamedChain::Base)
106 }
107
108 /// BNB Smart Chain (Chain ID: 56)
109 ///
110 /// # Examples
111 ///
112 /// ```rust
113 /// use odos_sdk::Chain;
114 ///
115 /// let chain = Chain::bsc();
116 /// assert_eq!(chain.id(), 56);
117 /// ```
118 pub const fn bsc() -> Self {
119 Self(NamedChain::BinanceSmartChain)
120 }
121
122 /// Avalanche C-Chain (Chain ID: 43114)
123 ///
124 /// # Examples
125 ///
126 /// ```rust
127 /// use odos_sdk::Chain;
128 ///
129 /// let chain = Chain::avalanche();
130 /// assert_eq!(chain.id(), 43114);
131 /// ```
132 pub const fn avalanche() -> Self {
133 Self(NamedChain::Avalanche)
134 }
135
136 /// Linea (Chain ID: 59144)
137 ///
138 /// # Examples
139 ///
140 /// ```rust
141 /// use odos_sdk::Chain;
142 ///
143 /// let chain = Chain::linea();
144 /// assert_eq!(chain.id(), 59144);
145 /// ```
146 pub const fn linea() -> Self {
147 Self(NamedChain::Linea)
148 }
149
150 /// Scroll (Chain ID: 534352)
151 ///
152 /// # Examples
153 ///
154 /// ```rust
155 /// use odos_sdk::Chain;
156 ///
157 /// let chain = Chain::scroll();
158 /// assert_eq!(chain.id(), 534352);
159 /// ```
160 pub const fn scroll() -> Self {
161 Self(NamedChain::Scroll)
162 }
163
164 /// ZkSync Era (Chain ID: 324)
165 ///
166 /// # Examples
167 ///
168 /// ```rust
169 /// use odos_sdk::Chain;
170 ///
171 /// let chain = Chain::zksync();
172 /// assert_eq!(chain.id(), 324);
173 /// ```
174 pub const fn zksync() -> Self {
175 Self(NamedChain::ZkSync)
176 }
177
178 /// Mantle (Chain ID: 5000)
179 ///
180 /// # Examples
181 ///
182 /// ```rust
183 /// use odos_sdk::Chain;
184 ///
185 /// let chain = Chain::mantle();
186 /// assert_eq!(chain.id(), 5000);
187 /// ```
188 pub const fn mantle() -> Self {
189 Self(NamedChain::Mantle)
190 }
191
192 /// Mode (Chain ID: 34443)
193 ///
194 /// # Examples
195 ///
196 /// ```rust
197 /// use odos_sdk::Chain;
198 ///
199 /// let chain = Chain::mode();
200 /// assert_eq!(chain.id(), 34443);
201 /// ```
202 pub const fn mode() -> Self {
203 Self(NamedChain::Mode)
204 }
205
206 /// Fraxtal (Chain ID: 252)
207 ///
208 /// # Examples
209 ///
210 /// ```rust
211 /// use odos_sdk::Chain;
212 ///
213 /// let chain = Chain::fraxtal();
214 /// assert_eq!(chain.id(), 252);
215 /// ```
216 pub const fn fraxtal() -> Self {
217 Self(NamedChain::Fraxtal)
218 }
219
220 /// Sonic (Chain ID: 146)
221 ///
222 /// # Examples
223 ///
224 /// ```rust
225 /// use odos_sdk::Chain;
226 ///
227 /// let chain = Chain::sonic();
228 /// assert_eq!(chain.id(), 146);
229 /// ```
230 pub const fn sonic() -> Self {
231 Self(NamedChain::Sonic)
232 }
233
234 /// Berachain (Chain ID: 80094)
235 ///
236 /// # Examples
237 ///
238 /// ```rust
239 /// use odos_sdk::Chain;
240 ///
241 /// let chain = Chain::berachain();
242 /// assert_eq!(chain.id(), 80094);
243 /// ```
244 pub const fn berachain() -> Self {
245 Self(NamedChain::Berachain)
246 }
247
248 /// Unichain (Chain ID: 130)
249 ///
250 /// # Examples
251 ///
252 /// ```rust
253 /// use odos_sdk::Chain;
254 ///
255 /// let chain = Chain::unichain();
256 /// assert_eq!(chain.id(), 130);
257 /// ```
258 pub const fn unichain() -> Self {
259 Self(NamedChain::Unichain)
260 }
261
262 /// Create a chain from a chain ID
263 ///
264 /// # Arguments
265 ///
266 /// * `id` - The EVM chain ID
267 ///
268 /// # Returns
269 ///
270 /// * `Ok(Chain)` - If the chain ID is recognized
271 /// * `Err(OdosChainError)` - If the chain ID is not supported
272 ///
273 /// # Examples
274 ///
275 /// ```rust
276 /// use odos_sdk::Chain;
277 ///
278 /// let chain = Chain::from_chain_id(1)?; // Ethereum
279 /// let chain = Chain::from_chain_id(42161)?; // Arbitrum
280 /// let chain = Chain::from_chain_id(8453)?; // Base
281 ///
282 /// // Unsupported chain
283 /// assert!(Chain::from_chain_id(999999).is_err());
284 /// # Ok::<(), Box<dyn std::error::Error>>(())
285 /// ```
286 pub fn from_chain_id(id: u64) -> OdosChainResult<Self> {
287 NamedChain::try_from(id)
288 .map(Self)
289 .map_err(|_| OdosChainError::UnsupportedChain {
290 chain: format!("Chain ID {id}"),
291 })
292 }
293
294 /// Get the chain ID
295 ///
296 /// # Examples
297 ///
298 /// ```rust
299 /// use odos_sdk::Chain;
300 ///
301 /// assert_eq!(Chain::ethereum().id(), 1);
302 /// assert_eq!(Chain::arbitrum().id(), 42161);
303 /// assert_eq!(Chain::base().id(), 8453);
304 /// ```
305 pub fn id(&self) -> u64 {
306 self.0.into()
307 }
308
309 /// Get the inner `NamedChain`
310 ///
311 /// # Examples
312 ///
313 /// ```rust
314 /// use odos_sdk::Chain;
315 /// use alloy_chains::NamedChain;
316 ///
317 /// let chain = Chain::ethereum();
318 /// assert_eq!(chain.inner(), NamedChain::Mainnet);
319 /// ```
320 pub const fn inner(&self) -> NamedChain {
321 self.0
322 }
323}
324
325impl fmt::Display for Chain {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 write!(f, "{}", self.0)
328 }
329}
330
331impl From<NamedChain> for Chain {
332 fn from(chain: NamedChain) -> Self {
333 Self(chain)
334 }
335}
336
337impl From<Chain> for NamedChain {
338 fn from(chain: Chain) -> Self {
339 chain.0
340 }
341}
342
343impl From<Chain> for u64 {
344 fn from(chain: Chain) -> Self {
345 chain.0.into()
346 }
347}
348
349// Custom serialization using chain ID
350impl Serialize for Chain {
351 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
352 where
353 S: Serializer,
354 {
355 let chain_id: u64 = self.0.into();
356 chain_id.serialize(serializer)
357 }
358}
359
360impl<'de> Deserialize<'de> for Chain {
361 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
362 where
363 D: Deserializer<'de>,
364 {
365 let chain_id = u64::deserialize(deserializer)?;
366 NamedChain::try_from(chain_id)
367 .map(Self)
368 .map_err(serde::de::Error::custom)
369 }
370}
371
372// Implement OdosChain trait by delegating to inner NamedChain
373impl OdosChain for Chain {
374 fn lo_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
375 self.0.lo_router_address()
376 }
377
378 fn v2_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
379 self.0.v2_router_address()
380 }
381
382 fn v3_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
383 self.0.v3_router_address()
384 }
385
386 fn supports_odos(&self) -> bool {
387 self.0.supports_odos()
388 }
389
390 fn supports_lo(&self) -> bool {
391 self.0.supports_lo()
392 }
393
394 fn supports_v2(&self) -> bool {
395 self.0.supports_v2()
396 }
397
398 fn supports_v3(&self) -> bool {
399 self.0.supports_v3()
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_chain_constructors() {
409 assert_eq!(Chain::ethereum().id(), 1);
410 assert_eq!(Chain::arbitrum().id(), 42161);
411 assert_eq!(Chain::optimism().id(), 10);
412 assert_eq!(Chain::polygon().id(), 137);
413 assert_eq!(Chain::base().id(), 8453);
414 assert_eq!(Chain::bsc().id(), 56);
415 assert_eq!(Chain::avalanche().id(), 43114);
416 assert_eq!(Chain::linea().id(), 59144);
417 assert_eq!(Chain::scroll().id(), 534352);
418 assert_eq!(Chain::zksync().id(), 324);
419 assert_eq!(Chain::mantle().id(), 5000);
420 assert_eq!(Chain::mode().id(), 34443);
421 assert_eq!(Chain::fraxtal().id(), 252);
422 assert_eq!(Chain::sonic().id(), 146);
423 assert_eq!(Chain::berachain().id(), 80094);
424 assert_eq!(Chain::unichain().id(), 130);
425 }
426
427 #[test]
428 fn test_from_chain_id() {
429 assert_eq!(Chain::from_chain_id(1).unwrap().id(), 1);
430 assert_eq!(Chain::from_chain_id(42161).unwrap().id(), 42161);
431 assert_eq!(Chain::from_chain_id(8453).unwrap().id(), 8453);
432
433 // Unsupported chain
434 assert!(Chain::from_chain_id(999999).is_err());
435 }
436
437 #[test]
438 fn test_inner() {
439 assert_eq!(Chain::ethereum().inner(), NamedChain::Mainnet);
440 assert_eq!(Chain::arbitrum().inner(), NamedChain::Arbitrum);
441 assert_eq!(Chain::base().inner(), NamedChain::Base);
442 }
443
444 #[test]
445 fn test_display() {
446 assert_eq!(format!("{}", Chain::ethereum()), "mainnet");
447 assert_eq!(format!("{}", Chain::arbitrum()), "arbitrum");
448 assert_eq!(format!("{}", Chain::base()), "base");
449 }
450
451 #[test]
452 fn test_conversions() {
453 // From NamedChain
454 let chain: Chain = NamedChain::Mainnet.into();
455 assert_eq!(chain.id(), 1);
456
457 // To NamedChain
458 let named: NamedChain = Chain::ethereum().into();
459 assert_eq!(named, NamedChain::Mainnet);
460
461 // To u64
462 let id: u64 = Chain::ethereum().into();
463 assert_eq!(id, 1);
464 }
465
466 #[test]
467 fn test_odos_chain_trait() {
468 let chain = Chain::ethereum();
469
470 // Test trait methods work
471 assert!(chain.supports_odos());
472 assert!(chain.supports_v2());
473 assert!(chain.supports_v3());
474 assert!(chain.v2_router_address().is_ok());
475 assert!(chain.v3_router_address().is_ok());
476 }
477
478 #[test]
479 fn test_equality() {
480 assert_eq!(Chain::ethereum(), Chain::ethereum());
481 assert_ne!(Chain::ethereum(), Chain::arbitrum());
482 }
483
484 #[test]
485 fn test_serialization() {
486 let chain = Chain::ethereum();
487
488 // Serialize (as chain ID)
489 let json = serde_json::to_string(&chain).unwrap();
490 assert_eq!(json, "1"); // Ethereum chain ID
491
492 // Deserialize
493 let deserialized: Chain = serde_json::from_str(&json).unwrap();
494 assert_eq!(deserialized, chain);
495
496 // Test other chains
497 assert_eq!(serde_json::to_string(&Chain::arbitrum()).unwrap(), "42161");
498 assert_eq!(serde_json::to_string(&Chain::base()).unwrap(), "8453");
499 }
500}