Skip to main content

ows_core/
chain.rs

1use crate::caip::ChainId;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::str::FromStr;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "lowercase")]
8pub enum ChainType {
9    Evm,
10    Solana,
11    Cosmos,
12    Bitcoin,
13    Tron,
14    Ton,
15    Spark,
16    Filecoin,
17    Sui,
18    Xrpl,
19    Nano,
20    Near,
21}
22
23/// All supported chain families, used for universal wallet derivation.
24pub const ALL_CHAIN_TYPES: [ChainType; 12] = [
25    ChainType::Evm,
26    ChainType::Solana,
27    ChainType::Bitcoin,
28    ChainType::Cosmos,
29    ChainType::Tron,
30    ChainType::Ton,
31    ChainType::Spark,
32    ChainType::Filecoin,
33    ChainType::Sui,
34    ChainType::Xrpl,
35    ChainType::Nano,
36    ChainType::Near,
37];
38
39/// A specific chain (e.g. "ethereum", "arbitrum") with its family type and CAIP-2 ID.
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub struct Chain {
42    pub name: &'static str,
43    pub chain_type: ChainType,
44    pub chain_id: &'static str,
45}
46
47impl Chain {
48    /// Return the EIP-155 reference portion of this chain's CAIP-2 ID.
49    pub fn evm_chain_reference(&self) -> Result<&str, String> {
50        if self.chain_type != ChainType::Evm {
51            return Err(format!("chain '{}' is not an EVM chain", self.chain_id));
52        }
53
54        let chain_id = self
55            .chain_id
56            .parse::<ChainId>()
57            .map_err(|e| e.to_string())?;
58        if chain_id.namespace != "eip155" {
59            return Err(format!(
60                "EVM chain '{}' is missing an eip155 reference",
61                self.chain_id
62            ));
63        }
64
65        self.chain_id
66            .split_once(':')
67            .map(|(_, reference)| reference)
68            .ok_or_else(|| format!("invalid CAIP-2 chain ID: '{}'", self.chain_id))
69    }
70
71    /// Return the numeric EIP-155 chain ID for an EVM chain.
72    pub fn evm_chain_id_u64(&self) -> Result<u64, String> {
73        self.evm_chain_reference()?
74            .parse()
75            .map_err(|_| format!("cannot extract numeric chain ID from: {}", self.chain_id))
76    }
77}
78
79/// Known chains registry.
80pub const KNOWN_CHAINS: &[Chain] = &[
81    Chain {
82        name: "ethereum",
83        chain_type: ChainType::Evm,
84        chain_id: "eip155:1",
85    },
86    Chain {
87        name: "polygon",
88        chain_type: ChainType::Evm,
89        chain_id: "eip155:137",
90    },
91    Chain {
92        name: "arbitrum",
93        chain_type: ChainType::Evm,
94        chain_id: "eip155:42161",
95    },
96    Chain {
97        name: "optimism",
98        chain_type: ChainType::Evm,
99        chain_id: "eip155:10",
100    },
101    Chain {
102        name: "base",
103        chain_type: ChainType::Evm,
104        chain_id: "eip155:8453",
105    },
106    Chain {
107        name: "plasma",
108        chain_type: ChainType::Evm,
109        chain_id: "eip155:9745",
110    },
111    Chain {
112        name: "bsc",
113        chain_type: ChainType::Evm,
114        chain_id: "eip155:56",
115    },
116    Chain {
117        name: "avalanche",
118        chain_type: ChainType::Evm,
119        chain_id: "eip155:43114",
120    },
121    Chain {
122        name: "etherlink",
123        chain_type: ChainType::Evm,
124        chain_id: "eip155:42793",
125    },
126    Chain {
127        name: "solana",
128        chain_type: ChainType::Solana,
129        chain_id: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
130    },
131    Chain {
132        name: "bitcoin",
133        chain_type: ChainType::Bitcoin,
134        chain_id: "bip122:000000000019d6689c085ae165831e93",
135    },
136    Chain {
137        name: "cosmos",
138        chain_type: ChainType::Cosmos,
139        chain_id: "cosmos:cosmoshub-4",
140    },
141    Chain {
142        name: "tron",
143        chain_type: ChainType::Tron,
144        chain_id: "tron:mainnet",
145    },
146    Chain {
147        name: "ton",
148        chain_type: ChainType::Ton,
149        chain_id: "ton:mainnet",
150    },
151    Chain {
152        name: "spark",
153        chain_type: ChainType::Spark,
154        chain_id: "spark:mainnet",
155    },
156    Chain {
157        name: "filecoin",
158        chain_type: ChainType::Filecoin,
159        chain_id: "fil:mainnet",
160    },
161    Chain {
162        name: "sui",
163        chain_type: ChainType::Sui,
164        chain_id: "sui:mainnet",
165    },
166    Chain {
167        name: "xrpl",
168        chain_type: ChainType::Xrpl,
169        chain_id: "xrpl:mainnet",
170    },
171    Chain {
172        name: "xrpl-testnet",
173        chain_type: ChainType::Xrpl,
174        chain_id: "xrpl:testnet",
175    },
176    Chain {
177        name: "xrpl-devnet",
178        chain_type: ChainType::Xrpl,
179        chain_id: "xrpl:devnet",
180    },
181    Chain {
182        name: "nano",
183        chain_type: ChainType::Nano,
184        chain_id: "nano:mainnet",
185    },
186    Chain {
187        name: "near",
188        chain_type: ChainType::Near,
189        chain_id: "near:mainnet",
190    },
191    Chain {
192        name: "near-testnet",
193        chain_type: ChainType::Near,
194        chain_id: "near:testnet",
195    },
196    Chain {
197        name: "tempo",
198        chain_type: ChainType::Evm,
199        chain_id: "eip155:4217",
200    },
201    Chain {
202        name: "hyperliquid",
203        chain_type: ChainType::Evm,
204        chain_id: "eip155:999",
205    },
206];
207
208/// Parse a chain string into a `Chain`. Accepts:
209/// - Friendly names: "ethereum", "base", "arbitrum", "solana", etc.
210/// - CAIP-2 chain IDs: "eip155:1", "eip155:8453", etc.
211/// - Bare numeric EVM chain IDs: "8453" → eip155:8453
212/// - Legacy "evm" (deprecated, warns on stderr, resolves to ethereum)
213pub fn parse_chain(s: &str) -> Result<Chain, String> {
214    let lower = s.to_lowercase();
215
216    // Legacy "evm" — deprecated, warn and resolve
217    if lower == "evm" {
218        eprintln!(
219            "warning: '--chain evm' is deprecated; use '--chain ethereum' \
220             or a specific chain name (base, arbitrum, polygon, ...)"
221        );
222        return Ok(*KNOWN_CHAINS.iter().find(|c| c.name == "ethereum").unwrap());
223    }
224
225    // Try friendly name match
226    if let Some(chain) = KNOWN_CHAINS.iter().find(|c| c.name == lower) {
227        return Ok(*chain);
228    }
229
230    // Try CAIP-2 chain ID match
231    if let Some(chain) = KNOWN_CHAINS.iter().find(|c| c.chain_id == s) {
232        return Ok(*chain);
233    }
234
235    // Bare numeric → treat as EVM chain ID (eip155:<n>)
236    if !lower.is_empty() && lower.chars().all(|c| c.is_ascii_digit()) {
237        let caip2 = format!("eip155:{}", lower);
238        if let Some(chain) = KNOWN_CHAINS.iter().find(|c| c.chain_id == caip2) {
239            return Ok(*chain);
240        }
241        let leaked: &'static str = Box::leak(caip2.into_boxed_str());
242        return Ok(Chain {
243            name: leaked,
244            chain_type: ChainType::Evm,
245            chain_id: leaked,
246        });
247    }
248
249    // Try namespace match for unknown CAIP-2 IDs (e.g. eip155:4217, eip155:84532).
250    // Uses the same signer as the namespace's default chain. The chain_id string is
251    // leaked to satisfy the 'static lifetime — acceptable since parse_chain is called
252    // with a small, bounded set of user-supplied chain identifiers.
253    if let Some((namespace, _reference)) = s.split_once(':') {
254        if let Some(ct) = ChainType::from_namespace(namespace) {
255            let leaked: &'static str = Box::leak(s.to_string().into_boxed_str());
256            return Ok(Chain {
257                name: leaked,
258                chain_type: ct,
259                chain_id: leaked,
260            });
261        }
262    }
263
264    Err(format!(
265        "unknown chain: '{s}'\n\n\
266         Supported chains:\n  \
267           EVM:     ethereum, base, arbitrum, optimism, polygon, bsc, avalanche, plasma, etherlink\n  \
268           Solana:  solana\n  \
269           Bitcoin: bitcoin\n  \
270           Other:   cosmos, tron, ton, sui, filecoin, spark, xrpl, nano, near\n\n\
271         Or use a CAIP-2 ID (eip155:8453) or bare EVM chain ID (8453)"
272    ))
273}
274
275/// Returns the default `Chain` for a given `ChainType` (first match in registry).
276pub fn default_chain_for_type(ct: ChainType) -> Chain {
277    *KNOWN_CHAINS.iter().find(|c| c.chain_type == ct).unwrap()
278}
279
280impl ChainType {
281    /// Returns the CAIP-2 namespace for this chain type.
282    pub fn namespace(&self) -> &'static str {
283        match self {
284            ChainType::Evm => "eip155",
285            ChainType::Solana => "solana",
286            ChainType::Cosmos => "cosmos",
287            ChainType::Bitcoin => "bip122",
288            ChainType::Tron => "tron",
289            ChainType::Ton => "ton",
290            ChainType::Spark => "spark",
291            ChainType::Filecoin => "fil",
292            ChainType::Sui => "sui",
293            ChainType::Xrpl => "xrpl",
294            ChainType::Nano => "nano",
295            ChainType::Near => "near",
296        }
297    }
298
299    /// Returns the BIP-44 coin type for this chain type.
300    pub fn default_coin_type(&self) -> u32 {
301        match self {
302            ChainType::Evm => 60,
303            ChainType::Solana => 501,
304            ChainType::Cosmos => 118,
305            ChainType::Bitcoin => 0,
306            ChainType::Tron => 195,
307            ChainType::Ton => 607,
308            ChainType::Spark => 8797555,
309            ChainType::Filecoin => 461,
310            ChainType::Sui => 784,
311            ChainType::Xrpl => 144,
312            ChainType::Nano => 165,
313            ChainType::Near => 397,
314        }
315    }
316
317    /// Returns the ChainType for a given CAIP-2 namespace.
318    pub fn from_namespace(ns: &str) -> Option<ChainType> {
319        match ns {
320            "eip155" => Some(ChainType::Evm),
321            "solana" => Some(ChainType::Solana),
322            "cosmos" => Some(ChainType::Cosmos),
323            "bip122" => Some(ChainType::Bitcoin),
324            "tron" => Some(ChainType::Tron),
325            "ton" => Some(ChainType::Ton),
326            "spark" => Some(ChainType::Spark),
327            "fil" => Some(ChainType::Filecoin),
328            "sui" => Some(ChainType::Sui),
329            "xrpl" => Some(ChainType::Xrpl),
330            "nano" => Some(ChainType::Nano),
331            "near" => Some(ChainType::Near),
332            _ => None,
333        }
334    }
335}
336
337impl fmt::Display for ChainType {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        let s = match self {
340            ChainType::Evm => "evm",
341            ChainType::Solana => "solana",
342            ChainType::Cosmos => "cosmos",
343            ChainType::Bitcoin => "bitcoin",
344            ChainType::Tron => "tron",
345            ChainType::Ton => "ton",
346            ChainType::Spark => "spark",
347            ChainType::Filecoin => "filecoin",
348            ChainType::Sui => "sui",
349            ChainType::Xrpl => "xrpl",
350            ChainType::Nano => "nano",
351            ChainType::Near => "near",
352        };
353        write!(f, "{}", s)
354    }
355}
356
357impl FromStr for ChainType {
358    type Err = String;
359
360    fn from_str(s: &str) -> Result<Self, Self::Err> {
361        match s.to_lowercase().as_str() {
362            "evm" => Ok(ChainType::Evm),
363            "solana" => Ok(ChainType::Solana),
364            "cosmos" => Ok(ChainType::Cosmos),
365            "bitcoin" => Ok(ChainType::Bitcoin),
366            "tron" => Ok(ChainType::Tron),
367            "ton" => Ok(ChainType::Ton),
368            "spark" => Ok(ChainType::Spark),
369            "filecoin" => Ok(ChainType::Filecoin),
370            "sui" => Ok(ChainType::Sui),
371            "xrpl" => Ok(ChainType::Xrpl),
372            "nano" => Ok(ChainType::Nano),
373            "near" => Ok(ChainType::Near),
374            _ => Err(format!("unknown chain type: {}", s)),
375        }
376    }
377}
378
379#[cfg(test)]
380mod tests {
381    use super::*;
382
383    #[test]
384    fn test_serde_roundtrip() {
385        let chain = ChainType::Evm;
386        let json = serde_json::to_string(&chain).unwrap();
387        assert_eq!(json, "\"evm\"");
388        let chain2: ChainType = serde_json::from_str(&json).unwrap();
389        assert_eq!(chain, chain2);
390    }
391
392    #[test]
393    fn test_serde_all_variants() {
394        for (chain, expected) in [
395            (ChainType::Evm, "\"evm\""),
396            (ChainType::Solana, "\"solana\""),
397            (ChainType::Cosmos, "\"cosmos\""),
398            (ChainType::Bitcoin, "\"bitcoin\""),
399            (ChainType::Tron, "\"tron\""),
400            (ChainType::Ton, "\"ton\""),
401            (ChainType::Spark, "\"spark\""),
402            (ChainType::Filecoin, "\"filecoin\""),
403            (ChainType::Sui, "\"sui\""),
404            (ChainType::Xrpl, "\"xrpl\""),
405            (ChainType::Nano, "\"nano\""),
406            (ChainType::Near, "\"near\""),
407        ] {
408            let json = serde_json::to_string(&chain).unwrap();
409            assert_eq!(json, expected);
410            let deserialized: ChainType = serde_json::from_str(&json).unwrap();
411            assert_eq!(chain, deserialized);
412        }
413    }
414
415    #[test]
416    fn test_namespace_mapping() {
417        assert_eq!(ChainType::Evm.namespace(), "eip155");
418        assert_eq!(ChainType::Solana.namespace(), "solana");
419        assert_eq!(ChainType::Cosmos.namespace(), "cosmos");
420        assert_eq!(ChainType::Bitcoin.namespace(), "bip122");
421        assert_eq!(ChainType::Tron.namespace(), "tron");
422        assert_eq!(ChainType::Ton.namespace(), "ton");
423        assert_eq!(ChainType::Spark.namespace(), "spark");
424        assert_eq!(ChainType::Filecoin.namespace(), "fil");
425        assert_eq!(ChainType::Sui.namespace(), "sui");
426        assert_eq!(ChainType::Xrpl.namespace(), "xrpl");
427        assert_eq!(ChainType::Nano.namespace(), "nano");
428        assert_eq!(ChainType::Near.namespace(), "near");
429    }
430
431    #[test]
432    fn test_coin_type_mapping() {
433        assert_eq!(ChainType::Evm.default_coin_type(), 60);
434        assert_eq!(ChainType::Solana.default_coin_type(), 501);
435        assert_eq!(ChainType::Cosmos.default_coin_type(), 118);
436        assert_eq!(ChainType::Bitcoin.default_coin_type(), 0);
437        assert_eq!(ChainType::Tron.default_coin_type(), 195);
438        assert_eq!(ChainType::Ton.default_coin_type(), 607);
439        assert_eq!(ChainType::Spark.default_coin_type(), 8797555);
440        assert_eq!(ChainType::Filecoin.default_coin_type(), 461);
441        assert_eq!(ChainType::Sui.default_coin_type(), 784);
442        assert_eq!(ChainType::Xrpl.default_coin_type(), 144);
443        assert_eq!(ChainType::Nano.default_coin_type(), 165);
444        assert_eq!(ChainType::Near.default_coin_type(), 397);
445    }
446
447    #[test]
448    fn test_from_namespace() {
449        assert_eq!(ChainType::from_namespace("eip155"), Some(ChainType::Evm));
450        assert_eq!(ChainType::from_namespace("solana"), Some(ChainType::Solana));
451        assert_eq!(ChainType::from_namespace("cosmos"), Some(ChainType::Cosmos));
452        assert_eq!(
453            ChainType::from_namespace("bip122"),
454            Some(ChainType::Bitcoin)
455        );
456        assert_eq!(ChainType::from_namespace("tron"), Some(ChainType::Tron));
457        assert_eq!(ChainType::from_namespace("ton"), Some(ChainType::Ton));
458        assert_eq!(ChainType::from_namespace("spark"), Some(ChainType::Spark));
459        assert_eq!(ChainType::from_namespace("fil"), Some(ChainType::Filecoin));
460        assert_eq!(ChainType::from_namespace("sui"), Some(ChainType::Sui));
461        assert_eq!(ChainType::from_namespace("xrpl"), Some(ChainType::Xrpl));
462        assert_eq!(ChainType::from_namespace("nano"), Some(ChainType::Nano));
463        assert_eq!(ChainType::from_namespace("near"), Some(ChainType::Near));
464        assert_eq!(ChainType::from_namespace("unknown"), None);
465    }
466
467    #[test]
468    fn test_from_str() {
469        assert_eq!("evm".parse::<ChainType>().unwrap(), ChainType::Evm);
470        assert_eq!("Solana".parse::<ChainType>().unwrap(), ChainType::Solana);
471        assert!("unknown".parse::<ChainType>().is_err());
472    }
473
474    #[test]
475    fn test_display() {
476        assert_eq!(ChainType::Evm.to_string(), "evm");
477        assert_eq!(ChainType::Bitcoin.to_string(), "bitcoin");
478    }
479
480    #[test]
481    fn test_parse_chain_friendly_name() {
482        let chain = parse_chain("ethereum").unwrap();
483        assert_eq!(chain.name, "ethereum");
484        assert_eq!(chain.chain_type, ChainType::Evm);
485        assert_eq!(chain.chain_id, "eip155:1");
486    }
487
488    #[test]
489    fn test_parse_chain_plasma_alias() {
490        let chain = parse_chain("plasma").unwrap();
491        assert_eq!(chain.name, "plasma");
492        assert_eq!(chain.chain_type, ChainType::Evm);
493        assert_eq!(chain.chain_id, "eip155:9745");
494    }
495
496    #[test]
497    fn test_parse_chain_etherlink_alias() {
498        let chain = parse_chain("etherlink").unwrap();
499        assert_eq!(chain.name, "etherlink");
500        assert_eq!(chain.chain_type, ChainType::Evm);
501        assert_eq!(chain.chain_id, "eip155:42793");
502    }
503
504    #[test]
505    fn test_parse_chain_caip2() {
506        let chain = parse_chain("eip155:42161").unwrap();
507        assert_eq!(chain.name, "arbitrum");
508        assert_eq!(chain.chain_type, ChainType::Evm);
509    }
510
511    #[test]
512    fn test_parse_chain_plasma_caip2() {
513        let chain = parse_chain("eip155:9745").unwrap();
514        assert_eq!(chain.name, "plasma");
515        assert_eq!(chain.chain_type, ChainType::Evm);
516        assert_eq!(chain.chain_id, "eip155:9745");
517    }
518
519    #[test]
520    fn test_parse_chain_unknown_evm_caip2() {
521        let chain = parse_chain("eip155:9746").unwrap();
522        assert_eq!(chain.name, "eip155:9746");
523        assert_eq!(chain.chain_type, ChainType::Evm);
524        assert_eq!(chain.chain_id, "eip155:9746");
525    }
526
527    #[test]
528    fn test_evm_chain_reference_for_known_chain() {
529        let chain = parse_chain("base").unwrap();
530        assert_eq!(chain.evm_chain_reference().unwrap(), "8453");
531        assert_eq!(chain.evm_chain_id_u64().unwrap(), 8453);
532    }
533
534    #[test]
535    fn test_evm_chain_reference_for_unknown_caip2_chain() {
536        let chain = parse_chain("eip155:999999").unwrap();
537        assert_eq!(chain.evm_chain_reference().unwrap(), "999999");
538        assert_eq!(chain.evm_chain_id_u64().unwrap(), 999999);
539    }
540
541    #[test]
542    fn test_evm_chain_reference_rejects_non_evm_chain() {
543        let chain = parse_chain("solana").unwrap();
544        let err = chain.evm_chain_reference().unwrap_err();
545        assert!(err.contains("not an EVM chain"));
546    }
547
548    #[test]
549    fn test_parse_chain_legacy_evm() {
550        let chain = parse_chain("evm").unwrap();
551        assert_eq!(chain.name, "ethereum");
552        assert_eq!(chain.chain_type, ChainType::Evm);
553    }
554
555    #[test]
556    fn test_parse_chain_solana() {
557        let chain = parse_chain("solana").unwrap();
558        assert_eq!(chain.chain_type, ChainType::Solana);
559    }
560
561    #[test]
562    fn test_parse_chain_xrpl() {
563        let chain = parse_chain("xrpl").unwrap();
564        assert_eq!(chain.chain_type, ChainType::Xrpl);
565        assert_eq!(chain.chain_id, "xrpl:mainnet");
566
567        let testnet = parse_chain("xrpl-testnet").unwrap();
568        assert_eq!(testnet.chain_type, ChainType::Xrpl);
569        assert_eq!(testnet.chain_id, "xrpl:testnet");
570
571        let devnet = parse_chain("xrpl-devnet").unwrap();
572        assert_eq!(devnet.chain_type, ChainType::Xrpl);
573        assert_eq!(devnet.chain_id, "xrpl:devnet");
574
575        // CAIP-2 IDs also accepted directly
576        let via_caip2 = parse_chain("xrpl:testnet").unwrap();
577        assert_eq!(via_caip2.chain_type, ChainType::Xrpl);
578        assert_eq!(via_caip2.chain_id, "xrpl:testnet");
579    }
580
581    #[test]
582    fn test_parse_chain_bare_numeric_known() {
583        // "8453" → Base (eip155:8453)
584        let chain = parse_chain("8453").unwrap();
585        assert_eq!(chain.name, "base");
586        assert_eq!(chain.chain_type, ChainType::Evm);
587        assert_eq!(chain.chain_id, "eip155:8453");
588    }
589
590    #[test]
591    fn test_parse_chain_bare_numeric_mainnet() {
592        let chain = parse_chain("1").unwrap();
593        assert_eq!(chain.name, "ethereum");
594        assert_eq!(chain.chain_id, "eip155:1");
595    }
596
597    #[test]
598    fn test_parse_chain_bare_numeric_unknown() {
599        // Unknown EVM chain ID still resolves
600        let chain = parse_chain("99999").unwrap();
601        assert_eq!(chain.chain_type, ChainType::Evm);
602        assert_eq!(chain.chain_id, "eip155:99999");
603    }
604
605    #[test]
606    fn test_parse_chain_unknown() {
607        assert!(parse_chain("unknown_chain").is_err());
608    }
609
610    #[test]
611    fn test_parse_chain_tempo_alias() {
612        let chain = parse_chain("tempo").unwrap();
613        assert_eq!(chain.name, "tempo");
614        assert_eq!(chain.chain_type, ChainType::Evm);
615        assert_eq!(chain.chain_id, "eip155:4217");
616    }
617
618    #[test]
619    fn test_parse_chain_tempo_caip2() {
620        let chain = parse_chain("eip155:4217").unwrap();
621        assert_eq!(chain.name, "tempo");
622        assert_eq!(chain.chain_type, ChainType::Evm);
623        assert_eq!(chain.chain_id, "eip155:4217");
624    }
625
626    #[test]
627    fn test_parse_chain_hyperliquid_alias() {
628        let chain = parse_chain("hyperliquid").unwrap();
629        assert_eq!(chain.name, "hyperliquid");
630        assert_eq!(chain.chain_type, ChainType::Evm);
631        assert_eq!(chain.chain_id, "eip155:999");
632    }
633
634    #[test]
635    fn test_parse_chain_hyperliquid_caip2() {
636        let chain = parse_chain("eip155:999").unwrap();
637        assert_eq!(chain.name, "hyperliquid");
638        assert_eq!(chain.chain_type, ChainType::Evm);
639        assert_eq!(chain.chain_id, "eip155:999");
640    }
641
642    #[test]
643    fn test_all_chain_types() {
644        assert_eq!(ALL_CHAIN_TYPES.len(), 12);
645    }
646
647    #[test]
648    fn test_parse_chain_near() {
649        let chain = parse_chain("near").unwrap();
650        assert_eq!(chain.name, "near");
651        assert_eq!(chain.chain_type, ChainType::Near);
652        assert_eq!(chain.chain_id, "near:mainnet");
653
654        let testnet = parse_chain("near-testnet").unwrap();
655        assert_eq!(testnet.chain_type, ChainType::Near);
656        assert_eq!(testnet.chain_id, "near:testnet");
657
658        // CAIP-2 IDs accepted directly
659        let via_caip2 = parse_chain("near:testnet").unwrap();
660        assert_eq!(via_caip2.chain_type, ChainType::Near);
661        assert_eq!(via_caip2.chain_id, "near:testnet");
662    }
663
664    #[test]
665    fn test_default_chain_for_type() {
666        let chain = default_chain_for_type(ChainType::Evm);
667        assert_eq!(chain.name, "ethereum");
668        assert_eq!(chain.chain_id, "eip155:1");
669    }
670}