use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[repr(u32)]
#[non_exhaustive]
pub enum DomainId {
Ethereum = 0,
Avalanche = 1,
Optimism = 2,
Arbitrum = 3,
Solana = 5,
Base = 6,
Polygon = 7,
Unichain = 10,
Linea = 11,
Codex = 12,
Sonic = 13,
WorldChain = 14,
Monad = 15,
Sei = 16,
BnbSmartChain = 17,
Xdc = 18,
HyperEvm = 19,
Ink = 21,
Plume = 22,
StarknetTestnet = 25,
ArcTestnet = 26,
}
impl DomainId {
#[inline]
pub const fn as_u32(self) -> u32 {
self as u32
}
#[inline]
pub const fn from_u32(value: u32) -> Option<Self> {
match value {
0 => Some(Self::Ethereum),
1 => Some(Self::Avalanche),
2 => Some(Self::Optimism),
3 => Some(Self::Arbitrum),
5 => Some(Self::Solana),
6 => Some(Self::Base),
7 => Some(Self::Polygon),
10 => Some(Self::Unichain),
11 => Some(Self::Linea),
12 => Some(Self::Codex),
13 => Some(Self::Sonic),
14 => Some(Self::WorldChain),
15 => Some(Self::Monad),
16 => Some(Self::Sei),
17 => Some(Self::BnbSmartChain),
18 => Some(Self::Xdc),
19 => Some(Self::HyperEvm),
21 => Some(Self::Ink),
22 => Some(Self::Plume),
25 => Some(Self::StarknetTestnet),
26 => Some(Self::ArcTestnet),
_ => None,
}
}
#[inline]
pub const fn name(self) -> &'static str {
match self {
Self::Ethereum => "Ethereum",
Self::Avalanche => "Avalanche",
Self::Optimism => "Optimism",
Self::Arbitrum => "Arbitrum",
Self::Solana => "Solana",
Self::Base => "Base",
Self::Polygon => "Polygon",
Self::Unichain => "Unichain",
Self::Linea => "Linea",
Self::Codex => "Codex",
Self::Sonic => "Sonic",
Self::WorldChain => "World Chain",
Self::Monad => "Monad",
Self::Sei => "Sei",
Self::BnbSmartChain => "BNB Smart Chain",
Self::Xdc => "XDC",
Self::HyperEvm => "HyperEVM",
Self::Ink => "Ink",
Self::Plume => "Plume",
Self::StarknetTestnet => "Starknet Testnet",
Self::ArcTestnet => "Arc Testnet",
}
}
#[inline]
pub const fn is_evm(self) -> bool {
!matches!(self, Self::Solana | Self::StarknetTestnet)
}
}
impl From<DomainId> for u32 {
#[inline]
fn from(domain: DomainId) -> Self {
domain.as_u32()
}
}
impl TryFrom<u32> for DomainId {
type Error = InvalidDomainId;
#[inline]
fn try_from(value: u32) -> Result<Self, Self::Error> {
Self::from_u32(value).ok_or(InvalidDomainId(value))
}
}
impl fmt::Display for DomainId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} ({})", self.name(), self.as_u32())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct InvalidDomainId(pub u32);
impl fmt::Display for InvalidDomainId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid CCTP domain ID: {}", self.0)
}
}
impl std::error::Error for InvalidDomainId {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_domain_id_values() {
assert_eq!(DomainId::Ethereum.as_u32(), 0);
assert_eq!(DomainId::Avalanche.as_u32(), 1);
assert_eq!(DomainId::Optimism.as_u32(), 2);
assert_eq!(DomainId::Arbitrum.as_u32(), 3);
assert_eq!(DomainId::Base.as_u32(), 6);
assert_eq!(DomainId::Polygon.as_u32(), 7);
assert_eq!(DomainId::Unichain.as_u32(), 10);
assert_eq!(DomainId::Solana.as_u32(), 5);
assert_eq!(DomainId::Linea.as_u32(), 11);
assert_eq!(DomainId::Codex.as_u32(), 12);
assert_eq!(DomainId::Sonic.as_u32(), 13);
assert_eq!(DomainId::WorldChain.as_u32(), 14);
assert_eq!(DomainId::Monad.as_u32(), 15);
assert_eq!(DomainId::Sei.as_u32(), 16);
assert_eq!(DomainId::BnbSmartChain.as_u32(), 17);
assert_eq!(DomainId::Xdc.as_u32(), 18);
assert_eq!(DomainId::HyperEvm.as_u32(), 19);
assert_eq!(DomainId::Ink.as_u32(), 21);
assert_eq!(DomainId::Plume.as_u32(), 22);
assert_eq!(DomainId::StarknetTestnet.as_u32(), 25);
assert_eq!(DomainId::ArcTestnet.as_u32(), 26);
}
#[test]
fn test_from_u32_valid() {
assert_eq!(DomainId::from_u32(0), Some(DomainId::Ethereum));
assert_eq!(DomainId::from_u32(1), Some(DomainId::Avalanche));
assert_eq!(DomainId::from_u32(2), Some(DomainId::Optimism));
assert_eq!(DomainId::from_u32(3), Some(DomainId::Arbitrum));
assert_eq!(DomainId::from_u32(6), Some(DomainId::Base));
assert_eq!(DomainId::from_u32(7), Some(DomainId::Polygon));
assert_eq!(DomainId::from_u32(10), Some(DomainId::Unichain));
assert_eq!(DomainId::from_u32(11), Some(DomainId::Linea));
assert_eq!(DomainId::from_u32(13), Some(DomainId::Sonic));
assert_eq!(DomainId::from_u32(16), Some(DomainId::Sei));
assert_eq!(DomainId::from_u32(17), Some(DomainId::BnbSmartChain));
assert_eq!(DomainId::from_u32(5), Some(DomainId::Solana));
assert_eq!(DomainId::from_u32(12), Some(DomainId::Codex));
assert_eq!(DomainId::from_u32(14), Some(DomainId::WorldChain));
assert_eq!(DomainId::from_u32(15), Some(DomainId::Monad));
assert_eq!(DomainId::from_u32(18), Some(DomainId::Xdc));
assert_eq!(DomainId::from_u32(19), Some(DomainId::HyperEvm));
assert_eq!(DomainId::from_u32(21), Some(DomainId::Ink));
assert_eq!(DomainId::from_u32(22), Some(DomainId::Plume));
assert_eq!(DomainId::from_u32(25), Some(DomainId::StarknetTestnet));
assert_eq!(DomainId::from_u32(26), Some(DomainId::ArcTestnet));
}
#[test]
fn test_from_u32_invalid() {
assert_eq!(DomainId::from_u32(4), None); assert_eq!(DomainId::from_u32(8), None); assert_eq!(DomainId::from_u32(9), None); assert_eq!(DomainId::from_u32(20), None); assert_eq!(DomainId::from_u32(23), None); assert_eq!(DomainId::from_u32(24), None); assert_eq!(DomainId::from_u32(27), None); assert_eq!(DomainId::from_u32(999), None); }
#[test]
fn test_try_from_valid() {
assert_eq!(DomainId::try_from(0).unwrap(), DomainId::Ethereum);
assert_eq!(DomainId::try_from(3).unwrap(), DomainId::Arbitrum);
}
#[test]
fn test_try_from_invalid() {
assert!(DomainId::try_from(999).is_err());
let err = DomainId::try_from(999).unwrap_err();
assert_eq!(err, InvalidDomainId(999));
}
#[test]
fn test_display() {
assert_eq!(format!("{}", DomainId::Ethereum), "Ethereum (0)");
assert_eq!(format!("{}", DomainId::Arbitrum), "Arbitrum (3)");
assert_eq!(format!("{}", DomainId::Base), "Base (6)");
}
#[test]
fn test_name() {
assert_eq!(DomainId::Ethereum.name(), "Ethereum");
assert_eq!(DomainId::Arbitrum.name(), "Arbitrum");
assert_eq!(DomainId::Avalanche.name(), "Avalanche");
}
#[test]
fn test_is_evm() {
assert!(DomainId::Ethereum.is_evm());
assert!(DomainId::Base.is_evm());
assert!(!DomainId::Solana.is_evm());
assert!(!DomainId::StarknetTestnet.is_evm());
}
#[test]
fn test_conversion_roundtrip() {
for domain in [
DomainId::Ethereum,
DomainId::Avalanche,
DomainId::Optimism,
DomainId::Arbitrum,
DomainId::Base,
DomainId::Polygon,
DomainId::Unichain,
DomainId::Solana,
DomainId::Linea,
DomainId::Codex,
DomainId::Sonic,
DomainId::WorldChain,
DomainId::Monad,
DomainId::Sei,
DomainId::BnbSmartChain,
DomainId::Xdc,
DomainId::HyperEvm,
DomainId::Ink,
DomainId::Plume,
DomainId::StarknetTestnet,
DomainId::ArcTestnet,
] {
let value: u32 = domain.into();
let parsed = DomainId::try_from(value).unwrap();
assert_eq!(domain, parsed);
}
}
}