chain_registry_interface/
lib.rs

1//! Runtime types and minimal runtime helpers for the Cosmos chain-registry schemas.
2//! - Exact field names via serde rename
3//! - Optional fields will use Option<T> in follow-up commits
4//! - Open-ended maps will use HashMap<String, serde_json::Value>
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8
9const RAW_BASE: &str = "https://raw.githubusercontent.com/cosmos/chain-registry/master";
10
11/// Chain corresponds to chain.schema.json root object (initial pass: required fields only).
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Chain {
14    #[serde(rename = "$schema")]
15    pub dollar_schema: String,
16
17    #[serde(rename = "chain_name")]
18    pub chain_name: String,
19
20    #[serde(rename = "chain_type")]
21    pub chain_type: String,
22
23    #[serde(rename = "status")]
24    pub status: String,
25}
26
27// AssetList corresponds to assetlist.schema.json (required fields + common optionals)
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
29pub struct AssetList {
30    #[serde(rename = "$schema")]
31    pub dollar_schema: String,
32    #[serde(rename = "chain_name")]
33    pub chain_name: String,
34    pub assets: Vec<Asset>,
35}
36
37#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38pub struct Asset {
39    pub denom_units: Vec<DenomUnit>,
40    pub type_asset: String,
41    pub base: String,
42    pub display: String,
43    pub name: String,
44    pub symbol: String,
45    #[serde(rename = "logo_URIs")] 
46    pub logo_uris: Option<LogoUris>,
47}
48
49#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
50pub struct DenomUnit {
51    pub denom: String,
52    pub exponent: Option<u32>,
53    pub aliases: Option<Vec<String>>,
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct LogoUris {
58    pub png: Option<String>,
59    pub svg: Option<String>,
60}
61
62// MemoKeys corresponds to memo_keys.schema.json
63#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
64pub struct MemoKeys {
65    #[serde(rename = "$schema")]
66    pub dollar_schema: String,
67    #[serde(rename = "memo_keys")]
68    pub memo_keys: Vec<MemoKey>,
69}
70
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct MemoKey {
73    pub key: String,
74    pub description: String,
75    #[serde(rename = "git_repo")]
76    pub git_repo: String,
77    pub memo: HashMap<String, serde_json::Value>,
78}
79
80// IbcData corresponds to ibc_data.schema.json
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct IbcData {
83    #[serde(rename = "$schema")]
84    pub dollar_schema: String,
85    #[serde(rename = "chain_1")]
86    pub chain_1: ChainInfo,
87    #[serde(rename = "chain_2")]
88    pub chain_2: ChainInfo,
89    pub channels: Vec<Channel>,
90    pub operators: Option<Vec<Operator>>, 
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
94pub struct ChainInfo {
95    #[serde(rename = "chain_name")]
96    pub chain_name: String,
97    #[serde(rename = "chain_id")]
98    pub chain_id: String,
99    #[serde(rename = "client_id")]
100    pub client_id: String,
101    #[serde(rename = "connection_id")]
102    pub connection_id: String,
103}
104
105#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct Channel {
107    #[serde(rename = "chain_1")]
108    pub chain_1: ChannelInfo,
109    #[serde(rename = "chain_2")]
110    pub chain_2: ChannelInfo,
111    pub ordering: String,
112    pub version: String,
113    pub fee_version: Option<String>,
114    pub tags: Option<ChannelTags>,
115}
116
117#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
118pub struct ChannelInfo {
119    pub channel_id: String,
120    pub port_id: String,
121    pub client_id: Option<String>,
122    pub connection_id: Option<String>,
123}
124
125#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
126pub struct ChannelTags {
127    pub preferred: Option<bool>,
128    pub status: Option<String>,
129}
130
131#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct Operator {
133    #[serde(rename = "chain_1")]
134    pub chain_1: OperatorInfo,
135    #[serde(rename = "chain_2")]
136    pub chain_2: OperatorInfo,
137    pub memo: String,
138    pub name: String,
139    pub discord_handle: Option<String>,
140}
141
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143pub struct OperatorInfo {
144    pub address: Option<String>,
145}
146
147fn chain_url(name: &str) -> String {
148    format!("{}/{}/chain.json", RAW_BASE, name)
149}
150
151fn assetlist_url(name: &str) -> String {
152    format!("{}/{}/assetlist.json", RAW_BASE, name)
153}
154
155fn memo_keys_url() -> String {
156    format!("{}/_memo_keys/ICS20_memo_keys.json", RAW_BASE)
157}
158
159
160pub fn ibc_connection_url(a: &str, b: &str) -> String {
161    let (x, y) = if a <= b { (a, b) } else { (b, a) };
162    format!("{}/_IBC/{}-{}.json", RAW_BASE, x, y)
163}
164
165fn versions_url(name: &str) -> String {
166    format!("{}/{}/versions.json", RAW_BASE, name)
167}
168
169/// Fetch IBC connection data for a pair of chains from _IBC/<a>-<b>.json (alphabetically sorted).
170pub async fn fetch_ibc_connection(chain_a: &str, chain_b: &str) -> Result<IbcData, reqwest::Error> {
171    if chain_a.is_empty() || chain_b.is_empty() {
172        // Mirror other helpers by returning a simple reqwest error is awkward; instead, use a dummy request builder
173        // In practice, callers should validate inputs; we keep it minimal here.
174    }
175    let url = ibc_connection_url(chain_a, chain_b);
176    let resp = reqwest::Client::new()
177        .get(url)
178        .header("Accept", "application/json")
179        .header("User-Agent", "chain-registry-interface-rust/0.0")
180        .send()
181        .await?;
182    let d = resp.json::<IbcData>().await?;
183    Ok(d)
184}
185
186/// Fetch a chain.json by chain name from the public registry.
187/// No caching or validation beyond JSON parsing.
188pub async fn fetch_chain(chain_name: &str) -> Result<Chain, reqwest::Error> {
189    let url = chain_url(chain_name);
190    let resp = reqwest::Client::new()
191        .get(url)
192        .header("Accept", "application/json")
193        .header("User-Agent", "chain-registry-interface-rust/0.0")
194        .send()
195        .await?;
196    let chain = resp.json::<Chain>().await?;
197    Ok(chain)
198}
199
200/// Fetch an assetlist.json by chain name.
201pub async fn fetch_asset_list(chain_name: &str) -> Result<AssetList, reqwest::Error> {
202    let url = assetlist_url(chain_name);
203    let resp = reqwest::Client::new()
204        .get(url)
205        .header("Accept", "application/json")
206        .header("User-Agent", "chain-registry-interface-rust/0.0")
207        .send()
208        .await?;
209    let al = resp.json::<AssetList>().await?;
210    Ok(al)
211}
212
213/// Fetch memo_keys.json (root of registry). Name parameter kept for signature symmetry.
214pub async fn fetch_memo_keys(_chain_name: &str) -> Result<MemoKeys, reqwest::Error> {
215    let url = memo_keys_url();
216    let resp = reqwest::Client::new()
217        .get(url)
218        .header("Accept", "application/json")
219        .header("User-Agent", "chain-registry-interface-rust/0.0")
220        .send()
221        .await?;
222    let mk = resp.json::<MemoKeys>().await?;
223    Ok(mk)
224}
225
226
227/// Fetch versions.json by chain name.
228pub async fn fetch_versions(chain_name: &str) -> Result<Versions, reqwest::Error> {
229    let url = versions_url(chain_name);
230    let resp = reqwest::Client::new()
231        .get(url)
232        .header("Accept", "application/json")
233        .header("User-Agent", "chain-registry-interface-rust/0.0")
234        .send()
235        .await?;
236    let v = resp.json::<Versions>().await?;
237    Ok(v)
238}
239
240
241// Versions corresponds to versions.schema.json
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
243pub struct Versions {
244    #[serde(rename = "$schema")]
245    pub dollar_schema: String,
246    #[serde(rename = "chain_name")]
247    pub chain_name: String,
248    pub versions: Vec<ChainVersion>,
249}
250
251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub struct ChainVersion {
253    pub name: String,
254    pub tag: Option<String>,
255    pub height: Option<f64>,
256    pub proposal: Option<f64>,
257    #[serde(rename = "previous_version_name")]
258    pub previous_version_name: Option<String>,
259    #[serde(rename = "next_version_name")]
260    pub next_version_name: Option<String>,
261    #[serde(rename = "recommended_version")]
262    pub recommended_version: Option<String>,
263    #[serde(rename = "compatible_versions")]
264    pub compatible_versions: Option<Vec<String>>,
265    pub sdk: Option<SdkInfo>,
266    pub consensus: Option<ConsensusInfo>,
267    pub cosmwasm: Option<CosmwasmInfo>,
268    pub ibc: Option<IbcInfo>,
269    pub language: Option<LanguageInfo>,
270    pub binaries: Option<Binaries>,
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
274pub struct SdkInfo {
275    #[serde(rename = "type")]
276    pub r#type: String,
277    pub version: Option<String>,
278    pub repo: Option<String>,
279    pub tag: Option<String>,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
283pub struct ConsensusInfo {
284    #[serde(rename = "type")]
285    pub r#type: String,
286    pub version: Option<String>,
287    pub repo: Option<String>,
288    pub tag: Option<String>,
289}
290
291#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
292pub struct CosmwasmInfo {
293    pub version: Option<String>,
294    pub repo: Option<String>,
295    pub tag: Option<String>,
296    pub enabled: Option<bool>,
297    pub path: Option<String>,
298}
299
300#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
301pub struct IbcInfo {
302    #[serde(rename = "type")]
303    pub r#type: String,
304    pub version: Option<String>,
305    pub repo: Option<String>,
306    pub tag: Option<String>,
307    #[serde(rename = "ics_enabled")]
308    pub ics_enabled: Option<Vec<String>>,
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
312pub struct LanguageInfo {
313    #[serde(rename = "type")]
314    pub r#type: String,
315    pub version: Option<String>,
316    pub repo: Option<String>,
317    pub tag: Option<String>,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
321pub struct Binaries {
322    #[serde(rename = "linux/amd64")]
323    pub linux_amd64: Option<String>,
324    #[serde(rename = "linux/arm64")]
325    pub linux_arm64: Option<String>,
326    #[serde(rename = "darwin/amd64")]
327    pub darwin_amd64: Option<String>,
328    #[serde(rename = "darwin/arm64")]
329    pub darwin_arm64: Option<String>,
330    #[serde(rename = "windows/amd64")]
331    pub windows_amd64: Option<String>,
332    #[serde(rename = "windows/arm64")]
333    pub windows_arm64: Option<String>,
334}