Skip to main content

icrc7_types/
icrc3_types.rs

1use candid::{CandidType, Decode, Deserialize, Encode, Principal};
2use ic_stable_structures::{storable::Bound, Storable};
3use icrc_ledger_types::{
4    icrc::generic_value::{Hash, Map, Value},
5    icrc1::account::Account,
6};
7use std::{collections::BTreeMap, marker::PhantomData};
8
9use serde::Serialize;
10use serde_bytes::ByteBuf;
11use std::ops::Deref;
12
13use crate::icrc7_types::Transaction;
14
15#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
16pub struct Block(Value);
17
18impl Storable for Block {
19    fn from_bytes(bytes: std::borrow::Cow<[u8]>) -> Self {
20        Decode!(bytes.as_ref(), Self).unwrap()
21    }
22
23    fn to_bytes(&self) -> std::borrow::Cow<[u8]> {
24        std::borrow::Cow::Owned(Encode!(self).unwrap())
25    }
26
27    const BOUND: Bound = Bound::Unbounded;
28}
29
30impl Block {
31    pub fn value(&self) -> &Value {
32        &self.0
33    }
34
35    pub fn value_mut(&mut self) -> &mut Value {
36        &mut self.0
37    }
38}
39
40impl AsRef<Value> for Block {
41    #[inline]
42    fn as_ref(&self) -> &Value {
43        &self.0
44    }
45}
46
47impl Deref for Block {
48    type Target = Value;
49    #[inline]
50    fn deref(&self) -> &Self::Target {
51        &self.0
52    }
53}
54
55impl From<Map> for Block {
56    fn from(value: Map) -> Self {
57        Self(Value::Map(value))
58    }
59}
60
61impl TryFrom<Value> for Block {
62    type Error = String;
63    fn try_from(value: Value) -> Result<Self, Self::Error> {
64        match value {
65            Value::Map(map) => Ok(Self(Value::Map(map))),
66            _ => Err("block must be a map value".to_string()),
67        }
68    }
69}
70
71impl Block {
72    pub fn new(phash: Option<Hash>, tx: Transaction) -> Self {
73        let mut block = Map::new();
74        if let Some(phash) = phash {
75            block.insert("phash".to_string(), Value::Blob(ByteBuf::from(phash)));
76        };
77
78        block.insert("btype".to_string(), Value::Text(tx.op));
79        block.insert("ts".to_string(), Value::Nat(tx.ts.into()));
80
81        let mut val = Map::new();
82        val.insert("tid".to_string(), Value::Nat(tx.tid.into()));
83        if let Some(from) = tx.from {
84            val.insert("from".to_string(), account_value(from));
85        }
86        if let Some(to) = tx.to {
87            val.insert("to".to_string(), account_value(to));
88        }
89        if let Some(spender) = tx.spender {
90            val.insert("spender".to_string(), account_value(spender));
91        }
92        if let Some(exp) = tx.exp {
93            val.insert("exp".to_string(), Value::Nat(exp.into()));
94        }
95        if let Some(meta) = tx.meta {
96            val.insert("meta".to_string(), Value::Map(meta));
97        }
98        if let Some(memo) = tx.memo {
99            val.insert("memo".to_string(), Value::Blob(ByteBuf::from(memo)));
100        }
101        val.insert("ts".to_string(), Value::Nat(tx.ts.into()));
102        block.insert("tx".to_string(), Value::Map(val));
103        Self(Value::Map(block))
104    }
105
106    pub fn into_inner(self) -> Value {
107        self.0
108    }
109
110    pub fn into_map(self) -> Map {
111        match self.0 {
112            Value::Map(map) => map,
113            _ => unreachable!(),
114        }
115    }
116}
117
118#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
119pub struct BlockType {
120    pub block_type: String,
121    pub url: String,
122}
123
124fn account_value(Account { owner, subaccount }: Account) -> Value {
125    let mut parts = vec![Value::blob(owner.as_slice())];
126    if let Some(subaccount) = subaccount {
127        parts.push(Value::blob(subaccount.as_slice()));
128    }
129    Value::Array(parts)
130}
131
132#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
133pub enum IndexType {
134    Managed,
135    Stable,
136    StableTyped,
137}
138
139#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
140pub struct TransactionRange {
141    pub start: u128,
142    pub length: u128,
143}
144
145#[derive(CandidType, Serialize, Deserialize, Debug, Clone)]
146pub struct ArchiveLedgerInfo {
147    pub archives: BTreeMap<Principal, TransactionRange>,
148    pub local_ledger_size: u128,
149    pub supported_blocks: Vec<BlockType>,
150    pub last_index: u128,
151    pub first_index: u128,
152    pub is_cleaning: bool,
153    pub latest_hash: Option<Hash>,
154    pub setting: ArchiveSetting,
155}
156
157impl Default for ArchiveLedgerInfo {
158    fn default() -> Self {
159        Self {
160            archives: BTreeMap::new(),
161            local_ledger_size: 0,
162            supported_blocks: vec![],
163            last_index: 0,
164            first_index: 0,
165            is_cleaning: false,
166            latest_hash: None,
167            setting: ArchiveSetting::default(),
168        }
169    }
170}
171
172impl ArchiveLedgerInfo {
173    pub fn new(setting: Option<ArchiveSetting>) -> Self {
174        let setting = setting.unwrap_or(ArchiveSetting::default());
175        Self {
176            archives: BTreeMap::new(),
177            local_ledger_size: 0,
178            last_index: 0,
179            first_index: 0,
180            is_cleaning: false,
181            latest_hash: None,
182            setting,
183            supported_blocks: vec![
184                BlockType {
185                    block_type: "7mint".into(),
186                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-7/ICRC-7.md".into(),
187                },
188                BlockType {
189                    block_type: "7burn".into(),
190                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-7/ICRC-7.md".into(),
191                },
192                BlockType {
193                    block_type: "7xfer".into(),
194                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-7/ICRC-7.md".into(),
195                },
196                BlockType {
197                    block_type: "7update".into(),
198                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-7/ICRC-7.md".into(),
199                },
200                BlockType {
201                    block_type: "37appr".into(),
202                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-37/ICRC-37.md"
203                        .into(),
204                },
205                BlockType {
206                    block_type: "37appr_coll".into(),
207                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-37/ICRC-37.md"
208                        .into(),
209                },
210                BlockType {
211                    block_type: "37revoke".into(),
212                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-37/ICRC-37.md"
213                        .into(),
214                },
215                BlockType {
216                    block_type: "37revoke_coll".into(),
217                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-37/ICRC-37.md"
218                        .into(),
219                },
220                BlockType {
221                    block_type: "37xfer".into(),
222                    url: "https://github.com/dfinity/ICRC/blob/main/ICRCs/ICRC-37/ICRC-37.md"
223                        .into(),
224                },
225            ],
226        }
227    }
228}
229
230#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
231pub struct ArchiveSetting {
232    pub archive_controllers: Option<Option<Vec<Principal>>>,
233    pub archive_cycles: u128,
234    pub archive_index_type: IndexType,
235    pub max_active_records: u128,
236    pub max_archive_pages: u128,
237    pub max_records_in_archive_instance: u128,
238    pub max_records_to_archive: u128,
239    pub settle_to_records: u128,
240}
241
242impl Default for ArchiveSetting {
243    fn default() -> Self {
244        Self {
245            archive_controllers: None,
246            archive_cycles: 2_000_000_000_000,
247            archive_index_type: IndexType::Stable,
248            max_active_records: 2000,
249            max_archive_pages: 62500,
250            max_records_in_archive_instance: 10_000_000,
251            max_records_to_archive: 10_000,
252            settle_to_records: 1000,
253        }
254    }
255}
256
257#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
258pub struct InitArchiveArg {
259    #[serde(rename = "archiveControllers")]
260    pub archive_controllers: Option<Option<Vec<Principal>>>,
261    #[serde(rename = "archiveCycles")]
262    pub archive_cycles: u128,
263    #[serde(rename = "archiveIndexType")]
264    pub archive_index_type: IndexType,
265    #[serde(rename = "maxActiveRecords")]
266    pub max_active_records: u128,
267    #[serde(rename = "maxArchivePages")]
268    pub max_archive_pages: u128,
269    #[serde(rename = "maxRecordsInArchiveInstance")]
270    pub max_records_in_archive_instance: u128,
271    #[serde(rename = "maxRecordsToArchive")]
272    pub max_records_to_archive: u128,
273    #[serde(rename = "settleToRecords")]
274    pub settle_to_records: u128,
275}
276
277impl InitArchiveArg {
278    pub fn to_archive_setting(self) -> ArchiveSetting {
279        ArchiveSetting {
280            archive_controllers: self.archive_controllers,
281            archive_cycles: self.archive_cycles,
282            archive_index_type: self.archive_index_type,
283            max_active_records: self.max_active_records,
284            max_archive_pages: self.max_archive_pages,
285            max_records_in_archive_instance: self.max_records_in_archive_instance,
286            max_records_to_archive: self.max_records_to_archive,
287            settle_to_records: self.settle_to_records,
288        }
289    }
290}
291
292#[derive(CandidType, Deserialize, Debug)]
293pub struct GetArchiveArgs {
294    pub from: Option<Principal>,
295}
296
297pub type GetBlocksArgs = Vec<TransactionRange>;
298
299#[derive(CandidType, Deserialize, Debug)]
300pub struct QueryBlock {
301    pub id: u128,
302    pub block: Value,
303}
304
305#[derive(CandidType, Deserialize, Debug)]
306pub struct Tip {
307    pub hash_tree: Vec<u8>,
308    pub last_block_hash: Hash,
309    pub last_block_index: Vec<u8>,
310}
311
312#[derive(CandidType, Deserialize, Debug)]
313pub struct GetTransactionsResult {
314    pub blocks: Vec<ArchivedTransactionResponse>,
315    pub log_length: u128,
316    pub archived_blocks: Vec<u8>,
317}
318
319#[derive(CandidType, Deserialize, Debug)]
320pub struct GetBlocksResult {
321    pub blocks: Vec<QueryBlock>,
322    pub log_length: u128,
323    pub archived_blocks: Vec<ArchivedTransactionResponse>,
324}
325
326#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
327#[serde(try_from = "candid::types::reference::Func")]
328pub struct GetTransactionsFn<Input: CandidType, Output: CandidType> {
329    pub canister_id: Principal,
330    pub method: String,
331    pub _marker: PhantomData<(Input, Output)>,
332}
333
334impl<Input: CandidType, Output: CandidType> GetTransactionsFn<Input, Output> {
335    pub fn new(canister_id: Principal, method: impl Into<String>) -> Self {
336        Self {
337            canister_id,
338            method: method.into(),
339            _marker: PhantomData,
340        }
341    }
342}
343
344impl<Input: CandidType, Output: CandidType> Clone for GetTransactionsFn<Input, Output> {
345    fn clone(&self) -> Self {
346        Self {
347            canister_id: self.canister_id,
348            method: self.method.clone(),
349            _marker: PhantomData,
350        }
351    }
352}
353
354impl<Input: CandidType, Output: CandidType> From<GetTransactionsFn<Input, Output>>
355    for candid::types::reference::Func
356{
357    fn from(get_transactions_fn: GetTransactionsFn<Input, Output>) -> Self {
358        let p: &Principal = &Principal::try_from(get_transactions_fn.canister_id.as_ref())
359            .expect("could not deserialize principal");
360        Self {
361            principal: *p,
362            method: get_transactions_fn.method,
363        }
364    }
365}
366
367impl<Input: CandidType, Output: CandidType> TryFrom<candid::types::reference::Func>
368    for GetTransactionsFn<Input, Output>
369{
370    type Error = String;
371    fn try_from(func: candid::types::reference::Func) -> Result<Self, Self::Error> {
372        let canister_id = Principal::try_from(func.principal.as_slice())
373            .map_err(|e| format!("principal is not a canister id: {}", e))?;
374        Ok(GetTransactionsFn {
375            canister_id,
376            method: func.method,
377            _marker: PhantomData,
378        })
379    }
380}
381
382impl<Input: CandidType, Output: CandidType> CandidType for GetTransactionsFn<Input, Output> {
383    fn _ty() -> candid::types::Type {
384        candid::func!((Input) -> (Output) query)
385    }
386
387    fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
388    where
389        S: candid::types::Serializer,
390    {
391        candid::types::reference::Func::from(self.clone()).idl_serialize(serializer)
392    }
393}
394
395#[derive(CandidType, Deserialize, Debug)]
396pub struct ArchivedTransactionResponse {
397    pub args: Vec<TransactionRange>,
398    pub callback: QueryTransactionsFn,
399}
400
401pub type QueryTransactionsFn = GetTransactionsFn<Vec<QueryBlock>, GetTransactionsResult>;
402
403#[derive(CandidType, Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
404pub struct GetArchivesResultItem {
405    pub canister_id: Principal,
406    pub start: u128,
407    pub end: u128,
408}
409
410#[derive(CandidType, Serialize, Deserialize, Clone, Debug)]
411pub struct ArchiveCreateArgs {
412    pub max_pages: u128,
413    pub max_records: u128,
414    pub first_index: u128,
415    pub controllers: Option<Option<Vec<Principal>>>,
416}