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}