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_SONIC_ROUTER,
13 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_SONIC_ROUTER, ODOS_V2_UNICHAIN_ROUTER, ODOS_V2_ZKSYNC_ROUTER,
17 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 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 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 | Sonic
317 | ZkSync
318 | Unichain
319 )
320 }
321
322 fn supports_lo(&self) -> bool {
323 use NamedChain::*;
324 matches!(
325 self,
326 Arbitrum
327 | Avalanche
328 | Base
329 | BinanceSmartChain
330 | Fraxtal
331 | Mainnet
332 | Optimism
333 | Polygon
334 | Linea
335 | Mantle
336 | Sonic
337 | ZkSync
338 | Unichain
339 )
340 }
341
342 fn supports_v2(&self) -> bool {
343 use NamedChain::*;
344 matches!(
345 self,
346 Arbitrum
347 | Avalanche
348 | Base
349 | BinanceSmartChain
350 | Fraxtal
351 | Mainnet
352 | Optimism
353 | Polygon
354 | Linea
355 | Mantle
356 | Sonic
357 | ZkSync
358 | Unichain
359 )
360 }
361
362 fn supports_v3(&self) -> bool {
363 use NamedChain::*;
364 matches!(
365 self,
366 Arbitrum
367 | Avalanche
368 | Base
369 | BinanceSmartChain
370 | Fraxtal
371 | Mainnet
372 | Optimism
373 | Polygon
374 | Linea
375 | Mantle
376 | Sonic
377 | ZkSync
378 | Unichain
379 )
380 }
381}
382
383pub trait OdosRouterSelection: OdosChain {
388 fn recommended_router_address(&self) -> OdosChainResult<Address> {
408 self.v3_router_address()
409 }
410
411 fn router_address_with_fallback(&self) -> OdosChainResult<Address> {
431 self.v3_router_address()
432 .or_else(|_| self.v2_router_address())
433 }
434
435 fn router_address_by_preference(&self, prefer_v3: bool) -> OdosChainResult<Address> {
457 if prefer_v3 {
458 self.v3_router_address()
459 } else {
460 self.v2_router_address()
461 }
462 }
463}
464
465impl<T: OdosChain> OdosRouterSelection for T {}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470 use alloy_chains::NamedChain;
471
472 #[test]
473 fn test_lo_router_addresses() {
474 let chains = [
475 NamedChain::Mainnet,
476 NamedChain::Optimism,
477 NamedChain::Polygon,
478 NamedChain::BinanceSmartChain,
479 ];
480
481 for chain in chains {
482 let address = chain.lo_router_address().unwrap();
483 assert!(address != Address::ZERO);
484 assert_eq!(address.to_string().len(), 42); }
486 }
487
488 #[test]
489 fn test_v2_router_addresses() {
490 let chains = [
491 NamedChain::Mainnet,
492 NamedChain::Arbitrum,
493 NamedChain::Optimism,
494 NamedChain::Polygon,
495 NamedChain::Base,
496 ];
497
498 for chain in chains {
499 let address = chain.v2_router_address().unwrap();
500 assert!(address != Address::ZERO);
501 assert_eq!(address.to_string().len(), 42); }
503 }
504
505 #[test]
506 fn test_v3_router_addresses() {
507 let chains = [
508 NamedChain::Mainnet,
509 NamedChain::Arbitrum,
510 NamedChain::Optimism,
511 NamedChain::Polygon,
512 NamedChain::Base,
513 ];
514
515 for chain in chains {
516 let address = chain.v3_router_address().unwrap();
517 assert_eq!(address, ODOS_V3);
518 }
519 }
520
521 #[test]
522 fn test_supports_odos() {
523 assert!(NamedChain::Mainnet.supports_odos());
524 assert!(NamedChain::Arbitrum.supports_odos());
525 assert!(!NamedChain::Sepolia.supports_odos());
526 }
527
528 #[test]
529 fn test_supports_lo() {
530 assert!(NamedChain::Mainnet.supports_lo());
531 assert!(NamedChain::Optimism.supports_lo());
532 assert!(NamedChain::Polygon.supports_lo());
533 assert!(NamedChain::BinanceSmartChain.supports_lo());
534 assert!(NamedChain::Arbitrum.supports_lo());
535 assert!(NamedChain::Base.supports_lo());
536 assert!(!NamedChain::Sepolia.supports_lo());
537 }
538
539 #[test]
540 fn test_supports_v2() {
541 assert!(NamedChain::Mainnet.supports_v2());
542 assert!(NamedChain::Arbitrum.supports_v2());
543 assert!(!NamedChain::Sepolia.supports_v2());
544 }
545
546 #[test]
547 fn test_supports_v3() {
548 assert!(NamedChain::Mainnet.supports_v3());
549 assert!(NamedChain::Arbitrum.supports_v3());
550 assert!(!NamedChain::Sepolia.supports_v3());
551 }
552
553 #[test]
554 fn test_router_availability() {
555 let avail = NamedChain::Mainnet.router_availability();
557 assert!(avail.limit_order);
558 assert!(avail.v2);
559 assert!(avail.v3);
560 assert_eq!(avail.count(), 3);
561
562 let avail = NamedChain::Arbitrum.router_availability();
564 assert!(avail.limit_order);
565 assert!(avail.v2);
566 assert!(avail.v3);
567 assert_eq!(avail.count(), 3);
568
569 let avail = NamedChain::Sepolia.router_availability();
571 assert!(!avail.limit_order);
572 assert!(!avail.v2);
573 assert!(!avail.v3);
574 assert_eq!(avail.count(), 0);
575 assert!(!avail.has_any());
576 }
577
578 #[test]
579 fn test_try_methods() {
580 assert!(NamedChain::Mainnet.try_lo_router_address().is_some());
581 assert!(NamedChain::Mainnet.try_v2_router_address().is_some());
582 assert!(NamedChain::Mainnet.try_v3_router_address().is_some());
583
584 assert!(NamedChain::Sepolia.try_lo_router_address().is_none());
585 assert!(NamedChain::Sepolia.try_v2_router_address().is_none());
586 assert!(NamedChain::Sepolia.try_v3_router_address().is_none());
587
588 assert!(NamedChain::Arbitrum.try_lo_router_address().is_some());
590 assert!(NamedChain::Arbitrum.try_v2_router_address().is_some());
591 assert!(NamedChain::Arbitrum.try_v3_router_address().is_some());
592 }
593
594 #[test]
595 fn test_router_selection() {
596 let chain = NamedChain::Mainnet;
597
598 assert_eq!(
600 chain.recommended_router_address().unwrap(),
601 chain.v3_router_address().unwrap()
602 );
603
604 assert_eq!(
606 chain.router_address_with_fallback().unwrap(),
607 chain.v3_router_address().unwrap()
608 );
609
610 assert_eq!(
612 chain.router_address_by_preference(true).unwrap(),
613 chain.v3_router_address().unwrap()
614 );
615 assert_eq!(
616 chain.router_address_by_preference(false).unwrap(),
617 chain.v2_router_address().unwrap()
618 );
619 }
620
621 #[test]
622 fn test_error_handling() {
623 let result = NamedChain::Sepolia.lo_router_address();
625 assert!(result.is_err());
626 assert!(matches!(
627 result.unwrap_err(),
628 OdosChainError::LimitOrderNotAvailable { .. }
629 ));
630
631 let result = NamedChain::Sepolia.v2_router_address();
632 assert!(result.is_err());
633 assert!(matches!(
634 result.unwrap_err(),
635 OdosChainError::V2NotAvailable { .. }
636 ));
637
638 let result = NamedChain::Sepolia.v3_router_address();
639 assert!(result.is_err());
640 assert!(matches!(
641 result.unwrap_err(),
642 OdosChainError::V3NotAvailable { .. }
643 ));
644 }
645}