1use std::fmt;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34#[repr(u32)]
35#[non_exhaustive]
36pub enum DomainId {
37 Ethereum = 0,
39 Avalanche = 1,
41 Optimism = 2,
43 Arbitrum = 3,
45 Solana = 5,
47 Base = 6,
49 Polygon = 7,
51 Unichain = 10,
53 Linea = 11,
55 Codex = 12,
57 Sonic = 13,
59 WorldChain = 14,
61 Monad = 15,
63 Sei = 16,
65 BnbSmartChain = 17,
67 Xdc = 18,
69 HyperEvm = 19,
71 Ink = 21,
73 Plume = 22,
75 StarknetTestnet = 25,
77 ArcTestnet = 26,
79}
80
81impl DomainId {
82 #[inline]
93 pub const fn as_u32(self) -> u32 {
94 self as u32
95 }
96
97 #[inline]
110 pub const fn from_u32(value: u32) -> Option<Self> {
111 match value {
112 0 => Some(Self::Ethereum),
113 1 => Some(Self::Avalanche),
114 2 => Some(Self::Optimism),
115 3 => Some(Self::Arbitrum),
116 5 => Some(Self::Solana),
117 6 => Some(Self::Base),
118 7 => Some(Self::Polygon),
119 10 => Some(Self::Unichain),
120 11 => Some(Self::Linea),
121 12 => Some(Self::Codex),
122 13 => Some(Self::Sonic),
123 14 => Some(Self::WorldChain),
124 15 => Some(Self::Monad),
125 16 => Some(Self::Sei),
126 17 => Some(Self::BnbSmartChain),
127 18 => Some(Self::Xdc),
128 19 => Some(Self::HyperEvm),
129 21 => Some(Self::Ink),
130 22 => Some(Self::Plume),
131 25 => Some(Self::StarknetTestnet),
132 26 => Some(Self::ArcTestnet),
133 _ => None,
134 }
135 }
136
137 #[inline]
149 pub const fn name(self) -> &'static str {
150 match self {
151 Self::Ethereum => "Ethereum",
152 Self::Avalanche => "Avalanche",
153 Self::Optimism => "Optimism",
154 Self::Arbitrum => "Arbitrum",
155 Self::Solana => "Solana",
156 Self::Base => "Base",
157 Self::Polygon => "Polygon",
158 Self::Unichain => "Unichain",
159 Self::Linea => "Linea",
160 Self::Codex => "Codex",
161 Self::Sonic => "Sonic",
162 Self::WorldChain => "World Chain",
163 Self::Monad => "Monad",
164 Self::Sei => "Sei",
165 Self::BnbSmartChain => "BNB Smart Chain",
166 Self::Xdc => "XDC",
167 Self::HyperEvm => "HyperEVM",
168 Self::Ink => "Ink",
169 Self::Plume => "Plume",
170 Self::StarknetTestnet => "Starknet Testnet",
171 Self::ArcTestnet => "Arc Testnet",
172 }
173 }
174}
175
176impl From<DomainId> for u32 {
177 #[inline]
178 fn from(domain: DomainId) -> Self {
179 domain.as_u32()
180 }
181}
182
183impl TryFrom<u32> for DomainId {
184 type Error = InvalidDomainId;
185
186 #[inline]
187 fn try_from(value: u32) -> Result<Self, Self::Error> {
188 Self::from_u32(value).ok_or(InvalidDomainId(value))
189 }
190}
191
192impl fmt::Display for DomainId {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 write!(f, "{} ({})", self.name(), self.as_u32())
195 }
196}
197
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub struct InvalidDomainId(pub u32);
201
202impl fmt::Display for InvalidDomainId {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 write!(f, "invalid CCTP domain ID: {}", self.0)
205 }
206}
207
208impl std::error::Error for InvalidDomainId {}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_domain_id_values() {
216 assert_eq!(DomainId::Ethereum.as_u32(), 0);
218 assert_eq!(DomainId::Avalanche.as_u32(), 1);
219 assert_eq!(DomainId::Optimism.as_u32(), 2);
220 assert_eq!(DomainId::Arbitrum.as_u32(), 3);
221 assert_eq!(DomainId::Base.as_u32(), 6);
222 assert_eq!(DomainId::Polygon.as_u32(), 7);
223 assert_eq!(DomainId::Unichain.as_u32(), 10);
224
225 assert_eq!(DomainId::Solana.as_u32(), 5);
227 assert_eq!(DomainId::Linea.as_u32(), 11);
228 assert_eq!(DomainId::Codex.as_u32(), 12);
229 assert_eq!(DomainId::Sonic.as_u32(), 13);
230 assert_eq!(DomainId::WorldChain.as_u32(), 14);
231 assert_eq!(DomainId::Monad.as_u32(), 15);
232 assert_eq!(DomainId::Sei.as_u32(), 16);
233 assert_eq!(DomainId::BnbSmartChain.as_u32(), 17);
234 assert_eq!(DomainId::Xdc.as_u32(), 18);
235 assert_eq!(DomainId::HyperEvm.as_u32(), 19);
236 assert_eq!(DomainId::Ink.as_u32(), 21);
237 assert_eq!(DomainId::Plume.as_u32(), 22);
238 assert_eq!(DomainId::StarknetTestnet.as_u32(), 25);
239 assert_eq!(DomainId::ArcTestnet.as_u32(), 26);
240 }
241
242 #[test]
243 fn test_from_u32_valid() {
244 assert_eq!(DomainId::from_u32(0), Some(DomainId::Ethereum));
246 assert_eq!(DomainId::from_u32(1), Some(DomainId::Avalanche));
247 assert_eq!(DomainId::from_u32(2), Some(DomainId::Optimism));
248 assert_eq!(DomainId::from_u32(3), Some(DomainId::Arbitrum));
249 assert_eq!(DomainId::from_u32(6), Some(DomainId::Base));
250 assert_eq!(DomainId::from_u32(7), Some(DomainId::Polygon));
251 assert_eq!(DomainId::from_u32(10), Some(DomainId::Unichain));
252
253 assert_eq!(DomainId::from_u32(11), Some(DomainId::Linea));
255 assert_eq!(DomainId::from_u32(13), Some(DomainId::Sonic));
256 assert_eq!(DomainId::from_u32(16), Some(DomainId::Sei));
257 assert_eq!(DomainId::from_u32(17), Some(DomainId::BnbSmartChain));
258
259 assert_eq!(DomainId::from_u32(5), Some(DomainId::Solana));
261 assert_eq!(DomainId::from_u32(12), Some(DomainId::Codex));
262 assert_eq!(DomainId::from_u32(14), Some(DomainId::WorldChain));
263 assert_eq!(DomainId::from_u32(15), Some(DomainId::Monad));
264 assert_eq!(DomainId::from_u32(18), Some(DomainId::Xdc));
265 assert_eq!(DomainId::from_u32(19), Some(DomainId::HyperEvm));
266 assert_eq!(DomainId::from_u32(21), Some(DomainId::Ink));
267 assert_eq!(DomainId::from_u32(22), Some(DomainId::Plume));
268 assert_eq!(DomainId::from_u32(25), Some(DomainId::StarknetTestnet));
269 assert_eq!(DomainId::from_u32(26), Some(DomainId::ArcTestnet));
270 }
271
272 #[test]
273 fn test_from_u32_invalid() {
274 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); }
284
285 #[test]
286 fn test_try_from_valid() {
287 assert_eq!(DomainId::try_from(0).unwrap(), DomainId::Ethereum);
288 assert_eq!(DomainId::try_from(3).unwrap(), DomainId::Arbitrum);
289 }
290
291 #[test]
292 fn test_try_from_invalid() {
293 assert!(DomainId::try_from(999).is_err());
294 let err = DomainId::try_from(999).unwrap_err();
295 assert_eq!(err, InvalidDomainId(999));
296 }
297
298 #[test]
299 fn test_display() {
300 assert_eq!(format!("{}", DomainId::Ethereum), "Ethereum (0)");
301 assert_eq!(format!("{}", DomainId::Arbitrum), "Arbitrum (3)");
302 assert_eq!(format!("{}", DomainId::Base), "Base (6)");
303 }
304
305 #[test]
306 fn test_name() {
307 assert_eq!(DomainId::Ethereum.name(), "Ethereum");
308 assert_eq!(DomainId::Arbitrum.name(), "Arbitrum");
309 assert_eq!(DomainId::Avalanche.name(), "Avalanche");
310 }
311
312 #[test]
313 fn test_conversion_roundtrip() {
314 for domain in [
315 DomainId::Ethereum,
317 DomainId::Avalanche,
318 DomainId::Optimism,
319 DomainId::Arbitrum,
320 DomainId::Base,
321 DomainId::Polygon,
322 DomainId::Unichain,
323 DomainId::Solana,
325 DomainId::Linea,
326 DomainId::Codex,
327 DomainId::Sonic,
328 DomainId::WorldChain,
329 DomainId::Monad,
330 DomainId::Sei,
331 DomainId::BnbSmartChain,
332 DomainId::Xdc,
333 DomainId::HyperEvm,
334 DomainId::Ink,
335 DomainId::Plume,
336 DomainId::StarknetTestnet,
337 DomainId::ArcTestnet,
338 ] {
339 let value: u32 = domain.into();
340 let parsed = DomainId::try_from(value).unwrap();
341 assert_eq!(domain, parsed);
342 }
343 }
344}