1#[cfg(feature = "cli")]
8use crate::cli::common::CurrentUnixSecsError;
9#[cfg(feature = "host")]
10use crate::hex::hex_bytes;
11#[cfg(feature = "host")]
12use crate::runtime::RuntimeError;
13use serde::Serialize;
14use serde_json::Value as JsonValue;
15use std::io;
16use thiserror::Error as ThisError;
17
18#[derive(Debug, ThisError)]
24pub enum IcrcError {
25 #[error("{0}")]
26 Usage(String),
27
28 #[cfg(feature = "cli")]
29 #[error(transparent)]
30 Clock(#[from] CurrentUnixSecsError),
31
32 #[cfg(feature = "host")]
33 #[error("failed to create Tokio runtime for ICRC query: {0}")]
34 Runtime(#[from] RuntimeError),
35
36 #[error("failed to build IC agent for endpoint {endpoint}: {reason}")]
37 AgentBuild { endpoint: String, reason: String },
38
39 #[error("invalid {field}: {reason}")]
40 InvalidPrincipal { field: &'static str, reason: String },
41
42 #[error("invalid subaccount hex: {reason}")]
43 InvalidSubaccountHex { reason: String },
44
45 #[error("invalid subaccount length: expected 32 bytes, got {bytes}")]
46 InvalidSubaccountLength { bytes: usize },
47
48 #[error("failed to encode Candid request for {message}: {reason}")]
49 CandidEncode {
50 message: &'static str,
51 reason: String,
52 },
53
54 #[error("ICRC ledger method {method} failed: {reason}")]
55 AgentCall {
56 method: &'static str,
57 reason: String,
58 },
59
60 #[error("failed to decode Candid response {message}: {reason}")]
61 CandidDecode {
62 message: &'static str,
63 reason: String,
64 },
65
66 #[error(transparent)]
67 Io(#[from] io::Error),
68
69 #[error(transparent)]
70 Json(#[from] serde_json::Error),
71}
72
73#[derive(Clone, Debug, Eq, PartialEq)]
79pub struct IcrcTokenRequest {
80 pub source_endpoint: String,
81 pub now_unix_secs: u64,
82 pub ledger_canister_id: String,
83}
84
85#[derive(Clone, Debug, Eq, PartialEq)]
91pub struct IcrcBalanceRequest {
92 pub source_endpoint: String,
93 pub now_unix_secs: u64,
94 pub ledger_canister_id: String,
95 pub account_owner: String,
96 pub subaccount_hex: Option<String>,
97}
98
99#[derive(Clone, Debug, Eq, PartialEq)]
105pub struct IcrcAllowanceRequest {
106 pub source_endpoint: String,
107 pub now_unix_secs: u64,
108 pub ledger_canister_id: String,
109 pub account_owner: String,
110 pub account_subaccount_hex: Option<String>,
111 pub spender_owner: String,
112 pub spender_subaccount_hex: Option<String>,
113}
114
115#[derive(Clone, Debug, Eq, PartialEq)]
121pub struct IcrcIndexRequest {
122 pub source_endpoint: String,
123 pub now_unix_secs: u64,
124 pub ledger_canister_id: String,
125}
126
127#[derive(Clone, Debug, Eq, PartialEq)]
133pub struct IcrcTransactionsRequest {
134 pub source_endpoint: String,
135 pub now_unix_secs: u64,
136 pub ledger_canister_id: String,
137 pub start: u64,
138 pub limit: u32,
139 pub follow_archives: bool,
140}
141
142#[derive(Clone, Debug, Eq, PartialEq)]
148pub struct IcrcBlockTypesRequest {
149 pub source_endpoint: String,
150 pub now_unix_secs: u64,
151 pub ledger_canister_id: String,
152}
153
154#[derive(Clone, Debug, Eq, PartialEq)]
160pub struct IcrcArchivesRequest {
161 pub source_endpoint: String,
162 pub now_unix_secs: u64,
163 pub ledger_canister_id: String,
164 pub from_canister_id: Option<String>,
165}
166
167#[derive(Clone, Debug, Eq, PartialEq)]
173pub struct IcrcTipCertificateRequest {
174 pub source_endpoint: String,
175 pub now_unix_secs: u64,
176 pub ledger_canister_id: String,
177}
178
179#[derive(Clone, Debug, Eq, PartialEq)]
185pub struct IcrcCapabilitiesRequest {
186 pub source_endpoint: String,
187 pub now_unix_secs: u64,
188 pub ledger_canister_id: String,
189}
190
191#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
197pub struct IcrcTokenReport {
198 pub schema_version: u32,
199 pub ledger_canister_id: String,
200 pub fetched_at: String,
201 pub source_endpoint: String,
202 pub fetched_by: String,
203 pub token_name: String,
204 pub token_symbol: String,
205 pub decimals: u8,
206 pub transfer_fee: String,
207 pub total_supply: String,
208 pub minting_account_owner: Option<String>,
209 pub minting_account_subaccount_hex: Option<String>,
210 pub supported_standards: Vec<IcrcTokenStandardRow>,
211 pub metadata: Vec<IcrcTokenMetadataRow>,
212}
213
214#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
220pub struct IcrcBalanceReport {
221 pub schema_version: u32,
222 pub ledger_canister_id: String,
223 pub account_owner: String,
224 pub subaccount_hex: Option<String>,
225 pub fetched_at: String,
226 pub source_endpoint: String,
227 pub fetched_by: String,
228 pub token_symbol: String,
229 pub decimals: u8,
230 pub balance: String,
231}
232
233#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
239pub struct IcrcAllowanceReport {
240 pub schema_version: u32,
241 pub ledger_canister_id: String,
242 pub account_owner: String,
243 pub account_subaccount_hex: Option<String>,
244 pub spender_owner: String,
245 pub spender_subaccount_hex: Option<String>,
246 pub fetched_at: String,
247 pub source_endpoint: String,
248 pub fetched_by: String,
249 pub token_symbol: String,
250 pub decimals: u8,
251 pub allowance: String,
252 pub expires_at_unix_nanos: Option<String>,
253}
254
255#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
261pub struct IcrcIndexReport {
262 pub schema_version: u32,
263 pub ledger_canister_id: String,
264 pub fetched_at: String,
265 pub source_endpoint: String,
266 pub fetched_by: String,
267 pub index_canister_id: Option<String>,
268 pub index_error: Option<String>,
269}
270
271#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
277pub struct IcrcTransactionsReport {
278 pub schema_version: u32,
279 pub ledger_canister_id: String,
280 pub fetched_at: String,
281 pub source_endpoint: String,
282 pub fetched_by: String,
283 pub requested_start: String,
284 pub requested_limit: u32,
285 pub follow_archives: bool,
286 pub log_length: Option<String>,
287 pub blocks: Vec<IcrcTransactionBlockRow>,
288 pub archived_blocks: Vec<IcrcArchivedBlocksRow>,
289 pub followed_archive_blocks: Vec<IcrcFollowedArchiveBlockRow>,
290 pub archive_follow_errors: Vec<IcrcArchiveFollowErrorRow>,
291}
292
293#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
299pub struct IcrcBlockTypesReport {
300 pub schema_version: u32,
301 pub ledger_canister_id: String,
302 pub fetched_at: String,
303 pub source_endpoint: String,
304 pub fetched_by: String,
305 pub block_types: Vec<IcrcBlockTypeRow>,
306}
307
308#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
314pub struct IcrcArchivesReport {
315 pub schema_version: u32,
316 pub ledger_canister_id: String,
317 pub from_canister_id: Option<String>,
318 pub fetched_at: String,
319 pub source_endpoint: String,
320 pub fetched_by: String,
321 pub archives: Vec<IcrcArchiveRow>,
322}
323
324#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
330pub struct IcrcTipCertificateReport {
331 pub schema_version: u32,
332 pub ledger_canister_id: String,
333 pub fetched_at: String,
334 pub source_endpoint: String,
335 pub fetched_by: String,
336 pub certificate_present: bool,
337 pub certificate_hex: Option<String>,
338 pub certificate_bytes: Option<usize>,
339 pub hash_tree_hex: Option<String>,
340 pub hash_tree_bytes: Option<usize>,
341}
342
343#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
349pub struct IcrcCapabilitiesReport {
350 pub schema_version: u32,
351 pub ledger_canister_id: String,
352 pub fetched_at: String,
353 pub source_endpoint: String,
354 pub fetched_by: String,
355 pub supported_standards: Vec<IcrcTokenStandardRow>,
356 pub capabilities: Vec<IcrcCapabilityRow>,
357}
358
359#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
365pub struct IcrcCapabilityRow {
366 pub capability: String,
367 pub method: String,
368 pub status: String,
369 pub details: Option<String>,
370 pub error: Option<String>,
371}
372
373#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
379pub struct IcrcTokenStandardRow {
380 pub name: String,
381 pub url: String,
382}
383
384#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
390pub struct IcrcTokenMetadataRow {
391 pub key: String,
392 pub value_type: String,
393 pub value: JsonValue,
394}
395
396#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
402pub struct IcrcTransactionBlockRow {
403 pub index: String,
404 pub block_type: Option<String>,
405 pub transaction_kind: Option<String>,
406 pub timestamp_unix_nanos: Option<String>,
407 pub amount_base_units: Option<String>,
408 pub raw_block: JsonValue,
409}
410
411#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
417pub struct IcrcArchivedBlocksRow {
418 pub callback_canister_id: String,
419 pub callback_method: String,
420 pub ranges: Vec<IcrcArchivedRangeRow>,
421}
422
423#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
429pub struct IcrcArchivedRangeRow {
430 pub start: String,
431 pub length: String,
432}
433
434#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
440pub struct IcrcFollowedArchiveBlockRow {
441 pub archive_canister_id: String,
442 pub callback_method: String,
443 pub index: String,
444 pub block_type: Option<String>,
445 pub transaction_kind: Option<String>,
446 pub timestamp_unix_nanos: Option<String>,
447 pub amount_base_units: Option<String>,
448 pub raw_block: JsonValue,
449}
450
451#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
457pub struct IcrcArchiveFollowErrorRow {
458 pub callback_canister_id: String,
459 pub callback_method: String,
460 pub ranges: Vec<IcrcArchivedRangeRow>,
461 pub error: String,
462}
463
464#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
470pub struct IcrcBlockTypeRow {
471 pub block_type: String,
472 pub url: String,
473}
474
475#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
481pub struct IcrcArchiveRow {
482 pub canister_id: String,
483 pub start: String,
484 pub end: String,
485}
486
487#[cfg(feature = "host")]
493#[derive(Clone, Debug, Eq, PartialEq)]
494pub(in crate::icrc) struct IcrcTokenData {
495 pub(in crate::icrc) token_name: String,
496 pub(in crate::icrc) token_symbol: String,
497 pub(in crate::icrc) decimals: u8,
498 pub(in crate::icrc) transfer_fee: String,
499 pub(in crate::icrc) total_supply: String,
500 pub(in crate::icrc) minting_account_owner: Option<String>,
501 pub(in crate::icrc) minting_account_subaccount_hex: Option<String>,
502 pub(in crate::icrc) supported_standards: Vec<IcrcTokenStandardRow>,
503 pub(in crate::icrc) metadata: Vec<IcrcTokenMetadataRow>,
504}
505
506#[cfg(feature = "host")]
512#[derive(Clone, Debug, Eq, PartialEq)]
513pub(in crate::icrc) struct IcrcBalanceData {
514 pub(in crate::icrc) token_symbol: String,
515 pub(in crate::icrc) decimals: u8,
516 pub(in crate::icrc) balance: String,
517}
518
519#[cfg(feature = "host")]
525#[derive(Clone, Debug, Eq, PartialEq)]
526pub(in crate::icrc) struct IcrcAllowanceData {
527 pub(in crate::icrc) token_symbol: String,
528 pub(in crate::icrc) decimals: u8,
529 pub(in crate::icrc) allowance: String,
530 pub(in crate::icrc) expires_at_unix_nanos: Option<String>,
531}
532
533#[cfg(feature = "host")]
539#[derive(Clone, Debug, Eq, PartialEq)]
540pub(in crate::icrc) struct IcrcIndexData {
541 pub(in crate::icrc) index_canister_id: Option<String>,
542 pub(in crate::icrc) index_error: Option<String>,
543}
544
545#[cfg(feature = "host")]
551#[derive(Clone, Debug, PartialEq)]
552pub(in crate::icrc) struct IcrcTransactionsData {
553 pub(in crate::icrc) log_length: Option<String>,
554 pub(in crate::icrc) blocks: Vec<IcrcTransactionBlockRow>,
555 pub(in crate::icrc) archived_blocks: Vec<IcrcArchivedBlocksRow>,
556 pub(in crate::icrc) followed_archive_blocks: Vec<IcrcFollowedArchiveBlockRow>,
557 pub(in crate::icrc) archive_follow_errors: Vec<IcrcArchiveFollowErrorRow>,
558}
559
560#[cfg(feature = "host")]
566#[derive(Clone, Debug, Eq, PartialEq)]
567pub(in crate::icrc) struct IcrcBlockTypesData {
568 pub(in crate::icrc) block_types: Vec<IcrcBlockTypeRow>,
569}
570
571#[cfg(feature = "host")]
577#[derive(Clone, Debug, Eq, PartialEq)]
578pub(in crate::icrc) struct IcrcArchivesData {
579 pub(in crate::icrc) archives: Vec<IcrcArchiveRow>,
580}
581
582#[cfg(feature = "host")]
588#[derive(Clone, Debug, Eq, PartialEq)]
589pub(in crate::icrc) struct IcrcTipCertificateData {
590 pub(in crate::icrc) certificate_hex: Option<String>,
591 pub(in crate::icrc) certificate_bytes: Option<usize>,
592 pub(in crate::icrc) hash_tree_hex: Option<String>,
593 pub(in crate::icrc) hash_tree_bytes: Option<usize>,
594}
595
596#[cfg(feature = "host")]
602#[derive(Clone, Debug, Eq, PartialEq)]
603pub(in crate::icrc) struct IcrcCapabilitiesData {
604 pub(in crate::icrc) supported_standards: Vec<IcrcTokenStandardRow>,
605 pub(in crate::icrc) capabilities: Vec<IcrcCapabilityRow>,
606}
607
608#[cfg(feature = "host")]
609pub(in crate::icrc) fn normalize_subaccount_hex(value: &str) -> Result<String, IcrcError> {
610 let bytes = subaccount_bytes_from_hex(value)?;
611 Ok(hex_bytes(&bytes))
612}
613
614#[cfg(feature = "host")]
615pub(in crate::icrc) fn subaccount_bytes_from_hex(value: &str) -> Result<Vec<u8>, IcrcError> {
616 let value = value.trim();
617 if !value.len().is_multiple_of(2) {
618 return Err(IcrcError::InvalidSubaccountHex {
619 reason: "hex string must contain an even number of characters".to_string(),
620 });
621 }
622 let bytes = (0..value.len())
623 .step_by(2)
624 .map(|index| {
625 u8::from_str_radix(&value[index..index + 2], 16).map_err(|err| {
626 IcrcError::InvalidSubaccountHex {
627 reason: err.to_string(),
628 }
629 })
630 })
631 .collect::<Result<Vec<_>, _>>()?;
632 if bytes.len() != 32 {
633 return Err(IcrcError::InvalidSubaccountLength { bytes: bytes.len() });
634 }
635 Ok(bytes)
636}