1use alloy_chains::NamedChain;
6use alloy_primitives::Address;
7use thiserror::Error;
8
9use crate::{
10 RouterAvailability, ODOS_LO_ARBITRUM_ROUTER, ODOS_LO_AVALANCHE_ROUTER, ODOS_LO_BASE_ROUTER,
11 ODOS_LO_BSC_ROUTER, ODOS_LO_ETHEREUM_ROUTER, ODOS_LO_FRAXTAL_ROUTER, ODOS_LO_LINEA_ROUTER,
12 ODOS_LO_MANTLE_ROUTER, ODOS_LO_OP_ROUTER, ODOS_LO_POLYGON_ROUTER, ODOS_LO_SCROLL_ROUTER,
13 ODOS_LO_SONIC_ROUTER, ODOS_LO_UNICHAIN_ROUTER, ODOS_LO_ZKSYNC_ROUTER, ODOS_V2_ARBITRUM_ROUTER,
14 ODOS_V2_AVALANCHE_ROUTER, ODOS_V2_BASE_ROUTER, ODOS_V2_BSC_ROUTER, ODOS_V2_ETHEREUM_ROUTER,
15 ODOS_V2_FRAXTAL_ROUTER, ODOS_V2_LINEA_ROUTER, ODOS_V2_MANTLE_ROUTER, ODOS_V2_OP_ROUTER,
16 ODOS_V2_POLYGON_ROUTER, ODOS_V2_SCROLL_ROUTER, ODOS_V2_SONIC_ROUTER, ODOS_V2_UNICHAIN_ROUTER,
17 ODOS_V2_ZKSYNC_ROUTER, ODOS_V3,
18};
19
20#[derive(Error, Debug, Clone, PartialEq)]
22pub enum OdosChainError {
23 #[error("Chain {chain:?} is not supported by Odos protocol")]
25 UnsupportedChain { chain: String },
26
27 #[error("Odos Limit Order router is not available on chain {chain:?}")]
29 LimitOrderNotAvailable { chain: String },
30
31 #[error("Odos V2 router is not available on chain {chain:?}")]
33 V2NotAvailable { chain: String },
34
35 #[error("Odos V3 router is not available on chain {chain:?}")]
37 V3NotAvailable { chain: String },
38
39 #[error("Invalid address format: {address}")]
41 InvalidAddress { address: String },
42}
43
44pub type OdosChainResult<T> = Result<T, OdosChainError>;
46
47pub trait OdosChain {
76 fn lo_router_address(&self) -> OdosChainResult<Address>;
93 fn v2_router_address(&self) -> OdosChainResult<Address>;
110
111 fn v3_router_address(&self) -> OdosChainResult<Address>;
131
132 fn supports_odos(&self) -> bool;
138
139 fn supports_lo(&self) -> bool;
145
146 fn supports_v2(&self) -> bool;
152
153 fn supports_v3(&self) -> bool;
159
160 fn router_availability(&self) -> RouterAvailability {
178 RouterAvailability {
179 limit_order: self.supports_lo(),
180 v2: self.supports_v2(),
181 v3: self.supports_v3(),
182 }
183 }
184
185 fn try_lo_router_address(&self) -> Option<Address> {
191 self.lo_router_address().ok()
192 }
193
194 fn try_v2_router_address(&self) -> Option<Address> {
200 self.v2_router_address().ok()
201 }
202
203 fn try_v3_router_address(&self) -> Option<Address> {
209 self.v3_router_address().ok()
210 }
211}
212
213impl OdosChain for NamedChain {
214 fn lo_router_address(&self) -> OdosChainResult<Address> {
215 use NamedChain::*;
216
217 if !self.supports_odos() {
218 return Err(OdosChainError::LimitOrderNotAvailable {
219 chain: format!("{self:?}"),
220 });
221 }
222
223 if !self.supports_lo() {
224 return Err(OdosChainError::LimitOrderNotAvailable {
225 chain: format!("{self:?}"),
226 });
227 }
228
229 Ok(match self {
230 Arbitrum => ODOS_LO_ARBITRUM_ROUTER,
231 Avalanche => ODOS_LO_AVALANCHE_ROUTER,
232 Base => ODOS_LO_BASE_ROUTER,
233 BinanceSmartChain => ODOS_LO_BSC_ROUTER,
234 Fraxtal => ODOS_LO_FRAXTAL_ROUTER,
235 Mainnet => ODOS_LO_ETHEREUM_ROUTER,
236 Optimism => ODOS_LO_OP_ROUTER,
237 Polygon => ODOS_LO_POLYGON_ROUTER,
238 Linea => ODOS_LO_LINEA_ROUTER,
239 Mantle => ODOS_LO_MANTLE_ROUTER,
240 Scroll => ODOS_LO_SCROLL_ROUTER,
241 Sonic => ODOS_LO_SONIC_ROUTER,
242 ZkSync => ODOS_LO_ZKSYNC_ROUTER,
243 Unichain => ODOS_LO_UNICHAIN_ROUTER,
244 _ => {
245 return Err(OdosChainError::LimitOrderNotAvailable {
246 chain: format!("{self:?}"),
247 });
248 }
249 })
250 }
251
252 fn v2_router_address(&self) -> OdosChainResult<Address> {
253 use NamedChain::*;
254
255 if !self.supports_odos() {
256 return Err(OdosChainError::V2NotAvailable {
257 chain: format!("{self:?}"),
258 });
259 }
260
261 if !self.supports_v2() {
263 return self.v3_router_address();
264 }
265
266 Ok(match self {
267 Arbitrum => ODOS_V2_ARBITRUM_ROUTER,
268 Avalanche => ODOS_V2_AVALANCHE_ROUTER,
269 Base => ODOS_V2_BASE_ROUTER,
270 BinanceSmartChain => ODOS_V2_BSC_ROUTER,
271 Fraxtal => ODOS_V2_FRAXTAL_ROUTER,
272 Mainnet => ODOS_V2_ETHEREUM_ROUTER,
273 Optimism => ODOS_V2_OP_ROUTER,
274 Polygon => ODOS_V2_POLYGON_ROUTER,
275 Linea => ODOS_V2_LINEA_ROUTER,
276 Mantle => ODOS_V2_MANTLE_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 !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 | BinanceSmartChain
312 | Fraxtal
313 | Mainnet
314 | Optimism
315 | Polygon
316 | Linea
317 | Mantle
318 | Scroll
319 | Sonic
320 | ZkSync
321 | Unichain
322 )
323 }
324
325 fn supports_lo(&self) -> bool {
326 use NamedChain::*;
327 matches!(
328 self,
329 Arbitrum
330 | Avalanche
331 | Base
332 | BinanceSmartChain
333 | Fraxtal
334 | Mainnet
335 | Optimism
336 | Polygon
337 | Linea
338 | Mantle
339 | Scroll
340 | Sonic
341 | ZkSync
342 | Unichain
343 )
344 }
345
346 fn supports_v2(&self) -> bool {
347 use NamedChain::*;
348 matches!(
349 self,
350 Arbitrum
351 | Avalanche
352 | Base
353 | BinanceSmartChain
354 | Fraxtal
355 | Mainnet
356 | Optimism
357 | Polygon
358 | Linea
359 | Mantle
360 | Scroll
361 | Sonic
362 | ZkSync
363 | Unichain
364 )
365 }
366
367 fn supports_v3(&self) -> bool {
368 use NamedChain::*;
369 matches!(
370 self,
371 Arbitrum
372 | Avalanche
373 | Base
374 | BinanceSmartChain
375 | Fraxtal
376 | Mainnet
377 | Optimism
378 | Polygon
379 | Linea
380 | Mantle
381 | Scroll
382 | Sonic
383 | ZkSync
384 | Unichain
385 )
386 }
387}
388
389pub trait OdosRouterSelection: OdosChain {
394 fn recommended_router_address(&self) -> OdosChainResult<Address> {
414 self.v3_router_address()
415 }
416
417 fn router_address_with_fallback(&self) -> OdosChainResult<Address> {
437 self.v3_router_address()
438 .or_else(|_| self.v2_router_address())
439 }
440
441 fn router_address_by_preference(&self, prefer_v3: bool) -> OdosChainResult<Address> {
463 if prefer_v3 {
464 self.v3_router_address()
465 } else {
466 self.v2_router_address()
467 }
468 }
469}
470
471impl<T: OdosChain> OdosRouterSelection for T {}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476 use alloy_chains::NamedChain;
477
478 #[test]
479 fn test_lo_router_addresses() {
480 let chains = [
481 NamedChain::Mainnet,
482 NamedChain::Optimism,
483 NamedChain::Polygon,
484 NamedChain::BinanceSmartChain,
485 ];
486
487 for chain in chains {
488 let address = chain.lo_router_address().unwrap();
489 assert!(address != Address::ZERO);
490 assert_eq!(address.to_string().len(), 42); }
492 }
493
494 #[test]
495 fn test_v2_router_addresses() {
496 let chains = [
497 NamedChain::Mainnet,
498 NamedChain::Arbitrum,
499 NamedChain::Optimism,
500 NamedChain::Polygon,
501 NamedChain::Base,
502 ];
503
504 for chain in chains {
505 let address = chain.v2_router_address().unwrap();
506 assert!(address != Address::ZERO);
507 assert_eq!(address.to_string().len(), 42); }
509 }
510
511 #[test]
512 fn test_v3_router_addresses() {
513 let chains = [
514 NamedChain::Mainnet,
515 NamedChain::Arbitrum,
516 NamedChain::Optimism,
517 NamedChain::Polygon,
518 NamedChain::Base,
519 ];
520
521 for chain in chains {
522 let address = chain.v3_router_address().unwrap();
523 assert_eq!(address, ODOS_V3);
524 }
525 }
526
527 #[test]
528 fn test_supports_odos() {
529 assert!(NamedChain::Mainnet.supports_odos());
530 assert!(NamedChain::Arbitrum.supports_odos());
531 assert!(!NamedChain::Sepolia.supports_odos());
532 }
533
534 #[test]
535 fn test_supports_lo() {
536 assert!(NamedChain::Mainnet.supports_lo());
537 assert!(NamedChain::Optimism.supports_lo());
538 assert!(NamedChain::Polygon.supports_lo());
539 assert!(NamedChain::BinanceSmartChain.supports_lo());
540 assert!(NamedChain::Arbitrum.supports_lo());
541 assert!(NamedChain::Base.supports_lo());
542 assert!(!NamedChain::Sepolia.supports_lo());
543 }
544
545 #[test]
546 fn test_supports_v2() {
547 assert!(NamedChain::Mainnet.supports_v2());
548 assert!(NamedChain::Arbitrum.supports_v2());
549 assert!(!NamedChain::Sepolia.supports_v2());
550 }
551
552 #[test]
553 fn test_supports_v3() {
554 assert!(NamedChain::Mainnet.supports_v3());
555 assert!(NamedChain::Arbitrum.supports_v3());
556 assert!(!NamedChain::Sepolia.supports_v3());
557 }
558
559 #[test]
560 fn test_router_availability() {
561 let avail = NamedChain::Mainnet.router_availability();
563 assert!(avail.limit_order);
564 assert!(avail.v2);
565 assert!(avail.v3);
566 assert_eq!(avail.count(), 3);
567
568 let avail = NamedChain::Arbitrum.router_availability();
570 assert!(avail.limit_order);
571 assert!(avail.v2);
572 assert!(avail.v3);
573 assert_eq!(avail.count(), 3);
574
575 let avail = NamedChain::Sepolia.router_availability();
577 assert!(!avail.limit_order);
578 assert!(!avail.v2);
579 assert!(!avail.v3);
580 assert_eq!(avail.count(), 0);
581 assert!(!avail.has_any());
582 }
583
584 #[test]
585 fn test_try_methods() {
586 assert!(NamedChain::Mainnet.try_lo_router_address().is_some());
587 assert!(NamedChain::Mainnet.try_v2_router_address().is_some());
588 assert!(NamedChain::Mainnet.try_v3_router_address().is_some());
589
590 assert!(NamedChain::Sepolia.try_lo_router_address().is_none());
591 assert!(NamedChain::Sepolia.try_v2_router_address().is_none());
592 assert!(NamedChain::Sepolia.try_v3_router_address().is_none());
593
594 assert!(NamedChain::Arbitrum.try_lo_router_address().is_some());
596 assert!(NamedChain::Arbitrum.try_v2_router_address().is_some());
597 assert!(NamedChain::Arbitrum.try_v3_router_address().is_some());
598 }
599
600 #[test]
601 fn test_router_selection() {
602 let chain = NamedChain::Mainnet;
603
604 assert_eq!(
606 chain.recommended_router_address().unwrap(),
607 chain.v3_router_address().unwrap()
608 );
609
610 assert_eq!(
612 chain.router_address_with_fallback().unwrap(),
613 chain.v3_router_address().unwrap()
614 );
615
616 assert_eq!(
618 chain.router_address_by_preference(true).unwrap(),
619 chain.v3_router_address().unwrap()
620 );
621 assert_eq!(
622 chain.router_address_by_preference(false).unwrap(),
623 chain.v2_router_address().unwrap()
624 );
625 }
626
627 #[test]
628 fn test_error_handling() {
629 let result = NamedChain::Sepolia.lo_router_address();
631 assert!(result.is_err());
632 assert!(matches!(
633 result.unwrap_err(),
634 OdosChainError::LimitOrderNotAvailable { .. }
635 ));
636
637 let result = NamedChain::Sepolia.v2_router_address();
638 assert!(result.is_err());
639 assert!(matches!(
640 result.unwrap_err(),
641 OdosChainError::V2NotAvailable { .. }
642 ));
643
644 let result = NamedChain::Sepolia.v3_router_address();
645 assert!(result.is_err());
646 assert!(matches!(
647 result.unwrap_err(),
648 OdosChainError::V3NotAvailable { .. }
649 ));
650 }
651}