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 const fn is_op_stack(&self) -> bool {
306 matches!(
307 self.0,
308 NamedChain::Optimism | NamedChain::Base | NamedChain::Fraxtal
309 )
310 }
311
312 pub fn from_name(name: &str) -> OdosChainResult<Self> {
317 let normalized = normalize_chain_name(name);
318
319 if let Ok(chain_id) = normalized.parse::<u64>() {
320 return Self::from_chain_id(chain_id);
321 }
322
323 match normalized.as_str() {
324 "mainnet" | "ethereum" | "eth" | "ethereum mainnet" => Ok(Self::ethereum()),
325 "arbitrum" | "arb" | "arbitrum one" => Ok(Self::arbitrum()),
326 "optimism" | "op" => Ok(Self::optimism()),
327 "polygon" | "matic" | "polygon pos" => Ok(Self::polygon()),
328 "base" => Ok(Self::base()),
329 "bsc" | "bnb" | "bnb smart chain" | "binance smart chain" => Ok(Self::bsc()),
330 "avalanche" | "avax" | "avalanche c chain" => Ok(Self::avalanche()),
331 "linea" => Ok(Self::linea()),
332 "zksync" | "zk sync" | "zksync era" => Ok(Self::zksync()),
333 "mantle" => Ok(Self::mantle()),
334 "fraxtal" => Ok(Self::fraxtal()),
335 "sonic" => Ok(Self::sonic()),
336 "unichain" => Ok(Self::unichain()),
337 _ => Err(OdosChainError::UnsupportedChain {
338 chain: name.trim().to_string(),
339 }),
340 }
341 }
342}
343
344fn normalize_chain_name(name: &str) -> String {
345 name.trim()
346 .to_ascii_lowercase()
347 .replace(['-', '_'], " ")
348 .split_whitespace()
349 .collect::<Vec<_>>()
350 .join(" ")
351}
352
353impl fmt::Display for Chain {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 write!(f, "{}", self.0)
356 }
357}
358
359impl From<NamedChain> for Chain {
360 fn from(chain: NamedChain) -> Self {
361 Self(chain)
362 }
363}
364
365impl From<Chain> for NamedChain {
366 fn from(chain: Chain) -> Self {
367 chain.0
368 }
369}
370
371impl From<Chain> for u64 {
372 fn from(chain: Chain) -> Self {
373 chain.0.into()
374 }
375}
376
377impl FromStr for Chain {
378 type Err = OdosChainError;
379
380 fn from_str(s: &str) -> Result<Self, Self::Err> {
381 Self::from_name(s)
382 }
383}
384
385impl Serialize for Chain {
387 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
388 where
389 S: Serializer,
390 {
391 let chain_id: u64 = self.0.into();
392 chain_id.serialize(serializer)
393 }
394}
395
396impl<'de> Deserialize<'de> for Chain {
397 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
398 where
399 D: Deserializer<'de>,
400 {
401 let chain_id = u64::deserialize(deserializer)?;
402 Self::from_chain_id(chain_id).map_err(serde::de::Error::custom)
403 }
404}
405
406impl OdosChain for Chain {
408 fn lo_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
409 self.0.lo_router_address()
410 }
411
412 fn v2_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
413 self.0.v2_router_address()
414 }
415
416 fn v3_router_address(&self) -> OdosChainResult<alloy_primitives::Address> {
417 self.0.v3_router_address()
418 }
419
420 fn supports_odos(&self) -> bool {
421 self.0.supports_odos()
422 }
423
424 fn supports_lo(&self) -> bool {
425 self.0.supports_lo()
426 }
427
428 fn supports_v2(&self) -> bool {
429 self.0.supports_v2()
430 }
431
432 fn supports_v3(&self) -> bool {
433 self.0.supports_v3()
434 }
435}
436
437#[cfg(test)]
438mod tests {
439 use super::*;
440
441 #[test]
442 fn test_chain_constructors() {
443 assert_eq!(Chain::ethereum().id(), 1);
444 assert_eq!(Chain::arbitrum().id(), 42161);
445 assert_eq!(Chain::optimism().id(), 10);
446 assert_eq!(Chain::polygon().id(), 137);
447 assert_eq!(Chain::base().id(), 8453);
448 assert_eq!(Chain::bsc().id(), 56);
449 assert_eq!(Chain::avalanche().id(), 43114);
450 assert_eq!(Chain::linea().id(), 59144);
451 assert_eq!(Chain::zksync().id(), 324);
452 assert_eq!(Chain::mantle().id(), 5000);
453 assert_eq!(Chain::fraxtal().id(), 252);
454 assert_eq!(Chain::sonic().id(), 146);
455 assert_eq!(Chain::unichain().id(), 130);
456 }
457
458 #[test]
459 fn test_from_chain_id() {
460 assert_eq!(Chain::from_chain_id(1).unwrap().id(), 1);
461 assert_eq!(Chain::from_chain_id(42161).unwrap().id(), 42161);
462 assert_eq!(Chain::from_chain_id(8453).unwrap().id(), 8453);
463
464 assert!(Chain::from_chain_id(999999).is_err());
466 assert!(Chain::from_chain_id(11155111).is_err());
467 }
468
469 #[test]
470 fn test_from_name() {
471 assert_eq!(Chain::from_name("ethereum").unwrap(), Chain::ethereum());
472 assert_eq!(Chain::from_name("mainnet").unwrap(), Chain::ethereum());
473 assert_eq!(Chain::from_name("arb").unwrap(), Chain::arbitrum());
474 assert_eq!(Chain::from_name("op").unwrap(), Chain::optimism());
475 assert_eq!(Chain::from_name("bnb smart chain").unwrap(), Chain::bsc());
476 assert_eq!(Chain::from_name("8453").unwrap(), Chain::base());
477 assert!(Chain::from_name("sepolia").is_err());
478 }
479
480 #[test]
481 fn test_inner() {
482 assert_eq!(Chain::ethereum().inner(), NamedChain::Mainnet);
483 assert_eq!(Chain::arbitrum().inner(), NamedChain::Arbitrum);
484 assert_eq!(Chain::base().inner(), NamedChain::Base);
485 }
486
487 #[test]
488 fn test_display() {
489 assert_eq!(format!("{}", Chain::ethereum()), "mainnet");
490 assert_eq!(format!("{}", Chain::arbitrum()), "arbitrum");
491 assert_eq!(format!("{}", Chain::base()), "base");
492 }
493
494 #[test]
495 fn test_conversions() {
496 let chain: Chain = NamedChain::Mainnet.into();
498 assert_eq!(chain.id(), 1);
499
500 let named: NamedChain = Chain::ethereum().into();
502 assert_eq!(named, NamedChain::Mainnet);
503
504 let id: u64 = Chain::ethereum().into();
506 assert_eq!(id, 1);
507 }
508
509 #[test]
510 fn test_odos_chain_trait() {
511 let chain = Chain::ethereum();
512
513 assert!(chain.supports_odos());
515 assert!(chain.supports_v2());
516 assert!(chain.supports_v3());
517 assert!(chain.v2_router_address().is_ok());
518 assert!(chain.v3_router_address().is_ok());
519 }
520
521 #[test]
522 fn test_is_op_stack() {
523 assert!(Chain::optimism().is_op_stack());
524 assert!(Chain::base().is_op_stack());
525 assert!(Chain::fraxtal().is_op_stack());
526
527 assert!(!Chain::ethereum().is_op_stack());
528 assert!(!Chain::arbitrum().is_op_stack());
529 assert!(!Chain::polygon().is_op_stack());
530 }
531
532 #[test]
533 fn test_equality() {
534 assert_eq!(Chain::ethereum(), Chain::ethereum());
535 assert_ne!(Chain::ethereum(), Chain::arbitrum());
536 }
537
538 #[test]
539 fn test_serialization() {
540 let chain = Chain::ethereum();
541
542 let json = serde_json::to_string(&chain).unwrap();
544 assert_eq!(json, "1"); let deserialized: Chain = serde_json::from_str(&json).unwrap();
548 assert_eq!(deserialized, chain);
549
550 assert_eq!(serde_json::to_string(&Chain::arbitrum()).unwrap(), "42161");
552 assert_eq!(serde_json::to_string(&Chain::base()).unwrap(), "8453");
553 }
554}