odos_sdk/
chain.rs

1use alloy_chains::NamedChain;
2use alloy_primitives::Address;
3use thiserror::Error;
4
5use crate::{
6    RouterAvailability, ODOS_LO_ARBITRUM_ROUTER, ODOS_LO_AVALANCHE_ROUTER, ODOS_LO_BASE_ROUTER,
7    ODOS_LO_BERACHAIN_ROUTER, ODOS_LO_BSC_ROUTER, ODOS_LO_ETHEREUM_ROUTER, ODOS_LO_FRAXTAL_ROUTER,
8    ODOS_LO_LINEA_ROUTER, ODOS_LO_MANTLE_ROUTER, ODOS_LO_MODE_ROUTER, ODOS_LO_OP_ROUTER,
9    ODOS_LO_POLYGON_ROUTER, ODOS_LO_SCROLL_ROUTER, ODOS_LO_SONIC_ROUTER, ODOS_LO_UNICHAIN_ROUTER,
10    ODOS_LO_ZKSYNC_ROUTER, ODOS_V2_ARBITRUM_ROUTER, ODOS_V2_AVALANCHE_ROUTER, ODOS_V2_BASE_ROUTER,
11    ODOS_V2_BSC_ROUTER, ODOS_V2_ETHEREUM_ROUTER, ODOS_V2_FRAXTAL_ROUTER, ODOS_V2_LINEA_ROUTER,
12    ODOS_V2_MANTLE_ROUTER, ODOS_V2_MODE_ROUTER, ODOS_V2_OP_ROUTER, ODOS_V2_POLYGON_ROUTER,
13    ODOS_V2_SCROLL_ROUTER, ODOS_V2_SONIC_ROUTER, ODOS_V2_UNICHAIN_ROUTER, ODOS_V2_ZKSYNC_ROUTER,
14    ODOS_V3,
15};
16
17/// Errors that can occur when working with Odos chains
18#[derive(Error, Debug, Clone, PartialEq)]
19pub enum OdosChainError {
20    /// The chain is not supported by Odos protocol
21    #[error("Chain {chain:?} is not supported by Odos protocol")]
22    UnsupportedChain { chain: String },
23
24    /// The Limit Order router is not available on this chain
25    #[error("Odos Limit Order router is not available on chain {chain:?}")]
26    LimitOrderNotAvailable { chain: String },
27
28    /// The V2 router is not available on this chain
29    #[error("Odos V2 router is not available on chain {chain:?}")]
30    V2NotAvailable { chain: String },
31
32    /// The V3 router is not available on this chain
33    #[error("Odos V3 router is not available on chain {chain:?}")]
34    V3NotAvailable { chain: String },
35
36    /// Invalid address format
37    #[error("Invalid address format: {address}")]
38    InvalidAddress { address: String },
39}
40
41/// Result type for Odos chain operations
42pub type OdosChainResult<T> = Result<T, OdosChainError>;
43
44/// Trait for chains that support Odos protocol
45///
46/// This trait provides a type-safe way to access Odos router addresses
47/// for supported blockchain networks, integrating seamlessly with the
48/// Alloy ecosystem.
49///
50/// # Examples
51///
52/// ```rust
53/// use odos_sdk::OdosChain;
54/// use alloy_chains::NamedChain;
55///
56/// // Get router addresses
57/// let lo_router = NamedChain::Mainnet.lo_router_address()?;
58/// let v2_router = NamedChain::Mainnet.v2_router_address()?;
59/// let v3_router = NamedChain::Mainnet.v3_router_address()?;
60///
61/// // Check router support
62/// assert!(NamedChain::Mainnet.supports_odos());
63/// assert!(NamedChain::Mainnet.supports_lo());
64/// assert!(NamedChain::Mainnet.supports_v2());
65/// assert!(NamedChain::Mainnet.supports_v3());
66///
67/// // Get router availability
68/// let availability = NamedChain::Mainnet.router_availability();
69/// assert!(availability.limit_order && availability.v2 && availability.v3);
70/// # Ok::<(), odos_sdk::OdosChainError>(())
71/// ```
72pub trait OdosChain {
73    /// Get the Limit Order V2 router address for this chain
74    ///
75    /// # Returns
76    ///
77    /// * `Ok(Address)` - The LO router contract address
78    /// * `Err(OdosChainError)` - If the chain doesn't support LO or address is invalid
79    ///
80    /// # Example
81    ///
82    /// ```rust
83    /// use odos_sdk::OdosChain;
84    /// use alloy_chains::NamedChain;
85    ///
86    /// let address = NamedChain::Mainnet.lo_router_address()?;
87    /// # Ok::<(), odos_sdk::OdosChainError>(())
88    /// ```
89    fn lo_router_address(&self) -> OdosChainResult<Address>;
90    /// Get the V2 router address for this chain
91    ///
92    /// # Returns
93    ///
94    /// * `Ok(Address)` - The V2 router contract address
95    /// * `Err(OdosChainError)` - If the chain is not supported or address is invalid
96    ///
97    /// # Example
98    ///
99    /// ```rust
100    /// use odos_sdk::OdosChain;
101    /// use alloy_chains::NamedChain;
102    ///
103    /// let address = NamedChain::Mainnet.v2_router_address()?;
104    /// # Ok::<(), odos_sdk::OdosChainError>(())
105    /// ```
106    fn v2_router_address(&self) -> OdosChainResult<Address>;
107
108    /// Get the V3 router address for this chain
109    ///
110    /// V3 uses the same address across all supported chains,
111    /// following CREATE2 deterministic deployment.
112    ///
113    /// # Returns
114    ///
115    /// * `Ok(Address)` - The V3 router contract address
116    /// * `Err(OdosChainError)` - If the chain is not supported or address is invalid
117    ///
118    /// # Example
119    ///
120    /// ```rust
121    /// use odos_sdk::OdosChain;
122    /// use alloy_chains::NamedChain;
123    ///
124    /// let address = NamedChain::Mainnet.v3_router_address()?;
125    /// # Ok::<(), odos_sdk::OdosChainError>(())
126    /// ```
127    fn v3_router_address(&self) -> OdosChainResult<Address>;
128
129    /// Check if this chain supports Odos protocol
130    ///
131    /// # Returns
132    ///
133    /// `true` if any router (LO, V2, or V3) is supported on this chain
134    fn supports_odos(&self) -> bool;
135
136    /// Check if this chain supports Odos Limit Order
137    ///
138    /// # Returns
139    ///
140    /// `true` if LO is supported on this chain
141    fn supports_lo(&self) -> bool;
142
143    /// Check if this chain supports Odos V2
144    ///
145    /// # Returns
146    ///
147    /// `true` if V2 is supported on this chain
148    fn supports_v2(&self) -> bool;
149
150    /// Check if this chain supports Odos V3
151    ///
152    /// # Returns
153    ///
154    /// `true` if V3 is supported on this chain
155    fn supports_v3(&self) -> bool;
156
157    /// Get router availability for this chain
158    ///
159    /// # Returns
160    ///
161    /// A `RouterAvailability` struct indicating which routers are available
162    ///
163    /// # Example
164    ///
165    /// ```rust
166    /// use odos_sdk::OdosChain;
167    /// use alloy_chains::NamedChain;
168    ///
169    /// let availability = NamedChain::Mainnet.router_availability();
170    /// assert!(availability.limit_order);
171    /// assert!(availability.v2);
172    /// assert!(availability.v3);
173    /// ```
174    fn router_availability(&self) -> RouterAvailability {
175        RouterAvailability {
176            limit_order: self.supports_lo(),
177            v2: self.supports_v2(),
178            v3: self.supports_v3(),
179        }
180    }
181
182    /// Try to get the LO router address without errors
183    ///
184    /// # Returns
185    ///
186    /// `Some(address)` if supported, `None` if not supported
187    fn try_lo_router_address(&self) -> Option<Address> {
188        self.lo_router_address().ok()
189    }
190
191    /// Try to get the V2 router address without errors
192    ///
193    /// # Returns
194    ///
195    /// `Some(address)` if supported, `None` if not supported
196    fn try_v2_router_address(&self) -> Option<Address> {
197        self.v2_router_address().ok()
198    }
199
200    /// Try to get the V3 router address without errors
201    ///
202    /// # Returns
203    ///
204    /// `Some(address)` if supported, `None` if not supported
205    fn try_v3_router_address(&self) -> Option<Address> {
206        self.v3_router_address().ok()
207    }
208}
209
210impl OdosChain for NamedChain {
211    fn lo_router_address(&self) -> OdosChainResult<Address> {
212        use NamedChain::*;
213
214        if !self.supports_odos() {
215            return Err(OdosChainError::LimitOrderNotAvailable {
216                chain: format!("{self:?}"),
217            });
218        }
219
220        if !self.supports_lo() {
221            return Err(OdosChainError::LimitOrderNotAvailable {
222                chain: format!("{self:?}"),
223            });
224        }
225
226        Ok(match self {
227            Arbitrum => ODOS_LO_ARBITRUM_ROUTER,
228            Avalanche => ODOS_LO_AVALANCHE_ROUTER,
229            Base => ODOS_LO_BASE_ROUTER,
230            Berachain => ODOS_LO_BERACHAIN_ROUTER,
231            BinanceSmartChain => ODOS_LO_BSC_ROUTER,
232            Fraxtal => ODOS_LO_FRAXTAL_ROUTER,
233            Mainnet => ODOS_LO_ETHEREUM_ROUTER,
234            Optimism => ODOS_LO_OP_ROUTER,
235            Polygon => ODOS_LO_POLYGON_ROUTER,
236            Linea => ODOS_LO_LINEA_ROUTER,
237            Mantle => ODOS_LO_MANTLE_ROUTER,
238            Mode => ODOS_LO_MODE_ROUTER,
239            Scroll => ODOS_LO_SCROLL_ROUTER,
240            Sonic => ODOS_LO_SONIC_ROUTER,
241            ZkSync => ODOS_LO_ZKSYNC_ROUTER,
242            Unichain => ODOS_LO_UNICHAIN_ROUTER,
243            _ => {
244                return Err(OdosChainError::LimitOrderNotAvailable {
245                    chain: format!("{self:?}"),
246                });
247            }
248        })
249    }
250
251    fn v2_router_address(&self) -> OdosChainResult<Address> {
252        use NamedChain::*;
253
254        if !self.supports_odos() {
255            return Err(OdosChainError::V2NotAvailable {
256                chain: format!("{self:?}"),
257            });
258        }
259
260        // If V2 is not available on this chain, fall back to V3
261        if !self.supports_v2() {
262            return self.v3_router_address();
263        }
264
265        Ok(match self {
266            Arbitrum => ODOS_V2_ARBITRUM_ROUTER,
267            Avalanche => ODOS_V2_AVALANCHE_ROUTER,
268            Base => ODOS_V2_BASE_ROUTER,
269            BinanceSmartChain => ODOS_V2_BSC_ROUTER,
270            Fraxtal => ODOS_V2_FRAXTAL_ROUTER,
271            Mainnet => ODOS_V2_ETHEREUM_ROUTER,
272            Optimism => ODOS_V2_OP_ROUTER,
273            Polygon => ODOS_V2_POLYGON_ROUTER,
274            Linea => ODOS_V2_LINEA_ROUTER,
275            Mantle => ODOS_V2_MANTLE_ROUTER,
276            Mode => ODOS_V2_MODE_ROUTER,
277            Scroll => ODOS_V2_SCROLL_ROUTER,
278            Sonic => ODOS_V2_SONIC_ROUTER,
279            ZkSync => ODOS_V2_ZKSYNC_ROUTER,
280            Unichain => ODOS_V2_UNICHAIN_ROUTER,
281            _ => {
282                return Err(OdosChainError::UnsupportedChain {
283                    chain: format!("{self:?}"),
284                });
285            }
286        })
287    }
288
289    fn v3_router_address(&self) -> OdosChainResult<Address> {
290        if !self.supports_odos() {
291            return Err(OdosChainError::V3NotAvailable {
292                chain: format!("{self:?}"),
293            });
294        }
295
296        // If V3 is not available on this chain, fall back to V2
297        if !self.supports_v3() {
298            return self.v2_router_address();
299        }
300
301        Ok(ODOS_V3)
302    }
303
304    fn supports_odos(&self) -> bool {
305        use NamedChain::*;
306        matches!(
307            self,
308            Arbitrum
309                | Avalanche
310                | Base
311                | Berachain
312                | BinanceSmartChain
313                | Fraxtal
314                | Mainnet
315                | Optimism
316                | Polygon
317                | Linea
318                | Mantle
319                | Mode
320                | Scroll
321                | Sonic
322                | ZkSync
323                | Unichain
324        )
325    }
326
327    fn supports_lo(&self) -> bool {
328        use NamedChain::*;
329        matches!(
330            self,
331            Arbitrum
332                | Avalanche
333                | Base
334                | Berachain
335                | BinanceSmartChain
336                | Fraxtal
337                | Mainnet
338                | Optimism
339                | Polygon
340                | Linea
341                | Mantle
342                | Mode
343                | Scroll
344                | Sonic
345                | ZkSync
346                | Unichain
347        )
348    }
349
350    fn supports_v2(&self) -> bool {
351        use NamedChain::*;
352        matches!(
353            self,
354            Arbitrum
355                | Avalanche
356                | Base
357                | BinanceSmartChain
358                | Fraxtal
359                | Mainnet
360                | Optimism
361                | Polygon
362                | Linea
363                | Mantle
364                | Mode
365                | Scroll
366                | Sonic
367                | ZkSync
368                | Unichain
369        )
370    }
371
372    fn supports_v3(&self) -> bool {
373        use NamedChain::*;
374        matches!(
375            self,
376            Arbitrum
377                | Avalanche
378                | Base
379                | Berachain
380                | BinanceSmartChain
381                | Fraxtal
382                | Mainnet
383                | Optimism
384                | Polygon
385                | Linea
386                | Mantle
387                | Mode
388                | Scroll
389                | Sonic
390                | ZkSync
391                | Unichain
392        )
393    }
394}
395
396/// Extension trait for easy router selection
397///
398/// This trait provides convenient methods for choosing between V2 and V3
399/// routers based on your requirements.
400pub trait OdosRouterSelection: OdosChain {
401    /// Get the recommended router address for this chain
402    ///
403    /// Currently defaults to V3 for enhanced features, but this
404    /// may change based on performance characteristics.
405    ///
406    /// # Returns
407    ///
408    /// * `Ok(Address)` - The recommended router address
409    /// * `Err(OdosChainError)` - If the chain is not supported
410    ///
411    /// # Example
412    ///
413    /// ```rust
414    /// use odos_sdk::{OdosChain, OdosRouterSelection};
415    /// use alloy_chains::NamedChain;
416    ///
417    /// let address = NamedChain::Base.recommended_router_address()?;
418    /// # Ok::<(), odos_sdk::OdosChainError>(())
419    /// ```
420    fn recommended_router_address(&self) -> OdosChainResult<Address> {
421        self.v3_router_address()
422    }
423
424    /// Get router address with fallback strategy
425    ///
426    /// Tries V3 first, falls back to V2 if needed.
427    /// This is useful for maximum compatibility.
428    ///
429    /// # Returns
430    ///
431    /// * `Ok(Address)` - V3 address if available, otherwise V2 address
432    /// * `Err(OdosChainError)` - If neither version is supported
433    ///
434    /// # Example
435    ///
436    /// ```rust
437    /// use odos_sdk::{OdosChain, OdosRouterSelection};
438    /// use alloy_chains::NamedChain;
439    ///
440    /// let address = NamedChain::Mainnet.router_address_with_fallback()?;
441    /// # Ok::<(), odos_sdk::OdosChainError>(())
442    /// ```
443    fn router_address_with_fallback(&self) -> OdosChainResult<Address> {
444        self.v3_router_address()
445            .or_else(|_| self.v2_router_address())
446    }
447
448    /// Get router address based on preference
449    ///
450    /// # Arguments
451    ///
452    /// * `prefer_v3` - Whether to prefer V3 when both are available
453    ///
454    /// # Returns
455    ///
456    /// * `Ok(Address)` - The appropriate router address based on preference
457    /// * `Err(OdosChainError)` - If the preferred version is not supported
458    ///
459    /// # Example
460    ///
461    /// ```rust
462    /// use odos_sdk::{OdosChain, OdosRouterSelection};
463    /// use alloy_chains::NamedChain;
464    ///
465    /// let v3_address = NamedChain::Mainnet.router_address_by_preference(true)?;
466    /// let v2_address = NamedChain::Mainnet.router_address_by_preference(false)?;
467    /// # Ok::<(), odos_sdk::OdosChainError>(())
468    /// ```
469    fn router_address_by_preference(&self, prefer_v3: bool) -> OdosChainResult<Address> {
470        if prefer_v3 {
471            self.v3_router_address()
472        } else {
473            self.v2_router_address()
474        }
475    }
476}
477
478impl<T: OdosChain> OdosRouterSelection for T {}
479
480#[cfg(test)]
481mod tests {
482    use super::*;
483    use alloy_chains::NamedChain;
484
485    #[test]
486    fn test_lo_router_addresses() {
487        let chains = [
488            NamedChain::Mainnet,
489            NamedChain::Optimism,
490            NamedChain::Polygon,
491            NamedChain::BinanceSmartChain,
492            NamedChain::Berachain,
493        ];
494
495        for chain in chains {
496            let address = chain.lo_router_address().unwrap();
497            assert!(address != Address::ZERO);
498            assert_eq!(address.to_string().len(), 42); // 0x + 40 hex chars
499        }
500    }
501
502    #[test]
503    fn test_v2_router_addresses() {
504        let chains = [
505            NamedChain::Mainnet,
506            NamedChain::Arbitrum,
507            NamedChain::Optimism,
508            NamedChain::Polygon,
509            NamedChain::Base,
510        ];
511
512        for chain in chains {
513            let address = chain.v2_router_address().unwrap();
514            assert!(address != Address::ZERO);
515            assert_eq!(address.to_string().len(), 42); // 0x + 40 hex chars
516        }
517    }
518
519    #[test]
520    fn test_v3_router_addresses() {
521        let chains = [
522            NamedChain::Mainnet,
523            NamedChain::Arbitrum,
524            NamedChain::Optimism,
525            NamedChain::Polygon,
526            NamedChain::Base,
527        ];
528
529        for chain in chains {
530            let address = chain.v3_router_address().unwrap();
531            assert_eq!(address, ODOS_V3);
532        }
533    }
534
535    #[test]
536    fn test_supports_odos() {
537        assert!(NamedChain::Mainnet.supports_odos());
538        assert!(NamedChain::Arbitrum.supports_odos());
539        assert!(NamedChain::Berachain.supports_odos());
540        assert!(!NamedChain::Sepolia.supports_odos());
541    }
542
543    #[test]
544    fn test_supports_lo() {
545        assert!(NamedChain::Mainnet.supports_lo());
546        assert!(NamedChain::Optimism.supports_lo());
547        assert!(NamedChain::Polygon.supports_lo());
548        assert!(NamedChain::BinanceSmartChain.supports_lo());
549        assert!(NamedChain::Berachain.supports_lo());
550        assert!(NamedChain::Arbitrum.supports_lo());
551        assert!(NamedChain::Base.supports_lo());
552        assert!(!NamedChain::Sepolia.supports_lo());
553    }
554
555    #[test]
556    fn test_supports_v2() {
557        assert!(NamedChain::Mainnet.supports_v2());
558        assert!(NamedChain::Arbitrum.supports_v2());
559        assert!(!NamedChain::Berachain.supports_v2()); // Berachain only has LO + V3
560        assert!(!NamedChain::Sepolia.supports_v2());
561    }
562
563    #[test]
564    fn test_supports_v3() {
565        assert!(NamedChain::Mainnet.supports_v3());
566        assert!(NamedChain::Arbitrum.supports_v3());
567        assert!(NamedChain::Berachain.supports_v3());
568        assert!(!NamedChain::Sepolia.supports_v3());
569    }
570
571    #[test]
572    fn test_berachain_lo_v3_only() {
573        // Berachain has LO + V3, but not V2
574        assert!(NamedChain::Berachain.supports_lo());
575        assert!(!NamedChain::Berachain.supports_v2());
576        assert!(NamedChain::Berachain.supports_v3());
577
578        // LO router should work
579        let lo_result = NamedChain::Berachain.lo_router_address();
580        assert!(lo_result.is_ok());
581        assert_eq!(lo_result.unwrap(), ODOS_LO_BERACHAIN_ROUTER);
582
583        // Requesting V2 should fallback to V3
584        let v2_result = NamedChain::Berachain.v2_router_address();
585        let v3_result = NamedChain::Berachain.v3_router_address();
586
587        assert!(v2_result.is_ok());
588        assert!(v3_result.is_ok());
589        assert_eq!(v2_result.unwrap(), v3_result.unwrap());
590    }
591
592    #[test]
593    fn test_router_availability() {
594        // Ethereum: all routers
595        let avail = NamedChain::Mainnet.router_availability();
596        assert!(avail.limit_order);
597        assert!(avail.v2);
598        assert!(avail.v3);
599        assert_eq!(avail.count(), 3);
600
601        // Berachain: LO + V3 only
602        let avail = NamedChain::Berachain.router_availability();
603        assert!(avail.limit_order);
604        assert!(!avail.v2);
605        assert!(avail.v3);
606        assert_eq!(avail.count(), 2);
607
608        // Arbitrum: all routers
609        let avail = NamedChain::Arbitrum.router_availability();
610        assert!(avail.limit_order);
611        assert!(avail.v2);
612        assert!(avail.v3);
613        assert_eq!(avail.count(), 3);
614
615        // Sepolia: none
616        let avail = NamedChain::Sepolia.router_availability();
617        assert!(!avail.limit_order);
618        assert!(!avail.v2);
619        assert!(!avail.v3);
620        assert_eq!(avail.count(), 0);
621        assert!(!avail.has_any());
622    }
623
624    #[test]
625    fn test_try_methods() {
626        assert!(NamedChain::Mainnet.try_lo_router_address().is_some());
627        assert!(NamedChain::Mainnet.try_v2_router_address().is_some());
628        assert!(NamedChain::Mainnet.try_v3_router_address().is_some());
629
630        assert!(NamedChain::Sepolia.try_lo_router_address().is_none());
631        assert!(NamedChain::Sepolia.try_v2_router_address().is_none());
632        assert!(NamedChain::Sepolia.try_v3_router_address().is_none());
633
634        // Arbitrum has all routers
635        assert!(NamedChain::Arbitrum.try_lo_router_address().is_some());
636        assert!(NamedChain::Arbitrum.try_v2_router_address().is_some());
637        assert!(NamedChain::Arbitrum.try_v3_router_address().is_some());
638    }
639
640    #[test]
641    fn test_router_selection() {
642        let chain = NamedChain::Mainnet;
643
644        // Recommended should be V3
645        assert_eq!(
646            chain.recommended_router_address().unwrap(),
647            chain.v3_router_address().unwrap()
648        );
649
650        // Fallback should also be V3 (since both are supported)
651        assert_eq!(
652            chain.router_address_with_fallback().unwrap(),
653            chain.v3_router_address().unwrap()
654        );
655
656        // Preference-based selection
657        assert_eq!(
658            chain.router_address_by_preference(true).unwrap(),
659            chain.v3_router_address().unwrap()
660        );
661        assert_eq!(
662            chain.router_address_by_preference(false).unwrap(),
663            chain.v2_router_address().unwrap()
664        );
665    }
666
667    #[test]
668    fn test_error_handling() {
669        // Test unsupported chain
670        let result = NamedChain::Sepolia.lo_router_address();
671        assert!(result.is_err());
672        assert!(matches!(
673            result.unwrap_err(),
674            OdosChainError::LimitOrderNotAvailable { .. }
675        ));
676
677        let result = NamedChain::Sepolia.v2_router_address();
678        assert!(result.is_err());
679        assert!(matches!(
680            result.unwrap_err(),
681            OdosChainError::V2NotAvailable { .. }
682        ));
683
684        let result = NamedChain::Sepolia.v3_router_address();
685        assert!(result.is_err());
686        assert!(matches!(
687            result.unwrap_err(),
688            OdosChainError::V3NotAvailable { .. }
689        ));
690    }
691}