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    /// Unichain (Chain ID: 130)
235    ///
236    /// # Examples
237    ///
238    /// ```rust
239    /// use odos_sdk::Chain;
240    ///
241    /// let chain = Chain::unichain();
242    /// assert_eq!(chain.id(), 130);
243    /// ```
244    pub const fn unichain() -> Self {
245        Self(NamedChain::Unichain)
246    }
247
248    /// Create a chain from a chain ID
249    ///
250    /// # Arguments
251    ///
252    /// * `id` - The EVM chain ID
253    ///
254    /// # Returns
255    ///
256    /// * `Ok(Chain)` - If the chain ID is recognized
257    /// * `Err(OdosChainError)` - If the chain ID is not supported
258    ///
259    /// # Examples
260    ///
261    /// ```rust
262    /// use odos_sdk::Chain;
263    ///
264    /// let chain = Chain::from_chain_id(1)?;      // Ethereum
265    /// let chain = Chain::from_chain_id(42161)?;  // Arbitrum
266    /// let chain = Chain::from_chain_id(8453)?;   // Base
267    ///
268    /// // Unsupported chain
269    /// assert!(Chain::from_chain_id(999999).is_err());
270    /// # Ok::<(), Box<dyn std::error::Error>>(())
271    /// ```
272    pub fn from_chain_id(id: u64) -> OdosChainResult<Self> {
273        NamedChain::try_from(id)
274            .map(Self)
275            .map_err(|_| OdosChainError::UnsupportedChain {
276                chain: format!("Chain ID {id}"),
277            })
278    }
279
280    /// Get the chain ID
281    ///
282    /// # Examples
283    ///
284    /// ```rust
285    /// use odos_sdk::Chain;
286    ///
287    /// assert_eq!(Chain::ethereum().id(), 1);
288    /// assert_eq!(Chain::arbitrum().id(), 42161);
289    /// assert_eq!(Chain::base().id(), 8453);
290    /// ```
291    pub fn id(&self) -> u64 {
292        self.0.into()
293    }
294
295    /// Get the inner `NamedChain`
296    ///
297    /// # Examples
298    ///
299    /// ```rust
300    /// use odos_sdk::Chain;
301    /// use alloy_chains::NamedChain;
302    ///
303    /// let chain = Chain::ethereum();
304    /// assert_eq!(chain.inner(), NamedChain::Mainnet);
305    /// ```
306    pub const fn inner(&self) -> NamedChain {
307        self.0
308    }
309}
310
311impl fmt::Display for Chain {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        write!(f, "{}", self.0)
314    }
315}
316
317impl From<NamedChain> for Chain {
318    fn from(chain: NamedChain) -> Self {
319        Self(chain)
320    }
321}
322
323impl From<Chain> for NamedChain {
324    fn from(chain: Chain) -> Self {
325        chain.0
326    }
327}
328
329impl From<Chain> for u64 {
330    fn from(chain: Chain) -> Self {
331        chain.0.into()
332    }
333}
334
335// Custom serialization using chain ID
336impl Serialize for Chain {
337    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
338    where
339        S: Serializer,
340    {
341        let chain_id: u64 = self.0.into();
342        chain_id.serialize(serializer)
343    }
344}
345
346impl<'de> Deserialize<'de> for Chain {
347    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
348    where
349        D: Deserializer<'de>,
350    {
351        let chain_id = u64::deserialize(deserializer)?;
352        NamedChain::try_from(chain_id)
353            .map(Self)
354            .map_err(serde::de::Error::custom)
355    }
356}
357
358// Implement OdosChain trait by delegating to inner NamedChain
359impl OdosChain for Chain {
360    fn lo_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
361        self.0.lo_router_address()
362    }
363
364    fn v2_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
365        self.0.v2_router_address()
366    }
367
368    fn v3_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
369        self.0.v3_router_address()
370    }
371
372    fn supports_odos(&self) -> bool {
373        self.0.supports_odos()
374    }
375
376    fn supports_lo(&self) -> bool {
377        self.0.supports_lo()
378    }
379
380    fn supports_v2(&self) -> bool {
381        self.0.supports_v2()
382    }
383
384    fn supports_v3(&self) -> bool {
385        self.0.supports_v3()
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn test_chain_constructors() {
395        assert_eq!(Chain::ethereum().id(), 1);
396        assert_eq!(Chain::arbitrum().id(), 42161);
397        assert_eq!(Chain::optimism().id(), 10);
398        assert_eq!(Chain::polygon().id(), 137);
399        assert_eq!(Chain::base().id(), 8453);
400        assert_eq!(Chain::bsc().id(), 56);
401        assert_eq!(Chain::avalanche().id(), 43114);
402        assert_eq!(Chain::linea().id(), 59144);
403        assert_eq!(Chain::scroll().id(), 534352);
404        assert_eq!(Chain::zksync().id(), 324);
405        assert_eq!(Chain::mantle().id(), 5000);
406        assert_eq!(Chain::mode().id(), 34443);
407        assert_eq!(Chain::fraxtal().id(), 252);
408        assert_eq!(Chain::sonic().id(), 146);
409        assert_eq!(Chain::unichain().id(), 130);
410    }
411
412    #[test]
413    fn test_from_chain_id() {
414        assert_eq!(Chain::from_chain_id(1).unwrap().id(), 1);
415        assert_eq!(Chain::from_chain_id(42161).unwrap().id(), 42161);
416        assert_eq!(Chain::from_chain_id(8453).unwrap().id(), 8453);
417
418        // Unsupported chain
419        assert!(Chain::from_chain_id(999999).is_err());
420    }
421
422    #[test]
423    fn test_inner() {
424        assert_eq!(Chain::ethereum().inner(), NamedChain::Mainnet);
425        assert_eq!(Chain::arbitrum().inner(), NamedChain::Arbitrum);
426        assert_eq!(Chain::base().inner(), NamedChain::Base);
427    }
428
429    #[test]
430    fn test_display() {
431        assert_eq!(format!("{}", Chain::ethereum()), "mainnet");
432        assert_eq!(format!("{}", Chain::arbitrum()), "arbitrum");
433        assert_eq!(format!("{}", Chain::base()), "base");
434    }
435
436    #[test]
437    fn test_conversions() {
438        // From NamedChain
439        let chain: Chain = NamedChain::Mainnet.into();
440        assert_eq!(chain.id(), 1);
441
442        // To NamedChain
443        let named: NamedChain = Chain::ethereum().into();
444        assert_eq!(named, NamedChain::Mainnet);
445
446        // To u64
447        let id: u64 = Chain::ethereum().into();
448        assert_eq!(id, 1);
449    }
450
451    #[test]
452    fn test_odos_chain_trait() {
453        let chain = Chain::ethereum();
454
455        // Test trait methods work
456        assert!(chain.supports_odos());
457        assert!(chain.supports_v2());
458        assert!(chain.supports_v3());
459        assert!(chain.v2_router_address().is_ok());
460        assert!(chain.v3_router_address().is_ok());
461    }
462
463    #[test]
464    fn test_equality() {
465        assert_eq!(Chain::ethereum(), Chain::ethereum());
466        assert_ne!(Chain::ethereum(), Chain::arbitrum());
467    }
468
469    #[test]
470    fn test_serialization() {
471        let chain = Chain::ethereum();
472
473        // Serialize (as chain ID)
474        let json = serde_json::to_string(&chain).unwrap();
475        assert_eq!(json, "1"); // Ethereum chain ID
476
477        // Deserialize
478        let deserialized: Chain = serde_json::from_str(&json).unwrap();
479        assert_eq!(deserialized, chain);
480
481        // Test other chains
482        assert_eq!(serde_json::to_string(&Chain::arbitrum()).unwrap(), "42161");
483        assert_eq!(serde_json::to_string(&Chain::base()).unwrap(), "8453");
484    }
485}