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}