use alloy_chains::NamedChain;
use alloy_primitives::Address;
use thiserror::Error;
use crate::{
RouterAvailability, ODOS_LO_ARBITRUM_ROUTER, ODOS_LO_AVALANCHE_ROUTER, ODOS_LO_BASE_ROUTER,
ODOS_LO_BSC_ROUTER, ODOS_LO_ETHEREUM_ROUTER, ODOS_LO_FRAXTAL_ROUTER, ODOS_LO_LINEA_ROUTER,
ODOS_LO_MANTLE_ROUTER, ODOS_LO_OP_ROUTER, ODOS_LO_POLYGON_ROUTER, ODOS_LO_SONIC_ROUTER,
ODOS_LO_UNICHAIN_ROUTER, ODOS_LO_ZKSYNC_ROUTER, ODOS_V2_ARBITRUM_ROUTER,
ODOS_V2_AVALANCHE_ROUTER, ODOS_V2_BASE_ROUTER, ODOS_V2_BSC_ROUTER, ODOS_V2_ETHEREUM_ROUTER,
ODOS_V2_FRAXTAL_ROUTER, ODOS_V2_LINEA_ROUTER, ODOS_V2_MANTLE_ROUTER, ODOS_V2_OP_ROUTER,
ODOS_V2_POLYGON_ROUTER, ODOS_V2_SONIC_ROUTER, ODOS_V2_UNICHAIN_ROUTER, ODOS_V2_ZKSYNC_ROUTER,
ODOS_V3,
};
#[derive(Error, Debug, Clone, PartialEq)]
pub enum OdosChainError {
#[error("Chain {chain:?} is not supported by Odos protocol")]
UnsupportedChain { chain: String },
#[error("Odos Limit Order router is not available on chain {chain:?}")]
LimitOrderNotAvailable { chain: String },
#[error("Odos V2 router is not available on chain {chain:?}")]
V2NotAvailable { chain: String },
#[error("Odos V3 router is not available on chain {chain:?}")]
V3NotAvailable { chain: String },
#[error("Invalid address format: {address}")]
InvalidAddress { address: String },
}
pub type OdosChainResult<T> = Result<T, OdosChainError>;
pub trait OdosChain {
fn lo_router_address(&self) -> OdosChainResult<Address>;
fn v2_router_address(&self) -> OdosChainResult<Address>;
fn v3_router_address(&self) -> OdosChainResult<Address>;
fn supports_odos(&self) -> bool;
fn supports_lo(&self) -> bool;
fn supports_v2(&self) -> bool;
fn supports_v3(&self) -> bool;
fn router_availability(&self) -> RouterAvailability {
RouterAvailability {
limit_order: self.supports_lo(),
v2: self.supports_v2(),
v3: self.supports_v3(),
}
}
fn try_lo_router_address(&self) -> Option<Address> {
self.lo_router_address().ok()
}
fn try_v2_router_address(&self) -> Option<Address> {
self.v2_router_address().ok()
}
fn try_v3_router_address(&self) -> Option<Address> {
self.v3_router_address().ok()
}
}
impl OdosChain for NamedChain {
fn lo_router_address(&self) -> OdosChainResult<Address> {
use NamedChain::*;
if !self.supports_odos() {
return Err(OdosChainError::LimitOrderNotAvailable {
chain: format!("{self:?}"),
});
}
if !self.supports_lo() {
return Err(OdosChainError::LimitOrderNotAvailable {
chain: format!("{self:?}"),
});
}
Ok(match self {
Arbitrum => ODOS_LO_ARBITRUM_ROUTER,
Avalanche => ODOS_LO_AVALANCHE_ROUTER,
Base => ODOS_LO_BASE_ROUTER,
BinanceSmartChain => ODOS_LO_BSC_ROUTER,
Fraxtal => ODOS_LO_FRAXTAL_ROUTER,
Mainnet => ODOS_LO_ETHEREUM_ROUTER,
Optimism => ODOS_LO_OP_ROUTER,
Polygon => ODOS_LO_POLYGON_ROUTER,
Linea => ODOS_LO_LINEA_ROUTER,
Mantle => ODOS_LO_MANTLE_ROUTER,
Sonic => ODOS_LO_SONIC_ROUTER,
ZkSync => ODOS_LO_ZKSYNC_ROUTER,
Unichain => ODOS_LO_UNICHAIN_ROUTER,
_ => {
return Err(OdosChainError::LimitOrderNotAvailable {
chain: format!("{self:?}"),
});
}
})
}
fn v2_router_address(&self) -> OdosChainResult<Address> {
use NamedChain::*;
if !self.supports_odos() {
return Err(OdosChainError::V2NotAvailable {
chain: format!("{self:?}"),
});
}
if !self.supports_v2() {
return self.v3_router_address();
}
Ok(match self {
Arbitrum => ODOS_V2_ARBITRUM_ROUTER,
Avalanche => ODOS_V2_AVALANCHE_ROUTER,
Base => ODOS_V2_BASE_ROUTER,
BinanceSmartChain => ODOS_V2_BSC_ROUTER,
Fraxtal => ODOS_V2_FRAXTAL_ROUTER,
Mainnet => ODOS_V2_ETHEREUM_ROUTER,
Optimism => ODOS_V2_OP_ROUTER,
Polygon => ODOS_V2_POLYGON_ROUTER,
Linea => ODOS_V2_LINEA_ROUTER,
Mantle => ODOS_V2_MANTLE_ROUTER,
Sonic => ODOS_V2_SONIC_ROUTER,
ZkSync => ODOS_V2_ZKSYNC_ROUTER,
Unichain => ODOS_V2_UNICHAIN_ROUTER,
_ => {
return Err(OdosChainError::UnsupportedChain {
chain: format!("{self:?}"),
});
}
})
}
fn v3_router_address(&self) -> OdosChainResult<Address> {
if !self.supports_odos() {
return Err(OdosChainError::V3NotAvailable {
chain: format!("{self:?}"),
});
}
if !self.supports_v3() {
return self.v2_router_address();
}
Ok(ODOS_V3)
}
fn supports_odos(&self) -> bool {
use NamedChain::*;
matches!(
self,
Arbitrum
| Avalanche
| Base
| BinanceSmartChain
| Fraxtal
| Mainnet
| Optimism
| Polygon
| Linea
| Mantle
| Sonic
| ZkSync
| Unichain
)
}
fn supports_lo(&self) -> bool {
use NamedChain::*;
matches!(
self,
Arbitrum
| Avalanche
| Base
| BinanceSmartChain
| Fraxtal
| Mainnet
| Optimism
| Polygon
| Linea
| Mantle
| Sonic
| ZkSync
| Unichain
)
}
fn supports_v2(&self) -> bool {
use NamedChain::*;
matches!(
self,
Arbitrum
| Avalanche
| Base
| BinanceSmartChain
| Fraxtal
| Mainnet
| Optimism
| Polygon
| Linea
| Mantle
| Sonic
| ZkSync
| Unichain
)
}
fn supports_v3(&self) -> bool {
use NamedChain::*;
matches!(
self,
Arbitrum
| Avalanche
| Base
| BinanceSmartChain
| Fraxtal
| Mainnet
| Optimism
| Polygon
| Linea
| Mantle
| Sonic
| ZkSync
| Unichain
)
}
}
pub trait OdosRouterSelection: OdosChain {
fn recommended_router_address(&self) -> OdosChainResult<Address> {
self.v3_router_address()
}
fn router_address_with_fallback(&self) -> OdosChainResult<Address> {
self.v3_router_address()
.or_else(|_| self.v2_router_address())
}
fn router_address_by_preference(&self, prefer_v3: bool) -> OdosChainResult<Address> {
if prefer_v3 {
self.v3_router_address()
} else {
self.v2_router_address()
}
}
}
impl<T: OdosChain> OdosRouterSelection for T {}
#[cfg(test)]
mod tests {
use super::*;
use alloy_chains::NamedChain;
#[test]
fn test_lo_router_addresses() {
let chains = [
NamedChain::Mainnet,
NamedChain::Optimism,
NamedChain::Polygon,
NamedChain::BinanceSmartChain,
];
for chain in chains {
let address = chain.lo_router_address().unwrap();
assert!(address != Address::ZERO);
assert_eq!(address.to_string().len(), 42); }
}
#[test]
fn test_v2_router_addresses() {
let chains = [
NamedChain::Mainnet,
NamedChain::Arbitrum,
NamedChain::Optimism,
NamedChain::Polygon,
NamedChain::Base,
];
for chain in chains {
let address = chain.v2_router_address().unwrap();
assert!(address != Address::ZERO);
assert_eq!(address.to_string().len(), 42); }
}
#[test]
fn test_v3_router_addresses() {
let chains = [
NamedChain::Mainnet,
NamedChain::Arbitrum,
NamedChain::Optimism,
NamedChain::Polygon,
NamedChain::Base,
];
for chain in chains {
let address = chain.v3_router_address().unwrap();
assert_eq!(address, ODOS_V3);
}
}
#[test]
fn test_supports_odos() {
assert!(NamedChain::Mainnet.supports_odos());
assert!(NamedChain::Arbitrum.supports_odos());
assert!(!NamedChain::Sepolia.supports_odos());
}
#[test]
fn test_supports_lo() {
assert!(NamedChain::Mainnet.supports_lo());
assert!(NamedChain::Optimism.supports_lo());
assert!(NamedChain::Polygon.supports_lo());
assert!(NamedChain::BinanceSmartChain.supports_lo());
assert!(NamedChain::Arbitrum.supports_lo());
assert!(NamedChain::Base.supports_lo());
assert!(!NamedChain::Sepolia.supports_lo());
}
#[test]
fn test_supports_v2() {
assert!(NamedChain::Mainnet.supports_v2());
assert!(NamedChain::Arbitrum.supports_v2());
assert!(!NamedChain::Sepolia.supports_v2());
}
#[test]
fn test_supports_v3() {
assert!(NamedChain::Mainnet.supports_v3());
assert!(NamedChain::Arbitrum.supports_v3());
assert!(!NamedChain::Sepolia.supports_v3());
}
#[test]
fn test_router_availability() {
let avail = NamedChain::Mainnet.router_availability();
assert!(avail.limit_order);
assert!(avail.v2);
assert!(avail.v3);
assert_eq!(avail.count(), 3);
let avail = NamedChain::Arbitrum.router_availability();
assert!(avail.limit_order);
assert!(avail.v2);
assert!(avail.v3);
assert_eq!(avail.count(), 3);
let avail = NamedChain::Sepolia.router_availability();
assert!(!avail.limit_order);
assert!(!avail.v2);
assert!(!avail.v3);
assert_eq!(avail.count(), 0);
assert!(!avail.has_any());
}
#[test]
fn test_try_methods() {
assert!(NamedChain::Mainnet.try_lo_router_address().is_some());
assert!(NamedChain::Mainnet.try_v2_router_address().is_some());
assert!(NamedChain::Mainnet.try_v3_router_address().is_some());
assert!(NamedChain::Sepolia.try_lo_router_address().is_none());
assert!(NamedChain::Sepolia.try_v2_router_address().is_none());
assert!(NamedChain::Sepolia.try_v3_router_address().is_none());
assert!(NamedChain::Arbitrum.try_lo_router_address().is_some());
assert!(NamedChain::Arbitrum.try_v2_router_address().is_some());
assert!(NamedChain::Arbitrum.try_v3_router_address().is_some());
}
#[test]
fn test_router_selection() {
let chain = NamedChain::Mainnet;
assert_eq!(
chain.recommended_router_address().unwrap(),
chain.v3_router_address().unwrap()
);
assert_eq!(
chain.router_address_with_fallback().unwrap(),
chain.v3_router_address().unwrap()
);
assert_eq!(
chain.router_address_by_preference(true).unwrap(),
chain.v3_router_address().unwrap()
);
assert_eq!(
chain.router_address_by_preference(false).unwrap(),
chain.v2_router_address().unwrap()
);
}
#[test]
fn test_error_handling() {
let result = NamedChain::Sepolia.lo_router_address();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
OdosChainError::LimitOrderNotAvailable { .. }
));
let result = NamedChain::Sepolia.v2_router_address();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
OdosChainError::V2NotAvailable { .. }
));
let result = NamedChain::Sepolia.v3_router_address();
assert!(result.is_err());
assert!(matches!(
result.unwrap_err(),
OdosChainError::V3NotAvailable { .. }
));
}
}