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