1use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9#[derive(Clone, Debug, Deserialize, Serialize)]
11#[serde(rename_all = "camelCase")]
12pub struct ChainRecord {
13 pub name: String,
14 #[serde(default, skip_serializing_if = "Option::is_none")]
15 pub title: Option<String>,
16 pub chain: String,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 pub icon: Option<String>,
19 #[serde(default)]
20 pub rpc: Vec<String>,
21 #[serde(default)]
22 pub features: Vec<Feature>,
23 #[serde(default)]
24 pub faucets: Vec<String>,
25 pub native_currency: NativeCurrency,
26 #[serde(rename = "infoURL")]
27 pub info_url: String,
28 pub short_name: String,
29 pub chain_id: u64,
30 pub network_id: u64,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub slip44: Option<u64>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub ens: Option<Ens>,
35 #[serde(default, skip_serializing_if = "Vec::is_empty")]
36 pub explorers: Vec<Explorer>,
37 #[serde(default, skip_serializing_if = "Option::is_none")]
38 pub parent: Option<Parent>,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub status: Option<String>,
41 #[serde(default, skip_serializing_if = "Vec::is_empty")]
42 pub red_flags: Vec<String>,
43}
44
45impl ChainRecord {
46 pub fn native_currency(&self) -> &NativeCurrency {
48 &self.native_currency
49 }
50
51 pub fn rpc_endpoints(&self) -> &[String] {
53 &self.rpc
54 }
55
56 pub fn faucets(&self) -> &[String] {
58 &self.faucets
59 }
60
61 pub fn features(&self) -> &[Feature] {
63 &self.features
64 }
65
66 pub fn explorers(&self) -> &[Explorer] {
68 &self.explorers
69 }
70
71 pub fn red_flags(&self) -> &[String] {
73 &self.red_flags
74 }
75
76 pub fn parent(&self) -> Option<&Parent> {
78 self.parent.as_ref()
79 }
80
81 pub fn ens(&self) -> Option<&Ens> {
83 self.ens.as_ref()
84 }
85}
86
87#[derive(Clone, Debug, Deserialize, Serialize)]
88pub struct Feature {
89 pub name: String,
90}
91
92#[derive(Clone, Debug, Deserialize, Serialize)]
93#[serde(rename_all = "camelCase")]
94pub struct NativeCurrency {
95 pub name: String,
96 pub symbol: String,
97 pub decimals: u8,
98}
99
100#[derive(Clone, Debug, Deserialize, Serialize)]
101pub struct Ens {
102 pub registry: String,
103}
104
105#[derive(Clone, Debug, Deserialize, Serialize)]
106pub struct Explorer {
107 pub name: String,
108 pub url: String,
109 pub standard: String,
110 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub icon: Option<String>,
112}
113
114#[derive(Clone, Debug, Deserialize, Serialize)]
115#[serde(rename_all = "camelCase")]
116pub struct Parent {
117 #[serde(rename = "type")]
118 pub type_field: String,
119 pub chain: String,
120 #[serde(default, skip_serializing_if = "Vec::is_empty")]
121 pub bridges: Vec<Bridge>,
122}
123
124impl Parent {
125 pub fn bridges(&self) -> &[Bridge] {
127 &self.bridges
128 }
129}
130
131#[derive(Clone, Debug, Deserialize, Serialize)]
132pub struct Bridge {
133 pub url: String,
134}
135
136#[derive(Debug, Error)]
138pub enum SchemaLoadError {
139 #[error("CHAINS_JSON_PATH not set; build script should export it")]
140 MissingPath,
141 #[error("failed to read {0}: {1}")]
142 Io(String, #[source] std::io::Error),
143 #[error("failed to parse {0}: {1}")]
144 Json(String, #[source] serde_json::Error),
145}
146
147pub fn load_chains() -> Result<Vec<ChainRecord>, SchemaLoadError> {
149 let path = option_env!("CHAINS_JSON_PATH").ok_or(SchemaLoadError::MissingPath)?;
150 let text =
151 std::fs::read_to_string(path).map_err(|e| SchemaLoadError::Io(path.to_string(), e))?;
152 serde_json::from_str(&text).map_err(|e| SchemaLoadError::Json(path.to_string(), e))
153}