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_BSC_ROUTER, ODOS_LO_ETHEREUM_ROUTER, ODOS_LO_FRAXTAL_ROUTER, ODOS_LO_LINEA_ROUTER,
8 ODOS_LO_MANTLE_ROUTER, ODOS_LO_MODE_ROUTER, ODOS_LO_OP_ROUTER, ODOS_LO_POLYGON_ROUTER,
9 ODOS_LO_SCROLL_ROUTER, ODOS_LO_SONIC_ROUTER, ODOS_LO_UNICHAIN_ROUTER, ODOS_LO_ZKSYNC_ROUTER,
10 ODOS_V2_ARBITRUM_ROUTER, ODOS_V2_AVALANCHE_ROUTER, ODOS_V2_BASE_ROUTER, ODOS_V2_BSC_ROUTER,
11 ODOS_V2_ETHEREUM_ROUTER, ODOS_V2_FRAXTAL_ROUTER, ODOS_V2_LINEA_ROUTER, ODOS_V2_MANTLE_ROUTER,
12 ODOS_V2_MODE_ROUTER, ODOS_V2_OP_ROUTER, ODOS_V2_POLYGON_ROUTER, ODOS_V2_SCROLL_ROUTER,
13 ODOS_V2_SONIC_ROUTER, ODOS_V2_UNICHAIN_ROUTER, ODOS_V2_ZKSYNC_ROUTER, ODOS_V3,
14};
15
16#[derive(Error, Debug, Clone, PartialEq)]
18pub enum OdosChainError {
19 #[error("Chain {chain:?} is not supported by Odos protocol")]
21 UnsupportedChain { chain: String },
22
23 #[error("Odos Limit Order router is not available on chain {chain:?}")]
25 LimitOrderNotAvailable { chain: String },
26
27 #[error("Odos V2 router is not available on chain {chain:?}")]
29 V2NotAvailable { chain: String },
30
31 #[error("Odos V3 router is not available on chain {chain:?}")]
33 V3NotAvailable { chain: String },
34
35 #[error("Invalid address format: {address}")]
37 InvalidAddress { address: String },
38}
39
40pub type OdosChainResult<T> = Result<T, OdosChainError>;
42
43pub trait OdosChain {
72 fn lo_router_address(&self) -> OdosChainResult<Address>;
89 fn v2_router_address(&self) -> OdosChainResult<Address>;
106
107 fn v3_router_address(&self) -> OdosChainResult<Address>;
127
128 fn supports_odos(&self) -> bool;
134
135 fn supports_lo(&self) -> bool;
141
142 fn supports_v2(&self) -> bool;
148
149 fn supports_v3(&self) -> bool;
155
156 fn router_availability(&self) -> RouterAvailability {
174 RouterAvailability {
175 limit_order: self.supports_lo(),
176 v2: self.supports_v2(),
177 v3: self.supports_v3(),
178 }
179 }
180
181 fn try_lo_router_address(&self) -> Option<Address> {
187 self.lo_router_address().ok()
188 }
189
190 fn try_v2_router_address(&self) -> Option<Address> {
196 self.v2_router_address().ok()
197 }
198
199 fn try_v3_router_address(&self) -> Option<Address> {
205 self.v3_router_address().ok()
206 }
207}
208
209impl OdosChain for NamedChain {
210 fn lo_router_address(&self) -> OdosChainResult<Address> {
211 use NamedChain::*;
212
213 if !self.supports_odos() {
214 return Err(OdosChainError::LimitOrderNotAvailable {
215 chain: format!("{self:?}"),
216 });
217 }
218
219 if !self.supports_lo() {
220 return Err(OdosChainError::LimitOrderNotAvailable {
221 chain: format!("{self:?}"),
222 });
223 }
224
225 Ok(match self {
226 Arbitrum => ODOS_LO_ARBITRUM_ROUTER,
227 Avalanche => ODOS_LO_AVALANCHE_ROUTER,
228 Base => ODOS_LO_BASE_ROUTER,
229 BinanceSmartChain => ODOS_LO_BSC_ROUTER,
230 Fraxtal => ODOS_LO_FRAXTAL_ROUTER,
231 Mainnet => ODOS_LO_ETHEREUM_ROUTER,
232 Optimism => ODOS_LO_OP_ROUTER,
233 Polygon => ODOS_LO_POLYGON_ROUTER,
234 Linea => ODOS_LO_LINEA_ROUTER,
235 Mantle => ODOS_LO_MANTLE_ROUTER,
236 Mode => ODOS_LO_MODE_ROUTER,
237 Scroll => ODOS_LO_SCROLL_ROUTER,
238 Sonic => ODOS_LO_SONIC_ROUTER,
239 ZkSync => ODOS_LO_ZKSYNC_ROUTER,
240 Unichain => ODOS_LO_UNICHAIN_ROUTER,
241 _ => {
242 return Err(OdosChainError::LimitOrderNotAvailable {
243 chain: format!("{self:?}"),
244 });
245 }
246 })
247 }
248
249 fn v2_router_address(&self) -> OdosChainResult<Address> {
250 use NamedChain::*;
251
252 if !self.supports_odos() {
253 return Err(OdosChainError::V2NotAvailable {
254 chain: format!("{self:?}"),
255 });
256 }
257
258 if !self.supports_v2() {
260 return self.v3_router_address();
261 }
262
263 Ok(match self {
264 Arbitrum => ODOS_V2_ARBITRUM_ROUTER,
265 Avalanche => ODOS_V2_AVALANCHE_ROUTER,
266 Base => ODOS_V2_BASE_ROUTER,
267 BinanceSmartChain => ODOS_V2_BSC_ROUTER,
268 Fraxtal => ODOS_V2_FRAXTAL_ROUTER,
269 Mainnet => ODOS_V2_ETHEREUM_ROUTER,
270 Optimism => ODOS_V2_OP_ROUTER,
271 Polygon => ODOS_V2_POLYGON_ROUTER,
272 Linea => ODOS_V2_LINEA_ROUTER,
273 Mantle => ODOS_V2_MANTLE_ROUTER,
274 Mode => ODOS_V2_MODE_ROUTER,
275 Scroll => ODOS_V2_SCROLL_ROUTER,
276 Sonic => ODOS_V2_SONIC_ROUTER,
277 ZkSync => ODOS_V2_ZKSYNC_ROUTER,
278 Unichain => ODOS_V2_UNICHAIN_ROUTER,
279 _ => {
280 return Err(OdosChainError::UnsupportedChain {
281 chain: format!("{self:?}"),
282 });
283 }
284 })
285 }
286
287 fn v3_router_address(&self) -> OdosChainResult<Address> {
288 if !self.supports_odos() {
289 return Err(OdosChainError::V3NotAvailable {
290 chain: format!("{self:?}"),
291 });
292 }
293
294 if !self.supports_v3() {
296 return self.v2_router_address();
297 }
298
299 Ok(ODOS_V3)
300 }
301
302 fn supports_odos(&self) -> bool {
303 use NamedChain::*;
304 matches!(
305 self,
306 Arbitrum
307 | Avalanche
308 | Base
309 | BinanceSmartChain
310 | Fraxtal
311 | Mainnet
312 | Optimism
313 | Polygon
314 | Linea
315 | Mantle
316 | Mode
317 | Scroll
318 | Sonic
319 | ZkSync
320 | Unichain
321 )
322 }
323
324 fn supports_lo(&self) -> bool {
325 use NamedChain::*;
326 matches!(
327 self,
328 Arbitrum
329 | Avalanche
330 | Base
331 | BinanceSmartChain
332 | Fraxtal
333 | Mainnet
334 | Optimism
335 | Polygon
336 | Linea
337 | Mantle
338 | Mode
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 | Mode
361 | Scroll
362 | Sonic
363 | ZkSync
364 | Unichain
365 )
366 }
367
368 fn supports_v3(&self) -> bool {
369 use NamedChain::*;
370 matches!(
371 self,
372 Arbitrum
373 | Avalanche
374 | Base
375 | BinanceSmartChain
376 | Fraxtal
377 | Mainnet
378 | Optimism
379 | Polygon
380 | Linea
381 | Mantle
382 | Mode
383 | Scroll
384 | Sonic
385 | ZkSync
386 | Unichain
387 )
388 }
389}
390
391pub trait OdosRouterSelection: OdosChain {
396 fn recommended_router_address(&self) -> OdosChainResult<Address> {
416 self.v3_router_address()
417 }
418
419 fn router_address_with_fallback(&self) -> OdosChainResult<Address> {
439 self.v3_router_address()
440 .or_else(|_| self.v2_router_address())
441 }
442
443 fn router_address_by_preference(&self, prefer_v3: bool) -> OdosChainResult<Address> {
465 if prefer_v3 {
466 self.v3_router_address()
467 } else {
468 self.v2_router_address()
469 }
470 }
471}
472
473impl<T: OdosChain> OdosRouterSelection for T {}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use alloy_chains::NamedChain;
479
480 #[test]
481 fn test_lo_router_addresses() {
482 let chains = [
483 NamedChain::Mainnet,
484 NamedChain::Optimism,
485 NamedChain::Polygon,
486 NamedChain::BinanceSmartChain,
487 ];
488
489 for chain in chains {
490 let address = chain.lo_router_address().unwrap();
491 assert!(address != Address::ZERO);
492 assert_eq!(address.to_string().len(), 42); }
494 }
495
496 #[test]
497 fn test_v2_router_addresses() {
498 let chains = [
499 NamedChain::Mainnet,
500 NamedChain::Arbitrum,
501 NamedChain::Optimism,
502 NamedChain::Polygon,
503 NamedChain::Base,
504 ];
505
506 for chain in chains {
507 let address = chain.v2_router_address().unwrap();
508 assert!(address != Address::ZERO);
509 assert_eq!(address.to_string().len(), 42); }
511 }
512
513 #[test]
514 fn test_v3_router_addresses() {
515 let chains = [
516 NamedChain::Mainnet,
517 NamedChain::Arbitrum,
518 NamedChain::Optimism,
519 NamedChain::Polygon,
520 NamedChain::Base,
521 ];
522
523 for chain in chains {
524 let address = chain.v3_router_address().unwrap();
525 assert_eq!(address, ODOS_V3);
526 }
527 }
528
529 #[test]
530 fn test_supports_odos() {
531 assert!(NamedChain::Mainnet.supports_odos());
532 assert!(NamedChain::Arbitrum.supports_odos());
533 assert!(!NamedChain::Sepolia.supports_odos());
534 }
535
536 #[test]
537 fn test_supports_lo() {
538 assert!(NamedChain::Mainnet.supports_lo());
539 assert!(NamedChain::Optimism.supports_lo());
540 assert!(NamedChain::Polygon.supports_lo());
541 assert!(NamedChain::BinanceSmartChain.supports_lo());
542 assert!(NamedChain::Arbitrum.supports_lo());
543 assert!(NamedChain::Base.supports_lo());
544 assert!(!NamedChain::Sepolia.supports_lo());
545 }
546
547 #[test]
548 fn test_supports_v2() {
549 assert!(NamedChain::Mainnet.supports_v2());
550 assert!(NamedChain::Arbitrum.supports_v2());
551 assert!(!NamedChain::Sepolia.supports_v2());
552 }
553
554 #[test]
555 fn test_supports_v3() {
556 assert!(NamedChain::Mainnet.supports_v3());
557 assert!(NamedChain::Arbitrum.supports_v3());
558 assert!(!NamedChain::Sepolia.supports_v3());
559 }
560
561 #[test]
562 fn test_router_availability() {
563 let avail = NamedChain::Mainnet.router_availability();
565 assert!(avail.limit_order);
566 assert!(avail.v2);
567 assert!(avail.v3);
568 assert_eq!(avail.count(), 3);
569
570 let avail = NamedChain::Arbitrum.router_availability();
572 assert!(avail.limit_order);
573 assert!(avail.v2);
574 assert!(avail.v3);
575 assert_eq!(avail.count(), 3);
576
577 let avail = NamedChain::Sepolia.router_availability();
579 assert!(!avail.limit_order);
580 assert!(!avail.v2);
581 assert!(!avail.v3);
582 assert_eq!(avail.count(), 0);
583 assert!(!avail.has_any());
584 }
585
586 #[test]
587 fn test_try_methods() {
588 assert!(NamedChain::Mainnet.try_lo_router_address().is_some());
589 assert!(NamedChain::Mainnet.try_v2_router_address().is_some());
590 assert!(NamedChain::Mainnet.try_v3_router_address().is_some());
591
592 assert!(NamedChain::Sepolia.try_lo_router_address().is_none());
593 assert!(NamedChain::Sepolia.try_v2_router_address().is_none());
594 assert!(NamedChain::Sepolia.try_v3_router_address().is_none());
595
596 assert!(NamedChain::Arbitrum.try_lo_router_address().is_some());
598 assert!(NamedChain::Arbitrum.try_v2_router_address().is_some());
599 assert!(NamedChain::Arbitrum.try_v3_router_address().is_some());
600 }
601
602 #[test]
603 fn test_router_selection() {
604 let chain = NamedChain::Mainnet;
605
606 assert_eq!(
608 chain.recommended_router_address().unwrap(),
609 chain.v3_router_address().unwrap()
610 );
611
612 assert_eq!(
614 chain.router_address_with_fallback().unwrap(),
615 chain.v3_router_address().unwrap()
616 );
617
618 assert_eq!(
620 chain.router_address_by_preference(true).unwrap(),
621 chain.v3_router_address().unwrap()
622 );
623 assert_eq!(
624 chain.router_address_by_preference(false).unwrap(),
625 chain.v2_router_address().unwrap()
626 );
627 }
628
629 #[test]
630 fn test_error_handling() {
631 let result = NamedChain::Sepolia.lo_router_address();
633 assert!(result.is_err());
634 assert!(matches!(
635 result.unwrap_err(),
636 OdosChainError::LimitOrderNotAvailable { .. }
637 ));
638
639 let result = NamedChain::Sepolia.v2_router_address();
640 assert!(result.is_err());
641 assert!(matches!(
642 result.unwrap_err(),
643 OdosChainError::V2NotAvailable { .. }
644 ));
645
646 let result = NamedChain::Sepolia.v3_router_address();
647 assert!(result.is_err());
648 assert!(matches!(
649 result.unwrap_err(),
650 OdosChainError::V3NotAvailable { .. }
651 ));
652 }
653}