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#[derive(Error, Debug, Clone, PartialEq)]
19pub enum OdosChainError {
20 #[error("Chain {chain:?} is not supported by Odos protocol")]
22 UnsupportedChain { chain: String },
23
24 #[error("Odos Limit Order router is not available on chain {chain:?}")]
26 LimitOrderNotAvailable { chain: String },
27
28 #[error("Odos V2 router is not available on chain {chain:?}")]
30 V2NotAvailable { chain: String },
31
32 #[error("Odos V3 router is not available on chain {chain:?}")]
34 V3NotAvailable { chain: String },
35
36 #[error("Invalid address format: {address}")]
38 InvalidAddress { address: String },
39}
40
41pub type OdosChainResult<T> = Result<T, OdosChainError>;
43
44pub trait OdosChain {
73 fn lo_router_address(&self) -> OdosChainResult<Address>;
90 fn v2_router_address(&self) -> OdosChainResult<Address>;
107
108 fn v3_router_address(&self) -> OdosChainResult<Address>;
128
129 fn supports_odos(&self) -> bool;
135
136 fn supports_lo(&self) -> bool;
142
143 fn supports_v2(&self) -> bool;
149
150 fn supports_v3(&self) -> bool;
156
157 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 fn try_lo_router_address(&self) -> Option<Address> {
188 self.lo_router_address().ok()
189 }
190
191 fn try_v2_router_address(&self) -> Option<Address> {
197 self.v2_router_address().ok()
198 }
199
200 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 !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 !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
396pub trait OdosRouterSelection: OdosChain {
401 fn recommended_router_address(&self) -> OdosChainResult<Address> {
421 self.v3_router_address()
422 }
423
424 fn router_address_with_fallback(&self) -> OdosChainResult<Address> {
444 self.v3_router_address()
445 .or_else(|_| self.v2_router_address())
446 }
447
448 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); }
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); }
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()); 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 assert!(NamedChain::Berachain.supports_lo());
575 assert!(!NamedChain::Berachain.supports_v2());
576 assert!(NamedChain::Berachain.supports_v3());
577
578 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 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 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 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 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 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 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 assert_eq!(
646 chain.recommended_router_address().unwrap(),
647 chain.v3_router_address().unwrap()
648 );
649
650 assert_eq!(
652 chain.router_address_with_fallback().unwrap(),
653 chain.v3_router_address().unwrap()
654 );
655
656 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 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}