1use std::{fmt, str::FromStr};
6
7use alloy_chains::NamedChain;
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9
10use crate::{OdosChain, OdosChainError, OdosChainResult};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Chain(NamedChain);
40
41impl Chain {
42 pub const fn ethereum() -> Self {
53 Self(NamedChain::Mainnet)
54 }
55
56 pub const fn arbitrum() -> Self {
67 Self(NamedChain::Arbitrum)
68 }
69
70 pub const fn optimism() -> Self {
81 Self(NamedChain::Optimism)
82 }
83
84 pub const fn polygon() -> Self {
95 Self(NamedChain::Polygon)
96 }
97
98 pub const fn base() -> Self {
109 Self(NamedChain::Base)
110 }
111
112 pub const fn bsc() -> Self {
123 Self(NamedChain::BinanceSmartChain)
124 }
125
126 pub const fn avalanche() -> Self {
137 Self(NamedChain::Avalanche)
138 }
139
140 pub const fn linea() -> Self {
151 Self(NamedChain::Linea)
152 }
153
154 pub const fn zksync() -> Self {
165 Self(NamedChain::ZkSync)
166 }
167
168 pub const fn mantle() -> Self {
179 Self(NamedChain::Mantle)
180 }
181
182 pub const fn fraxtal() -> Self {
193 Self(NamedChain::Fraxtal)
194 }
195
196 pub const fn sonic() -> Self {
207 Self(NamedChain::Sonic)
208 }
209
210 pub const fn unichain() -> Self {
221 Self(NamedChain::Unichain)
222 }
223
224 pub fn from_chain_id(id: u64) -> OdosChainResult<Self> {
249 let chain = NamedChain::try_from(id).map_err(|_| OdosChainError::UnsupportedChain {
250 chain: format!("Chain ID {id}"),
251 })?;
252
253 if chain.supports_odos() {
254 Ok(Self(chain))
255 } else {
256 Err(OdosChainError::UnsupportedChain {
257 chain: format!("Chain ID {id}"),
258 })
259 }
260 }
261
262 pub fn id(&self) -> u64 {
274 self.0.into()
275 }
276
277 pub const fn inner(&self) -> NamedChain {
289 self.0
290 }
291
292 pub fn from_name(name: &str) -> OdosChainResult<Self> {
297 let normalized = normalize_chain_name(name);
298
299 if let Ok(chain_id) = normalized.parse::<u64>() {
300 return Self::from_chain_id(chain_id);
301 }
302
303 match normalized.as_str() {
304 "mainnet" | "ethereum" | "eth" | "ethereum mainnet" => Ok(Self::ethereum()),
305 "arbitrum" | "arb" | "arbitrum one" => Ok(Self::arbitrum()),
306 "optimism" | "op" => Ok(Self::optimism()),
307 "polygon" | "matic" | "polygon pos" => Ok(Self::polygon()),
308 "base" => Ok(Self::base()),
309 "bsc" | "bnb" | "bnb smart chain" | "binance smart chain" => Ok(Self::bsc()),
310 "avalanche" | "avax" | "avalanche c chain" => Ok(Self::avalanche()),
311 "linea" => Ok(Self::linea()),
312 "zksync" | "zk sync" | "zksync era" => Ok(Self::zksync()),
313 "mantle" => Ok(Self::mantle()),
314 "fraxtal" => Ok(Self::fraxtal()),
315 "sonic" => Ok(Self::sonic()),
316 "unichain" => Ok(Self::unichain()),
317 _ => Err(OdosChainError::UnsupportedChain {
318 chain: name.trim().to_string(),
319 }),
320 }
321 }
322}
323
324fn normalize_chain_name(name: &str) -> String {
325 name.trim()
326 .to_ascii_lowercase()
327 .replace(['-', '_'], " ")
328 .split_whitespace()
329 .collect::<Vec<_>>()
330 .join(" ")
331}
332
333impl fmt::Display for Chain {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 write!(f, "{}", self.0)
336 }
337}
338
339impl From<NamedChain> for Chain {
340 fn from(chain: NamedChain) -> Self {
341 Self(chain)
342 }
343}
344
345impl From<Chain> for NamedChain {
346 fn from(chain: Chain) -> Self {
347 chain.0
348 }
349}
350
351impl From<Chain> for u64 {
352 fn from(chain: Chain) -> Self {
353 chain.0.into()
354 }
355}
356
357impl FromStr for Chain {
358 type Err = OdosChainError;
359
360 fn from_str(s: &str) -> Result<Self, Self::Err> {
361 Self::from_name(s)
362 }
363}
364
365impl Serialize for Chain {
367 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
368 where
369 S: Serializer,
370 {
371 let chain_id: u64 = self.0.into();
372 chain_id.serialize(serializer)
373 }
374}
375
376impl<'de> Deserialize<'de> for Chain {
377 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
378 where
379 D: Deserializer<'de>,
380 {
381 let chain_id = u64::deserialize(deserializer)?;
382 Self::from_chain_id(chain_id).map_err(serde::de::Error::custom)
383 }
384}
385
386impl OdosChain for Chain {
388 fn lo_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
389 self.0.lo_router_address()
390 }
391
392 fn v2_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
393 self.0.v2_router_address()
394 }
395
396 fn v3_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
397 self.0.v3_router_address()
398 }
399
400 fn supports_odos(&self) -> bool {
401 self.0.supports_odos()
402 }
403
404 fn supports_lo(&self) -> bool {
405 self.0.supports_lo()
406 }
407
408 fn supports_v2(&self) -> bool {
409 self.0.supports_v2()
410 }
411
412 fn supports_v3(&self) -> bool {
413 self.0.supports_v3()
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_chain_constructors() {
423 assert_eq!(Chain::ethereum().id(), 1);
424 assert_eq!(Chain::arbitrum().id(), 42161);
425 assert_eq!(Chain::optimism().id(), 10);
426 assert_eq!(Chain::polygon().id(), 137);
427 assert_eq!(Chain::base().id(), 8453);
428 assert_eq!(Chain::bsc().id(), 56);
429 assert_eq!(Chain::avalanche().id(), 43114);
430 assert_eq!(Chain::linea().id(), 59144);
431 assert_eq!(Chain::zksync().id(), 324);
432 assert_eq!(Chain::mantle().id(), 5000);
433 assert_eq!(Chain::fraxtal().id(), 252);
434 assert_eq!(Chain::sonic().id(), 146);
435 assert_eq!(Chain::unichain().id(), 130);
436 }
437
438 #[test]
439 fn test_from_chain_id() {
440 assert_eq!(Chain::from_chain_id(1).unwrap().id(), 1);
441 assert_eq!(Chain::from_chain_id(42161).unwrap().id(), 42161);
442 assert_eq!(Chain::from_chain_id(8453).unwrap().id(), 8453);
443
444 assert!(Chain::from_chain_id(999999).is_err());
446 assert!(Chain::from_chain_id(11155111).is_err());
447 }
448
449 #[test]
450 fn test_from_name() {
451 assert_eq!(Chain::from_name("ethereum").unwrap(), Chain::ethereum());
452 assert_eq!(Chain::from_name("mainnet").unwrap(), Chain::ethereum());
453 assert_eq!(Chain::from_name("arb").unwrap(), Chain::arbitrum());
454 assert_eq!(Chain::from_name("op").unwrap(), Chain::optimism());
455 assert_eq!(Chain::from_name("bnb smart chain").unwrap(), Chain::bsc());
456 assert_eq!(Chain::from_name("8453").unwrap(), Chain::base());
457 assert!(Chain::from_name("sepolia").is_err());
458 }
459
460 #[test]
461 fn test_inner() {
462 assert_eq!(Chain::ethereum().inner(), NamedChain::Mainnet);
463 assert_eq!(Chain::arbitrum().inner(), NamedChain::Arbitrum);
464 assert_eq!(Chain::base().inner(), NamedChain::Base);
465 }
466
467 #[test]
468 fn test_display() {
469 assert_eq!(format!("{}", Chain::ethereum()), "mainnet");
470 assert_eq!(format!("{}", Chain::arbitrum()), "arbitrum");
471 assert_eq!(format!("{}", Chain::base()), "base");
472 }
473
474 #[test]
475 fn test_conversions() {
476 let chain: Chain = NamedChain::Mainnet.into();
478 assert_eq!(chain.id(), 1);
479
480 let named: NamedChain = Chain::ethereum().into();
482 assert_eq!(named, NamedChain::Mainnet);
483
484 let id: u64 = Chain::ethereum().into();
486 assert_eq!(id, 1);
487 }
488
489 #[test]
490 fn test_odos_chain_trait() {
491 let chain = Chain::ethereum();
492
493 assert!(chain.supports_odos());
495 assert!(chain.supports_v2());
496 assert!(chain.supports_v3());
497 assert!(chain.v2_router_address().is_ok());
498 assert!(chain.v3_router_address().is_ok());
499 }
500
501 #[test]
502 fn test_equality() {
503 assert_eq!(Chain::ethereum(), Chain::ethereum());
504 assert_ne!(Chain::ethereum(), Chain::arbitrum());
505 }
506
507 #[test]
508 fn test_serialization() {
509 let chain = Chain::ethereum();
510
511 let json = serde_json::to_string(&chain).unwrap();
513 assert_eq!(json, "1"); let deserialized: Chain = serde_json::from_str(&json).unwrap();
517 assert_eq!(deserialized, chain);
518
519 assert_eq!(serde_json::to_string(&Chain::arbitrum()).unwrap(), "42161");
521 assert_eq!(serde_json::to_string(&Chain::base()).unwrap(), "8453");
522 }
523}