1use serde::{Deserialize, Serialize};
13use std::fmt;
14use thiserror::Error;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
43#[serde(rename_all = "snake_case")]
44#[repr(u32)]
45#[non_exhaustive]
46pub enum DomainId {
47 Ethereum = 0,
49 Avalanche = 1,
51 Optimism = 2,
53 Arbitrum = 3,
55 Solana = 5,
57 Base = 6,
59 Polygon = 7,
61 Unichain = 10,
63 Linea = 11,
65 Codex = 12,
67 Sonic = 13,
69 WorldChain = 14,
71 Monad = 15,
73 Sei = 16,
75 BnbSmartChain = 17,
77 Xdc = 18,
79 HyperEvm = 19,
81 Ink = 21,
83 Plume = 22,
85 StarknetTestnet = 25,
87 ArcTestnet = 26,
89}
90
91impl DomainId {
92 #[inline]
103 #[must_use]
104 pub const fn as_u32(self) -> u32 {
105 self as u32
106 }
107
108 #[inline]
121 #[must_use]
122 pub const fn from_u32(value: u32) -> Option<Self> {
123 match value {
124 0 => Some(Self::Ethereum),
125 1 => Some(Self::Avalanche),
126 2 => Some(Self::Optimism),
127 3 => Some(Self::Arbitrum),
128 5 => Some(Self::Solana),
129 6 => Some(Self::Base),
130 7 => Some(Self::Polygon),
131 10 => Some(Self::Unichain),
132 11 => Some(Self::Linea),
133 12 => Some(Self::Codex),
134 13 => Some(Self::Sonic),
135 14 => Some(Self::WorldChain),
136 15 => Some(Self::Monad),
137 16 => Some(Self::Sei),
138 17 => Some(Self::BnbSmartChain),
139 18 => Some(Self::Xdc),
140 19 => Some(Self::HyperEvm),
141 21 => Some(Self::Ink),
142 22 => Some(Self::Plume),
143 25 => Some(Self::StarknetTestnet),
144 26 => Some(Self::ArcTestnet),
145 _ => None,
146 }
147 }
148
149 #[inline]
161 #[must_use]
162 pub const fn name(self) -> &'static str {
163 match self {
164 Self::Ethereum => "Ethereum",
165 Self::Avalanche => "Avalanche",
166 Self::Optimism => "Optimism",
167 Self::Arbitrum => "Arbitrum",
168 Self::Solana => "Solana",
169 Self::Base => "Base",
170 Self::Polygon => "Polygon",
171 Self::Unichain => "Unichain",
172 Self::Linea => "Linea",
173 Self::Codex => "Codex",
174 Self::Sonic => "Sonic",
175 Self::WorldChain => "World Chain",
176 Self::Monad => "Monad",
177 Self::Sei => "Sei",
178 Self::BnbSmartChain => "BNB Smart Chain",
179 Self::Xdc => "XDC",
180 Self::HyperEvm => "HyperEVM",
181 Self::Ink => "Ink",
182 Self::Plume => "Plume",
183 Self::StarknetTestnet => "Starknet Testnet",
184 Self::ArcTestnet => "Arc Testnet",
185 }
186 }
187
188 #[inline]
193 #[must_use]
194 pub const fn is_evm(self) -> bool {
195 !matches!(self, Self::Solana | Self::StarknetTestnet)
196 }
197}
198
199impl From<DomainId> for u32 {
200 #[inline]
201 fn from(domain: DomainId) -> Self {
202 domain.as_u32()
203 }
204}
205
206impl TryFrom<u32> for DomainId {
207 type Error = InvalidDomainId;
208
209 #[inline]
210 fn try_from(value: u32) -> Result<Self, Self::Error> {
211 Self::from_u32(value).ok_or(InvalidDomainId(value))
212 }
213}
214
215impl fmt::Display for DomainId {
216 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 write!(f, "{} ({})", self.name(), self.as_u32())
218 }
219}
220
221#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
223#[error("invalid CCTP domain ID: {0}")]
224pub struct InvalidDomainId(pub u32);
225
226#[cfg(test)]
227mod tests {
228 use super::*;
229
230 #[test]
231 fn test_domain_id_values() {
232 assert_eq!(DomainId::Ethereum.as_u32(), 0);
234 assert_eq!(DomainId::Avalanche.as_u32(), 1);
235 assert_eq!(DomainId::Optimism.as_u32(), 2);
236 assert_eq!(DomainId::Arbitrum.as_u32(), 3);
237 assert_eq!(DomainId::Base.as_u32(), 6);
238 assert_eq!(DomainId::Polygon.as_u32(), 7);
239 assert_eq!(DomainId::Unichain.as_u32(), 10);
240
241 assert_eq!(DomainId::Solana.as_u32(), 5);
243 assert_eq!(DomainId::Linea.as_u32(), 11);
244 assert_eq!(DomainId::Codex.as_u32(), 12);
245 assert_eq!(DomainId::Sonic.as_u32(), 13);
246 assert_eq!(DomainId::WorldChain.as_u32(), 14);
247 assert_eq!(DomainId::Monad.as_u32(), 15);
248 assert_eq!(DomainId::Sei.as_u32(), 16);
249 assert_eq!(DomainId::BnbSmartChain.as_u32(), 17);
250 assert_eq!(DomainId::Xdc.as_u32(), 18);
251 assert_eq!(DomainId::HyperEvm.as_u32(), 19);
252 assert_eq!(DomainId::Ink.as_u32(), 21);
253 assert_eq!(DomainId::Plume.as_u32(), 22);
254 assert_eq!(DomainId::StarknetTestnet.as_u32(), 25);
255 assert_eq!(DomainId::ArcTestnet.as_u32(), 26);
256 }
257
258 #[test]
259 fn test_from_u32_valid() {
260 assert_eq!(DomainId::from_u32(0), Some(DomainId::Ethereum));
262 assert_eq!(DomainId::from_u32(1), Some(DomainId::Avalanche));
263 assert_eq!(DomainId::from_u32(2), Some(DomainId::Optimism));
264 assert_eq!(DomainId::from_u32(3), Some(DomainId::Arbitrum));
265 assert_eq!(DomainId::from_u32(6), Some(DomainId::Base));
266 assert_eq!(DomainId::from_u32(7), Some(DomainId::Polygon));
267 assert_eq!(DomainId::from_u32(10), Some(DomainId::Unichain));
268
269 assert_eq!(DomainId::from_u32(11), Some(DomainId::Linea));
271 assert_eq!(DomainId::from_u32(13), Some(DomainId::Sonic));
272 assert_eq!(DomainId::from_u32(16), Some(DomainId::Sei));
273 assert_eq!(DomainId::from_u32(17), Some(DomainId::BnbSmartChain));
274
275 assert_eq!(DomainId::from_u32(5), Some(DomainId::Solana));
277 assert_eq!(DomainId::from_u32(12), Some(DomainId::Codex));
278 assert_eq!(DomainId::from_u32(14), Some(DomainId::WorldChain));
279 assert_eq!(DomainId::from_u32(15), Some(DomainId::Monad));
280 assert_eq!(DomainId::from_u32(18), Some(DomainId::Xdc));
281 assert_eq!(DomainId::from_u32(19), Some(DomainId::HyperEvm));
282 assert_eq!(DomainId::from_u32(21), Some(DomainId::Ink));
283 assert_eq!(DomainId::from_u32(22), Some(DomainId::Plume));
284 assert_eq!(DomainId::from_u32(25), Some(DomainId::StarknetTestnet));
285 assert_eq!(DomainId::from_u32(26), Some(DomainId::ArcTestnet));
286 }
287
288 #[test]
289 fn test_from_u32_invalid() {
290 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); }
300
301 #[test]
302 fn test_try_from_valid() {
303 assert_eq!(DomainId::try_from(0).unwrap(), DomainId::Ethereum);
304 assert_eq!(DomainId::try_from(3).unwrap(), DomainId::Arbitrum);
305 }
306
307 #[test]
308 fn test_try_from_invalid() {
309 assert!(DomainId::try_from(999).is_err());
310 let err = DomainId::try_from(999).unwrap_err();
311 assert_eq!(err, InvalidDomainId(999));
312 }
313
314 #[test]
315 fn test_display() {
316 assert_eq!(format!("{}", DomainId::Ethereum), "Ethereum (0)");
317 assert_eq!(format!("{}", DomainId::Arbitrum), "Arbitrum (3)");
318 assert_eq!(format!("{}", DomainId::Base), "Base (6)");
319 }
320
321 #[test]
322 fn test_name() {
323 assert_eq!(DomainId::Ethereum.name(), "Ethereum");
324 assert_eq!(DomainId::Arbitrum.name(), "Arbitrum");
325 assert_eq!(DomainId::Avalanche.name(), "Avalanche");
326 }
327
328 #[test]
329 fn test_is_evm() {
330 assert!(DomainId::Ethereum.is_evm());
331 assert!(DomainId::Base.is_evm());
332 assert!(!DomainId::Solana.is_evm());
333 assert!(!DomainId::StarknetTestnet.is_evm());
334 }
335
336 #[test]
337 fn test_conversion_roundtrip() {
338 for domain in [
339 DomainId::Ethereum,
341 DomainId::Avalanche,
342 DomainId::Optimism,
343 DomainId::Arbitrum,
344 DomainId::Base,
345 DomainId::Polygon,
346 DomainId::Unichain,
347 DomainId::Solana,
349 DomainId::Linea,
350 DomainId::Codex,
351 DomainId::Sonic,
352 DomainId::WorldChain,
353 DomainId::Monad,
354 DomainId::Sei,
355 DomainId::BnbSmartChain,
356 DomainId::Xdc,
357 DomainId::HyperEvm,
358 DomainId::Ink,
359 DomainId::Plume,
360 DomainId::StarknetTestnet,
361 DomainId::ArcTestnet,
362 ] {
363 let value: u32 = domain.into();
364 let parsed = DomainId::try_from(value).unwrap();
365 assert_eq!(domain, parsed);
366 }
367 }
368}