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_MODE_ROUTER, ODOS_LO_OP_ROUTER, ODOS_LO_POLYGON_ROUTER,
13 ODOS_LO_SCROLL_ROUTER, ODOS_LO_SONIC_ROUTER, ODOS_LO_UNICHAIN_ROUTER, ODOS_LO_ZKSYNC_ROUTER,
14 ODOS_V2_ARBITRUM_ROUTER, ODOS_V2_AVALANCHE_ROUTER, ODOS_V2_BASE_ROUTER, ODOS_V2_BSC_ROUTER,
15 ODOS_V2_ETHEREUM_ROUTER, ODOS_V2_FRAXTAL_ROUTER, ODOS_V2_LINEA_ROUTER, ODOS_V2_MANTLE_ROUTER,
16 ODOS_V2_MODE_ROUTER, ODOS_V2_OP_ROUTER, ODOS_V2_POLYGON_ROUTER, ODOS_V2_SCROLL_ROUTER,
17 ODOS_V2_SONIC_ROUTER, ODOS_V2_UNICHAIN_ROUTER, 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 Mode => ODOS_LO_MODE_ROUTER,
241 Scroll => ODOS_LO_SCROLL_ROUTER,
242 Sonic => ODOS_LO_SONIC_ROUTER,
243 ZkSync => ODOS_LO_ZKSYNC_ROUTER,
244 Unichain => ODOS_LO_UNICHAIN_ROUTER,
245 _ => {
246 return Err(OdosChainError::LimitOrderNotAvailable {
247 chain: format!("{self:?}"),
248 });
249 }
250 })
251 }
252
253 fn v2_router_address(&self) -> OdosChainResult<Address> {
254 use NamedChain::*;
255
256 if !self.supports_odos() {
257 return Err(OdosChainError::V2NotAvailable {
258 chain: format!("{self:?}"),
259 });
260 }
261
262 if !self.supports_v2() {
264 return self.v3_router_address();
265 }
266
267 Ok(match self {
268 Arbitrum => ODOS_V2_ARBITRUM_ROUTER,
269 Avalanche => ODOS_V2_AVALANCHE_ROUTER,
270 Base => ODOS_V2_BASE_ROUTER,
271 BinanceSmartChain => ODOS_V2_BSC_ROUTER,
272 Fraxtal => ODOS_V2_FRAXTAL_ROUTER,
273 Mainnet => ODOS_V2_ETHEREUM_ROUTER,
274 Optimism => ODOS_V2_OP_ROUTER,
275 Polygon => ODOS_V2_POLYGON_ROUTER,
276 Linea => ODOS_V2_LINEA_ROUTER,
277 Mantle => ODOS_V2_MANTLE_ROUTER,
278 Mode => ODOS_V2_MODE_ROUTER,
279 Scroll => ODOS_V2_SCROLL_ROUTER,
280 Sonic => ODOS_V2_SONIC_ROUTER,
281 ZkSync => ODOS_V2_ZKSYNC_ROUTER,
282 Unichain => ODOS_V2_UNICHAIN_ROUTER,
283 _ => {
284 return Err(OdosChainError::UnsupportedChain {
285 chain: format!("{self:?}"),
286 });
287 }
288 })
289 }
290
291 fn v3_router_address(&self) -> OdosChainResult<Address> {
292 if !self.supports_odos() {
293 return Err(OdosChainError::V3NotAvailable {
294 chain: format!("{self:?}"),
295 });
296 }
297
298 if !self.supports_v3() {
300 return self.v2_router_address();
301 }
302
303 Ok(ODOS_V3)
304 }
305
306 fn supports_odos(&self) -> bool {
307 use NamedChain::*;
308 matches!(
309 self,
310 Arbitrum
311 | Avalanche
312 | Base
313 | BinanceSmartChain
314 | Fraxtal
315 | Mainnet
316 | Optimism
317 | Polygon
318 | Linea
319 | Mantle
320 | Mode
321 | Scroll
322 | Sonic
323 | ZkSync
324 | Unichain
325 )
326 }
327
328 fn supports_lo(&self) -> bool {
329 use NamedChain::*;
330 matches!(
331 self,
332 Arbitrum
333 | Avalanche
334 | Base
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 | BinanceSmartChain
380 | Fraxtal
381 | Mainnet
382 | Optimism
383 | Polygon
384 | Linea
385 | Mantle
386 | Mode
387 | Scroll
388 | Sonic
389 | ZkSync
390 | Unichain
391 )
392 }
393}
394
395pub trait OdosRouterSelection: OdosChain {
400 fn recommended_router_address(&self) -> OdosChainResult<Address> {
420 self.v3_router_address()
421 }
422
423 fn router_address_with_fallback(&self) -> OdosChainResult<Address> {
443 self.v3_router_address()
444 .or_else(|_| self.v2_router_address())
445 }
446
447 fn router_address_by_preference(&self, prefer_v3: bool) -> OdosChainResult<Address> {
469 if prefer_v3 {
470 self.v3_router_address()
471 } else {
472 self.v2_router_address()
473 }
474 }
475}
476
477impl<T: OdosChain> OdosRouterSelection for T {}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482 use alloy_chains::NamedChain;
483
484 #[test]
485 fn test_lo_router_addresses() {
486 let chains = [
487 NamedChain::Mainnet,
488 NamedChain::Optimism,
489 NamedChain::Polygon,
490 NamedChain::BinanceSmartChain,
491 ];
492
493 for chain in chains {
494 let address = chain.lo_router_address().unwrap();
495 assert!(address != Address::ZERO);
496 assert_eq!(address.to_string().len(), 42); }
498 }
499
500 #[test]
501 fn test_v2_router_addresses() {
502 let chains = [
503 NamedChain::Mainnet,
504 NamedChain::Arbitrum,
505 NamedChain::Optimism,
506 NamedChain::Polygon,
507 NamedChain::Base,
508 ];
509
510 for chain in chains {
511 let address = chain.v2_router_address().unwrap();
512 assert!(address != Address::ZERO);
513 assert_eq!(address.to_string().len(), 42); }
515 }
516
517 #[test]
518 fn test_v3_router_addresses() {
519 let chains = [
520 NamedChain::Mainnet,
521 NamedChain::Arbitrum,
522 NamedChain::Optimism,
523 NamedChain::Polygon,
524 NamedChain::Base,
525 ];
526
527 for chain in chains {
528 let address = chain.v3_router_address().unwrap();
529 assert_eq!(address, ODOS_V3);
530 }
531 }
532
533 #[test]
534 fn test_supports_odos() {
535 assert!(NamedChain::Mainnet.supports_odos());
536 assert!(NamedChain::Arbitrum.supports_odos());
537 assert!(!NamedChain::Sepolia.supports_odos());
538 }
539
540 #[test]
541 fn test_supports_lo() {
542 assert!(NamedChain::Mainnet.supports_lo());
543 assert!(NamedChain::Optimism.supports_lo());
544 assert!(NamedChain::Polygon.supports_lo());
545 assert!(NamedChain::BinanceSmartChain.supports_lo());
546 assert!(NamedChain::Arbitrum.supports_lo());
547 assert!(NamedChain::Base.supports_lo());
548 assert!(!NamedChain::Sepolia.supports_lo());
549 }
550
551 #[test]
552 fn test_supports_v2() {
553 assert!(NamedChain::Mainnet.supports_v2());
554 assert!(NamedChain::Arbitrum.supports_v2());
555 assert!(!NamedChain::Sepolia.supports_v2());
556 }
557
558 #[test]
559 fn test_supports_v3() {
560 assert!(NamedChain::Mainnet.supports_v3());
561 assert!(NamedChain::Arbitrum.supports_v3());
562 assert!(!NamedChain::Sepolia.supports_v3());
563 }
564
565 #[test]
566 fn test_router_availability() {
567 let avail = NamedChain::Mainnet.router_availability();
569 assert!(avail.limit_order);
570 assert!(avail.v2);
571 assert!(avail.v3);
572 assert_eq!(avail.count(), 3);
573
574 let avail = NamedChain::Arbitrum.router_availability();
576 assert!(avail.limit_order);
577 assert!(avail.v2);
578 assert!(avail.v3);
579 assert_eq!(avail.count(), 3);
580
581 let avail = NamedChain::Sepolia.router_availability();
583 assert!(!avail.limit_order);
584 assert!(!avail.v2);
585 assert!(!avail.v3);
586 assert_eq!(avail.count(), 0);
587 assert!(!avail.has_any());
588 }
589
590 #[test]
591 fn test_try_methods() {
592 assert!(NamedChain::Mainnet.try_lo_router_address().is_some());
593 assert!(NamedChain::Mainnet.try_v2_router_address().is_some());
594 assert!(NamedChain::Mainnet.try_v3_router_address().is_some());
595
596 assert!(NamedChain::Sepolia.try_lo_router_address().is_none());
597 assert!(NamedChain::Sepolia.try_v2_router_address().is_none());
598 assert!(NamedChain::Sepolia.try_v3_router_address().is_none());
599
600 assert!(NamedChain::Arbitrum.try_lo_router_address().is_some());
602 assert!(NamedChain::Arbitrum.try_v2_router_address().is_some());
603 assert!(NamedChain::Arbitrum.try_v3_router_address().is_some());
604 }
605
606 #[test]
607 fn test_router_selection() {
608 let chain = NamedChain::Mainnet;
609
610 assert_eq!(
612 chain.recommended_router_address().unwrap(),
613 chain.v3_router_address().unwrap()
614 );
615
616 assert_eq!(
618 chain.router_address_with_fallback().unwrap(),
619 chain.v3_router_address().unwrap()
620 );
621
622 assert_eq!(
624 chain.router_address_by_preference(true).unwrap(),
625 chain.v3_router_address().unwrap()
626 );
627 assert_eq!(
628 chain.router_address_by_preference(false).unwrap(),
629 chain.v2_router_address().unwrap()
630 );
631 }
632
633 #[test]
634 fn test_error_handling() {
635 let result = NamedChain::Sepolia.lo_router_address();
637 assert!(result.is_err());
638 assert!(matches!(
639 result.unwrap_err(),
640 OdosChainError::LimitOrderNotAvailable { .. }
641 ));
642
643 let result = NamedChain::Sepolia.v2_router_address();
644 assert!(result.is_err());
645 assert!(matches!(
646 result.unwrap_err(),
647 OdosChainError::V2NotAvailable { .. }
648 ));
649
650 let result = NamedChain::Sepolia.v3_router_address();
651 assert!(result.is_err());
652 assert!(matches!(
653 result.unwrap_err(),
654 OdosChainError::V3NotAvailable { .. }
655 ));
656 }
657}