Skip to main content

ic_query/icrc/model/
token_metadata.rs

1//! Canonical token metadata payload and rows shared by ledger sources and SNS/ICRC reports.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value as JsonValue;
5
6///
7/// IcrcTokenData
8///
9/// Source-layer token metadata returned by an ICRC ledger.
10///
11
12#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
13pub struct IcrcTokenData {
14    /// Native ledger token name.
15    pub token_name: String,
16    /// Native ledger token symbol.
17    pub token_symbol: String,
18    /// Number of decimal places in a base-unit amount.
19    pub decimals: u8,
20    /// Transfer fee as exact base-10 base-unit text.
21    pub transfer_fee: String,
22    /// Total supply as exact base-10 base-unit text.
23    pub total_supply: String,
24    /// Optional minting-account owner in principal text.
25    pub minting_account_owner: Option<String>,
26    /// Optional raw minting subaccount in lowercase hexadecimal.
27    pub minting_account_subaccount_hex: Option<String>,
28    /// Standards advertised by the ledger.
29    pub supported_standards: Vec<IcrcTokenStandardRow>,
30    /// Raw ledger metadata preserving native value kinds.
31    pub metadata: Vec<IcrcTokenMetadataRow>,
32}
33
34///
35/// IcrcTokenStandardRow
36///
37/// Serializable row for one ICRC standard supported by a ledger.
38///
39
40#[cfg_attr(
41    any(feature = "icrc-host", feature = "sns-host"),
42    derive(ic_platform::candid::contract::CandidType)
43)]
44#[cfg_attr(
45    any(feature = "icrc-host", feature = "sns-host"),
46    candid_path("ic_platform::candid::contract")
47)]
48#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
49pub struct IcrcTokenStandardRow {
50    /// Standard name advertised by the ledger.
51    pub name: String,
52    /// Specification URL advertised by the ledger.
53    pub url: String,
54}
55
56///
57/// IcrcTokenMetadataRow
58///
59/// Serializable row for one raw ICRC ledger metadata entry.
60///
61
62#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
63pub struct IcrcTokenMetadataRow {
64    /// Native metadata key.
65    pub key: String,
66    /// Native Candid metadata variant.
67    pub value_type: IcrcMetadataValueKind,
68    /// Lossless decimal, text, or lowercase hexadecimal JSON string.
69    pub value: JsonValue,
70}
71
72///
73/// IcrcMetadataValueKind
74///
75/// Native ICRC-1 metadata value variant represented by a report row.
76///
77
78#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
79#[serde(rename_all = "snake_case")]
80pub enum IcrcMetadataValueKind {
81    /// An arbitrary-precision unsigned integer.
82    Nat,
83    /// An arbitrary-precision signed integer.
84    Int,
85    /// A Unicode text value.
86    Text,
87    /// An opaque byte sequence.
88    Blob,
89}
90
91impl IcrcMetadataValueKind {
92    /// Return the stable JSON and text label.
93    #[must_use]
94    pub const fn as_str(self) -> &'static str {
95        match self {
96            Self::Nat => "nat",
97            Self::Int => "int",
98            Self::Text => "text",
99            Self::Blob => "blob",
100        }
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn metadata_value_kind_labels_round_trip() {
110        for (kind, label) in [
111            (IcrcMetadataValueKind::Nat, "nat"),
112            (IcrcMetadataValueKind::Int, "int"),
113            (IcrcMetadataValueKind::Text, "text"),
114            (IcrcMetadataValueKind::Blob, "blob"),
115        ] {
116            assert_eq!(
117                serde_json::to_string(&kind).unwrap(),
118                format!("\"{label}\"")
119            );
120            assert_eq!(
121                serde_json::from_str::<IcrcMetadataValueKind>(&format!("\"{label}\"")).unwrap(),
122                kind
123            );
124            assert_eq!(kind.as_str(), label);
125        }
126    }
127}
128
129///
130/// IcrcIndexData
131///
132/// ICRC-106 discovery outcome shared by sources and serialized reports.
133///
134
135#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
136#[serde(rename_all = "snake_case")]
137pub enum IcrcIndexData {
138    /// Index principal returned by the ledger.
139    Found(String),
140    /// Index discovery is unavailable; source families own transport-error policy.
141    Unavailable(String),
142}
143
144impl IcrcIndexData {
145    /// Borrow the discovered index principal when discovery succeeded.
146    #[must_use]
147    pub fn canister_id(&self) -> Option<&str> {
148        match self {
149            Self::Found(principal) => Some(principal),
150            Self::Unavailable(_) => None,
151        }
152    }
153
154    /// Borrow the source's explanation when discovery is unavailable.
155    #[must_use]
156    pub fn unavailable_reason(&self) -> Option<&str> {
157        match self {
158            Self::Found(_) => None,
159            Self::Unavailable(reason) => Some(reason),
160        }
161    }
162}