chainlist_rs/
schema.rs

1//! Serde bindings for the chainid.network / ethereum-lists chains schema.
2//!
3//! Use `load_chains()` to parse the downloaded `chains.json` from the path
4//! provided by `CHAINS_JSON_PATH` (set by the build script).
5
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9/// Top-level chain record as defined by chainid.network.
10#[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    /// Access the nested native currency.
47    pub fn native_currency(&self) -> &NativeCurrency {
48        &self.native_currency
49    }
50
51    /// Access RPC endpoints.
52    pub fn rpc_endpoints(&self) -> &[String] {
53        &self.rpc
54    }
55
56    /// Access faucet URLs.
57    pub fn faucets(&self) -> &[String] {
58        &self.faucets
59    }
60
61    /// Access feature flags.
62    pub fn features(&self) -> &[Feature] {
63        &self.features
64    }
65
66    /// Access explorers.
67    pub fn explorers(&self) -> &[Explorer] {
68        &self.explorers
69    }
70
71    /// Access red flags.
72    pub fn red_flags(&self) -> &[String] {
73        &self.red_flags
74    }
75
76    /// Access parent network if present.
77    pub fn parent(&self) -> Option<&Parent> {
78        self.parent.as_ref()
79    }
80
81    /// Access ENS registry if present.
82    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    /// Access bridges connecting this chain to its parent.
126    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/// Errors when loading the full schema JSON.
137#[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
147/// Load the full chain list from the downloaded chains.json.
148pub 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}