1use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
26use std::collections::HashSet;
27use std::fmt;
28use std::str::FromStr;
29
30use crate::networks;
31
32#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub struct ChainId {
55 pub namespace: String,
57 pub reference: String,
59}
60
61impl ChainId {
62 pub fn new<N: Into<String>, R: Into<String>>(namespace: N, reference: R) -> Self {
74 Self {
75 namespace: namespace.into(),
76 reference: reference.into(),
77 }
78 }
79
80 pub fn namespace(&self) -> &str {
82 &self.namespace
83 }
84
85 pub fn reference(&self) -> &str {
87 &self.reference
88 }
89
90 pub fn from_network_name(network_name: &str) -> Option<Self> {
106 networks::chain_id_by_network_name(network_name).cloned()
107 }
108
109 pub fn as_network_name(&self) -> Option<&'static str> {
125 networks::network_name_by_chain_id(self)
126 }
127}
128
129impl fmt::Display for ChainId {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 write!(f, "{}:{}", self.namespace, self.reference)
132 }
133}
134
135impl From<ChainId> for String {
136 fn from(value: ChainId) -> Self {
137 value.to_string()
138 }
139}
140
141#[derive(Debug, thiserror::Error)]
146#[error("Invalid chain id format {0}")]
147pub struct ChainIdFormatError(String);
148
149impl FromStr for ChainId {
150 type Err = ChainIdFormatError;
151
152 fn from_str(s: &str) -> Result<Self, Self::Err> {
153 let parts: Vec<&str> = s.splitn(2, ':').collect();
154 if parts.len() != 2 {
155 return Err(ChainIdFormatError(s.into()));
156 }
157 Ok(ChainId {
158 namespace: parts[0].into(),
159 reference: parts[1].into(),
160 })
161 }
162}
163
164impl Serialize for ChainId {
165 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
166 where
167 S: Serializer,
168 {
169 serializer.serialize_str(&self.to_string())
170 }
171}
172
173impl<'de> Deserialize<'de> for ChainId {
174 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
175 where
176 D: Deserializer<'de>,
177 {
178 let s = String::deserialize(deserializer)?;
179 ChainId::from_str(&s).map_err(de::Error::custom)
180 }
181}
182
183#[derive(Debug, Clone)]
215pub enum ChainIdPattern {
216 Wildcard {
218 namespace: String,
220 },
221 Exact {
223 namespace: String,
225 reference: String,
227 },
228 Set {
230 namespace: String,
232 references: HashSet<String>,
234 },
235}
236
237impl ChainIdPattern {
238 pub fn wildcard<S: Into<String>>(namespace: S) -> Self {
250 Self::Wildcard {
251 namespace: namespace.into(),
252 }
253 }
254
255 pub fn exact<N: Into<String>, R: Into<String>>(namespace: N, reference: R) -> Self {
267 Self::Exact {
268 namespace: namespace.into(),
269 reference: reference.into(),
270 }
271 }
272
273 pub fn set<N: Into<String>>(namespace: N, references: HashSet<String>) -> Self {
287 Self::Set {
288 namespace: namespace.into(),
289 references,
290 }
291 }
292
293 pub fn matches(&self, chain_id: &ChainId) -> bool {
299 match self {
300 ChainIdPattern::Wildcard { namespace } => chain_id.namespace == *namespace,
301 ChainIdPattern::Exact {
302 namespace,
303 reference,
304 } => chain_id.namespace == *namespace && chain_id.reference == *reference,
305 ChainIdPattern::Set {
306 namespace,
307 references,
308 } => chain_id.namespace == *namespace && references.contains(&chain_id.reference),
309 }
310 }
311
312 #[allow(dead_code)]
314 pub fn namespace(&self) -> &str {
315 match self {
316 ChainIdPattern::Wildcard { namespace } => namespace,
317 ChainIdPattern::Exact { namespace, .. } => namespace,
318 ChainIdPattern::Set { namespace, .. } => namespace,
319 }
320 }
321}
322
323impl fmt::Display for ChainIdPattern {
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 match self {
326 ChainIdPattern::Wildcard { namespace } => write!(f, "{}:*", namespace),
327 ChainIdPattern::Exact {
328 namespace,
329 reference,
330 } => write!(f, "{}:{}", namespace, reference),
331 ChainIdPattern::Set {
332 namespace,
333 references,
334 } => {
335 let refs: Vec<&str> = references.iter().map(|s| s.as_ref()).collect();
336 write!(f, "{}:{{{}}}", namespace, refs.join(","))
337 }
338 }
339 }
340}
341
342impl FromStr for ChainIdPattern {
343 type Err = ChainIdFormatError;
344
345 fn from_str(s: &str) -> Result<Self, Self::Err> {
346 let (namespace, rest) = s.split_once(':').ok_or(ChainIdFormatError(s.into()))?;
347
348 if namespace.is_empty() {
349 return Err(ChainIdFormatError(s.into()));
350 }
351
352 if rest == "*" {
354 return Ok(ChainIdPattern::wildcard(namespace));
355 }
356
357 if let Some(inner) = rest.strip_prefix('{').and_then(|r| r.strip_suffix('}')) {
359 let mut references = HashSet::new();
360
361 for item in inner.split(',') {
362 let item = item.trim();
363 if item.is_empty() {
364 return Err(ChainIdFormatError(s.into()));
365 }
366 references.insert(item.into());
367 }
368
369 if references.is_empty() {
370 return Err(ChainIdFormatError(s.into()));
371 }
372
373 return Ok(ChainIdPattern::set(namespace, references));
374 }
375
376 if rest.is_empty() {
378 return Err(ChainIdFormatError(s.into()));
379 }
380
381 Ok(ChainIdPattern::exact(namespace, rest))
382 }
383}
384
385impl Serialize for ChainIdPattern {
386 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
387 where
388 S: Serializer,
389 {
390 serializer.serialize_str(&self.to_string())
391 }
392}
393
394impl<'de> Deserialize<'de> for ChainIdPattern {
395 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
396 where
397 D: Deserializer<'de>,
398 {
399 let s = String::deserialize(deserializer)?;
400 ChainIdPattern::from_str(&s).map_err(de::Error::custom)
401 }
402}
403
404impl From<ChainId> for ChainIdPattern {
405 fn from(chain_id: ChainId) -> Self {
406 ChainIdPattern::exact(chain_id.namespace, chain_id.reference)
407 }
408}
409
410impl From<ChainIdPattern> for Vec<ChainIdPattern> {
411 fn from(value: ChainIdPattern) -> Self {
412 vec![value]
413 }
414}
415
416impl From<ChainId> for Vec<ChainId> {
417 fn from(value: ChainId) -> Self {
418 vec![value]
419 }
420}
421
422#[cfg(test)]
423mod tests {
424 use super::*;
425
426 #[test]
427 fn test_chain_id_serialize_eip155() {
428 let chain_id = ChainId::new("eip155", "1");
429 let serialized = serde_json::to_string(&chain_id).unwrap();
430 assert_eq!(serialized, "\"eip155:1\"");
431 }
432
433 #[test]
434 fn test_chain_id_serialize_solana() {
435 let chain_id = ChainId::new("solana", "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
436 let serialized = serde_json::to_string(&chain_id).unwrap();
437 assert_eq!(serialized, "\"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\"");
438 }
439
440 #[test]
441 fn test_chain_id_deserialize_eip155() {
442 let chain_id: ChainId = serde_json::from_str("\"eip155:1\"").unwrap();
443 assert_eq!(chain_id.namespace, "eip155");
444 assert_eq!(chain_id.reference, "1");
445 }
446
447 #[test]
448 fn test_chain_id_deserialize_solana() {
449 let chain_id: ChainId =
450 serde_json::from_str("\"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp\"").unwrap();
451 assert_eq!(chain_id.namespace, "solana");
452 assert_eq!(chain_id.reference, "5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp");
453 }
454
455 #[test]
456 fn test_chain_id_roundtrip_eip155() {
457 let original = ChainId::new("eip155", "8453");
458 let serialized = serde_json::to_string(&original).unwrap();
460 let deserialized: ChainId = serde_json::from_str(&serialized).unwrap();
461 assert_eq!(original, deserialized);
462 }
463
464 #[test]
465 fn test_chain_id_roundtrip_solana() {
466 let original = ChainId::new("solana", "devnet");
467 let serialized = serde_json::to_string(&original).unwrap();
468 let deserialized: ChainId = serde_json::from_str(&serialized).unwrap();
469 assert_eq!(original, deserialized);
470 }
471
472 #[test]
473 fn test_chain_id_deserialize_invalid_format() {
474 let result: Result<ChainId, _> = serde_json::from_str("\"invalid\"");
475 assert!(result.is_err());
476 }
477
478 #[test]
479 fn test_chain_id_deserialize_unknown_namespace() {
480 let result: Result<ChainId, _> = serde_json::from_str("\"unknown:1\"");
481 assert!(result.is_ok());
482 }
483
484 #[test]
485 fn test_pattern_wildcard_matches() {
486 let pattern = ChainIdPattern::wildcard("eip155");
487 assert!(pattern.matches(&ChainId::new("eip155", "1")));
488 assert!(pattern.matches(&ChainId::new("eip155", "8453")));
489 assert!(pattern.matches(&ChainId::new("eip155", "137")));
490 assert!(!pattern.matches(&ChainId::new("solana", "mainnet")));
491 }
492
493 #[test]
494 fn test_pattern_exact_matches() {
495 let pattern = ChainIdPattern::exact("eip155", "1");
496 assert!(pattern.matches(&ChainId::new("eip155", "1")));
497 assert!(!pattern.matches(&ChainId::new("eip155", "8453")));
498 assert!(!pattern.matches(&ChainId::new("solana", "1")));
499 }
500
501 #[test]
502 fn test_pattern_set_matches() {
503 let references: HashSet<String> = vec!["1", "8453", "137"]
504 .into_iter()
505 .map(String::from)
506 .collect();
507 let pattern = ChainIdPattern::set("eip155", references);
508 assert!(pattern.matches(&ChainId::new("eip155", "1")));
509 assert!(pattern.matches(&ChainId::new("eip155", "8453")));
510 assert!(pattern.matches(&ChainId::new("eip155", "137")));
511 assert!(!pattern.matches(&ChainId::new("eip155", "42")));
512 assert!(!pattern.matches(&ChainId::new("solana", "1")));
513 }
514
515 #[test]
516 fn test_pattern_namespace() {
517 let wildcard = ChainIdPattern::wildcard("eip155");
518 assert_eq!(wildcard.namespace(), "eip155");
519
520 let exact = ChainIdPattern::exact("solana", "mainnet");
521 assert_eq!(exact.namespace(), "solana");
522
523 let references: HashSet<String> = vec!["1"].into_iter().map(String::from).collect();
524 let set = ChainIdPattern::set("eip155", references);
525 assert_eq!(set.namespace(), "eip155");
526 }
527}