nyxd_scraper_shared/cosmos_module/
message_registry.rs

1// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::cosmos_module::CosmosModule;
5use crate::cosmos_module::modules::auth::Auth;
6use crate::cosmos_module::modules::authz::Authz;
7use crate::cosmos_module::modules::bank::Bank;
8use crate::cosmos_module::modules::capability::Capability;
9use crate::cosmos_module::modules::consensus::Consensus;
10use crate::cosmos_module::modules::crisis::Crisis;
11use crate::cosmos_module::modules::distribution::Distribution;
12use crate::cosmos_module::modules::evidence::Evidence;
13use crate::cosmos_module::modules::feegrant::Feegrant;
14use crate::cosmos_module::modules::gov_v1::GovV1;
15use crate::cosmos_module::modules::gov_v1beta1::GovV1Beta1;
16use crate::cosmos_module::modules::group::Group;
17use crate::cosmos_module::modules::ibc_core::IbcCore;
18use crate::cosmos_module::modules::ibc_fee::IbcFee;
19use crate::cosmos_module::modules::ibc_interchain_accounts_controller::IbcInterchainAccountsController;
20use crate::cosmos_module::modules::ibc_transfer_v1::IbcTransferV1;
21use crate::cosmos_module::modules::ibc_transfer_v2::IbcTransferV2;
22use crate::cosmos_module::modules::mint::Mint;
23use crate::cosmos_module::modules::nft::Nft;
24use crate::cosmos_module::modules::params::Params;
25use crate::cosmos_module::modules::slashing::Slashing;
26use crate::cosmos_module::modules::staking::Staking;
27use crate::cosmos_module::modules::upgrade::Upgrade;
28use crate::cosmos_module::modules::vesting::Vesting;
29use crate::cosmos_module::modules::wasm::Wasm;
30use crate::error::ScraperError;
31use cosmrs::Any;
32use cosmrs::proto::prost::Name;
33use cosmrs::proto::traits::Message;
34use serde::Serialize;
35use std::collections::HashMap;
36
37pub(crate) fn default_proto_to_json<T: Message + Default + Serialize>(
38    msg: &Any,
39) -> Result<serde_json::Value, ScraperError> {
40    let proto = <T as Message>::decode(msg.value.as_slice()).map_err(|error| {
41        ScraperError::InvalidProtoRepresentation {
42            type_url: msg.type_url.clone(),
43            error,
44        }
45    })?;
46    let mut base_serde =
47        serde_json::to_value(&proto).map_err(|error| ScraperError::JsonSerialisationFailure {
48            type_url: msg.type_url.clone(),
49            error,
50        })?;
51
52    // in bdjuno's output we also had @type field with the type_url
53    let obj = base_serde.as_object_mut().ok_or_else(|| {
54        ScraperError::JsonSerialisationFailureNotObject {
55            type_url: msg.type_url.clone(),
56        }
57    })?;
58    obj.insert(
59        "@type".to_string(),
60        serde_json::Value::String(msg.type_url.clone()),
61    );
62
63    Ok(base_serde)
64}
65
66type ConvertFn = fn(&Any) -> Result<serde_json::Value, ScraperError>;
67
68#[derive(Default, Clone)]
69pub struct MessageRegistry {
70    // type url to function converting bytes to proto and finally to json
71    registered_types: HashMap<String, ConvertFn>,
72}
73
74impl MessageRegistry {
75    pub fn new() -> Self {
76        MessageRegistry {
77            registered_types: Default::default(),
78        }
79    }
80
81    pub fn register<T>(&mut self)
82    where
83        T: Message + Default + Name + Serialize + 'static,
84    {
85        self.register_with_custom_fn::<T>(default_proto_to_json::<T>)
86    }
87
88    #[allow(clippy::panic)]
89    pub fn register_with_custom_fn<T>(&mut self, convert_fn: ConvertFn)
90    where
91        T: Message + Default + Name + Serialize + 'static,
92    {
93        if self
94            .registered_types
95            .insert(<T as Name>::type_url(), convert_fn)
96            .is_some()
97        {
98            // don't allow duplicate registration because it most likely implies bug in the code
99            panic!("duplicate registration of type {}", <T as Name>::type_url());
100        }
101    }
102
103    pub fn try_decode(&self, raw: &Any) -> Result<serde_json::Value, ScraperError> {
104        self.registered_types.get(&raw.type_url).ok_or(
105            ScraperError::MissingTypeUrlRegistration {
106                type_url: raw.type_url.clone(),
107            },
108        )?(raw)
109    }
110}
111
112pub fn default_message_registry() -> MessageRegistry {
113    let mut registry = MessageRegistry::new();
114    let modules: Vec<Box<dyn CosmosModule>> = vec![
115        Box::new(Auth),
116        Box::new(Authz),
117        Box::new(Bank),
118        Box::new(Capability),
119        Box::new(Consensus),
120        Box::new(Wasm),
121        Box::new(Crisis),
122        Box::new(Distribution),
123        Box::new(Evidence),
124        Box::new(Feegrant),
125        Box::new(GovV1),
126        Box::new(GovV1Beta1),
127        Box::new(Group),
128        Box::new(IbcCore),
129        Box::new(IbcFee),
130        Box::new(IbcTransferV1),
131        Box::new(IbcTransferV2),
132        Box::new(IbcInterchainAccountsController),
133        Box::new(Mint),
134        Box::new(Nft),
135        Box::new(Params),
136        Box::new(Slashing),
137        Box::new(Staking),
138        Box::new(Upgrade),
139        Box::new(Vesting),
140    ];
141
142    for module in modules {
143        module.register_messages(&mut registry)
144    }
145    registry
146}