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
85impl IcrcTokenRequest {
86 #[must_use]
87 pub fn new(
88 source_endpoint: impl Into<String>,
89 now_unix_secs: u64,
90 ledger_canister_id: impl Into<String>,
91 ) -> Self {
92 Self {
93 source_endpoint: source_endpoint.into(),
94 now_unix_secs,
95 ledger_canister_id: ledger_canister_id.into(),
96 }
97 }
98}
99
100#[derive(Clone, Debug, Eq, PartialEq)]
106pub struct IcrcBalanceRequest {
107 pub source_endpoint: String,
108 pub now_unix_secs: u64,
109 pub ledger_canister_id: String,
110 pub account_owner: String,
111 pub subaccount_hex: Option<String>,
112}
113
114impl IcrcBalanceRequest {
115 #[must_use]
116 pub fn new(
117 source_endpoint: impl Into<String>,
118 now_unix_secs: u64,
119 ledger_canister_id: impl Into<String>,
120 account_owner: impl Into<String>,
121 ) -> Self {
122 Self {
123 source_endpoint: source_endpoint.into(),
124 now_unix_secs,
125 ledger_canister_id: ledger_canister_id.into(),
126 account_owner: account_owner.into(),
127 subaccount_hex: None,
128 }
129 }
130
131 #[must_use]
132 pub fn with_subaccount_hex(mut self, subaccount_hex: impl Into<String>) -> Self {
133 self.subaccount_hex = Some(subaccount_hex.into());
134 self
135 }
136}
137
138#[derive(Clone, Debug, Eq, PartialEq)]
144pub struct IcrcAllowanceRequest {
145 pub source_endpoint: String,
146 pub now_unix_secs: u64,
147 pub ledger_canister_id: String,
148 pub account_owner: String,
149 pub account_subaccount_hex: Option<String>,
150 pub spender_owner: String,
151 pub spender_subaccount_hex: Option<String>,
152}
153
154impl IcrcAllowanceRequest {
155 #[must_use]
156 pub fn new(
157 source_endpoint: impl Into<String>,
158 now_unix_secs: u64,
159 ledger_canister_id: impl Into<String>,
160 account_owner: impl Into<String>,
161 spender_owner: impl Into<String>,
162 ) -> Self {
163 Self {
164 source_endpoint: source_endpoint.into(),
165 now_unix_secs,
166 ledger_canister_id: ledger_canister_id.into(),
167 account_owner: account_owner.into(),
168 account_subaccount_hex: None,
169 spender_owner: spender_owner.into(),
170 spender_subaccount_hex: None,
171 }
172 }
173
174 #[must_use]
175 pub fn with_account_subaccount_hex(
176 mut self,
177 account_subaccount_hex: impl Into<String>,
178 ) -> Self {
179 self.account_subaccount_hex = Some(account_subaccount_hex.into());
180 self
181 }
182
183 #[must_use]
184 pub fn with_spender_subaccount_hex(
185 mut self,
186 spender_subaccount_hex: impl Into<String>,
187 ) -> Self {
188 self.spender_subaccount_hex = Some(spender_subaccount_hex.into());
189 self
190 }
191}
192
193#[derive(Clone, Debug, Eq, PartialEq)]
199pub struct IcrcIndexRequest {
200 pub source_endpoint: String,
201 pub now_unix_secs: u64,
202 pub ledger_canister_id: String,
203}
204
205impl IcrcIndexRequest {
206 #[must_use]
207 pub fn new(
208 source_endpoint: impl Into<String>,
209 now_unix_secs: u64,
210 ledger_canister_id: impl Into<String>,
211 ) -> Self {
212 Self {
213 source_endpoint: source_endpoint.into(),
214 now_unix_secs,
215 ledger_canister_id: ledger_canister_id.into(),
216 }
217 }
218}
219
220#[derive(Clone, Debug, Eq, PartialEq)]
226pub struct IcrcTransactionsRequest {
227 pub source_endpoint: String,
228 pub now_unix_secs: u64,
229 pub ledger_canister_id: String,
230 pub start: u64,
231 pub limit: u32,
232 pub follow_archives: bool,
233}
234
235impl IcrcTransactionsRequest {
236 #[must_use]
237 pub fn new(
238 source_endpoint: impl Into<String>,
239 now_unix_secs: u64,
240 ledger_canister_id: impl Into<String>,
241 start: u64,
242 limit: u32,
243 ) -> Self {
244 Self {
245 source_endpoint: source_endpoint.into(),
246 now_unix_secs,
247 ledger_canister_id: ledger_canister_id.into(),
248 start,
249 limit,
250 follow_archives: false,
251 }
252 }
253
254 #[must_use]
255 pub const fn with_follow_archives(mut self, follow_archives: bool) -> Self {
256 self.follow_archives = follow_archives;
257 self
258 }
259}
260
261#[derive(Clone, Debug, Eq, PartialEq)]
267pub struct IcrcBlockTypesRequest {
268 pub source_endpoint: String,
269 pub now_unix_secs: u64,
270 pub ledger_canister_id: String,
271}
272
273impl IcrcBlockTypesRequest {
274 #[must_use]
275 pub fn new(
276 source_endpoint: impl Into<String>,
277 now_unix_secs: u64,
278 ledger_canister_id: impl Into<String>,
279 ) -> Self {
280 Self {
281 source_endpoint: source_endpoint.into(),
282 now_unix_secs,
283 ledger_canister_id: ledger_canister_id.into(),
284 }
285 }
286}
287
288#[derive(Clone, Debug, Eq, PartialEq)]
294pub struct IcrcArchivesRequest {
295 pub source_endpoint: String,
296 pub now_unix_secs: u64,
297 pub ledger_canister_id: String,
298 pub from_canister_id: Option<String>,
299}
300
301impl IcrcArchivesRequest {
302 #[must_use]
303 pub fn new(
304 source_endpoint: impl Into<String>,
305 now_unix_secs: u64,
306 ledger_canister_id: impl Into<String>,
307 ) -> Self {
308 Self {
309 source_endpoint: source_endpoint.into(),
310 now_unix_secs,
311 ledger_canister_id: ledger_canister_id.into(),
312 from_canister_id: None,
313 }
314 }
315
316 #[must_use]
317 pub fn with_from_canister_id(mut self, from_canister_id: impl Into<String>) -> Self {
318 self.from_canister_id = Some(from_canister_id.into());
319 self
320 }
321}
322
323#[derive(Clone, Debug, Eq, PartialEq)]
329pub struct IcrcTipCertificateRequest {
330 pub source_endpoint: String,
331 pub now_unix_secs: u64,
332 pub ledger_canister_id: String,
333}
334
335impl IcrcTipCertificateRequest {
336 #[must_use]
337 pub fn new(
338 source_endpoint: impl Into<String>,
339 now_unix_secs: u64,
340 ledger_canister_id: impl Into<String>,
341 ) -> Self {
342 Self {
343 source_endpoint: source_endpoint.into(),
344 now_unix_secs,
345 ledger_canister_id: ledger_canister_id.into(),
346 }
347 }
348}
349
350#[derive(Clone, Debug, Eq, PartialEq)]
356pub struct IcrcCapabilitiesRequest {
357 pub source_endpoint: String,
358 pub now_unix_secs: u64,
359 pub ledger_canister_id: String,
360}
361
362impl IcrcCapabilitiesRequest {
363 #[must_use]
364 pub fn new(
365 source_endpoint: impl Into<String>,
366 now_unix_secs: u64,
367 ledger_canister_id: impl Into<String>,
368 ) -> Self {
369 Self {
370 source_endpoint: source_endpoint.into(),
371 now_unix_secs,
372 ledger_canister_id: ledger_canister_id.into(),
373 }
374 }
375}
376
377#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
383pub struct IcrcTokenReport {
384 pub schema_version: u32,
385 pub ledger_canister_id: String,
386 pub fetched_at: String,
387 pub source_endpoint: String,
388 pub fetched_by: String,
389 pub token_name: String,
390 pub token_symbol: String,
391 pub decimals: u8,
392 pub transfer_fee: String,
393 pub total_supply: String,
394 pub minting_account_owner: Option<String>,
395 pub minting_account_subaccount_hex: Option<String>,
396 pub supported_standards: Vec<IcrcTokenStandardRow>,
397 pub metadata: Vec<IcrcTokenMetadataRow>,
398}
399
400#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
406pub struct IcrcBalanceReport {
407 pub schema_version: u32,
408 pub ledger_canister_id: String,
409 pub account_owner: String,
410 pub subaccount_hex: Option<String>,
411 pub fetched_at: String,
412 pub source_endpoint: String,
413 pub fetched_by: String,
414 pub token_symbol: String,
415 pub decimals: u8,
416 pub balance: String,
417}
418
419#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
425pub struct IcrcAllowanceReport {
426 pub schema_version: u32,
427 pub ledger_canister_id: String,
428 pub account_owner: String,
429 pub account_subaccount_hex: Option<String>,
430 pub spender_owner: String,
431 pub spender_subaccount_hex: Option<String>,
432 pub fetched_at: String,
433 pub source_endpoint: String,
434 pub fetched_by: String,
435 pub token_symbol: String,
436 pub decimals: u8,
437 pub allowance: String,
438 pub expires_at_unix_nanos: Option<String>,
439}
440
441#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
447pub struct IcrcIndexReport {
448 pub schema_version: u32,
449 pub ledger_canister_id: String,
450 pub fetched_at: String,
451 pub source_endpoint: String,
452 pub fetched_by: String,
453 pub index_canister_id: Option<String>,
454 pub index_error: Option<String>,
455}
456
457#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
463pub struct IcrcTransactionsReport {
464 pub schema_version: u32,
465 pub ledger_canister_id: String,
466 pub fetched_at: String,
467 pub source_endpoint: String,
468 pub fetched_by: String,
469 pub requested_start: String,
470 pub requested_limit: u32,
471 pub follow_archives: bool,
472 pub log_length: Option<String>,
473 pub blocks: Vec<IcrcTransactionBlockRow>,
474 pub archived_blocks: Vec<IcrcArchivedBlocksRow>,
475 pub followed_archive_blocks: Vec<IcrcFollowedArchiveBlockRow>,
476 pub archive_follow_errors: Vec<IcrcArchiveFollowErrorRow>,
477}
478
479#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
485pub struct IcrcBlockTypesReport {
486 pub schema_version: u32,
487 pub ledger_canister_id: String,
488 pub fetched_at: String,
489 pub source_endpoint: String,
490 pub fetched_by: String,
491 pub block_types: Vec<IcrcBlockTypeRow>,
492}
493
494#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
500pub struct IcrcArchivesReport {
501 pub schema_version: u32,
502 pub ledger_canister_id: String,
503 pub from_canister_id: Option<String>,
504 pub fetched_at: String,
505 pub source_endpoint: String,
506 pub fetched_by: String,
507 pub archives: Vec<IcrcArchiveRow>,
508}
509
510#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
516pub struct IcrcTipCertificateReport {
517 pub schema_version: u32,
518 pub ledger_canister_id: String,
519 pub fetched_at: String,
520 pub source_endpoint: String,
521 pub fetched_by: String,
522 pub certificate_present: bool,
523 pub certificate_hex: Option<String>,
524 pub certificate_bytes: Option<usize>,
525 pub hash_tree_hex: Option<String>,
526 pub hash_tree_bytes: Option<usize>,
527}
528
529#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
535pub struct IcrcCapabilitiesReport {
536 pub schema_version: u32,
537 pub ledger_canister_id: String,
538 pub fetched_at: String,
539 pub source_endpoint: String,
540 pub fetched_by: String,
541 pub supported_standards: Vec<IcrcTokenStandardRow>,
542 pub capabilities: Vec<IcrcCapabilityRow>,
543}
544
545#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
551pub struct IcrcCapabilityRow {
552 pub capability: String,
553 pub method: String,
554 pub status: String,
555 pub details: Option<String>,
556 pub error: Option<String>,
557}
558
559#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
565pub struct IcrcTokenStandardRow {
566 pub name: String,
567 pub url: String,
568}
569
570#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
576pub struct IcrcTokenMetadataRow {
577 pub key: String,
578 pub value_type: String,
579 pub value: JsonValue,
580}
581
582#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
588pub struct IcrcTransactionBlockRow {
589 pub index: String,
590 pub block_type: Option<String>,
591 pub transaction_kind: Option<String>,
592 pub timestamp_unix_nanos: Option<String>,
593 pub amount_base_units: Option<String>,
594 pub raw_block: JsonValue,
595}
596
597#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
603pub struct IcrcArchivedBlocksRow {
604 pub callback_canister_id: String,
605 pub callback_method: String,
606 pub ranges: Vec<IcrcArchivedRangeRow>,
607}
608
609#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
615pub struct IcrcArchivedRangeRow {
616 pub start: String,
617 pub length: String,
618}
619
620#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
626pub struct IcrcFollowedArchiveBlockRow {
627 pub archive_canister_id: String,
628 pub callback_method: String,
629 pub index: String,
630 pub block_type: Option<String>,
631 pub transaction_kind: Option<String>,
632 pub timestamp_unix_nanos: Option<String>,
633 pub amount_base_units: Option<String>,
634 pub raw_block: JsonValue,
635}
636
637#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
643pub struct IcrcArchiveFollowErrorRow {
644 pub callback_canister_id: String,
645 pub callback_method: String,
646 pub ranges: Vec<IcrcArchivedRangeRow>,
647 pub error: String,
648}
649
650#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
656pub struct IcrcBlockTypeRow {
657 pub block_type: String,
658 pub url: String,
659}
660
661#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
667pub struct IcrcArchiveRow {
668 pub canister_id: String,
669 pub start: String,
670 pub end: String,
671}
672
673#[cfg(feature = "host")]
679#[derive(Clone, Debug, Eq, PartialEq)]
680pub(in crate::icrc) struct IcrcTokenData {
681 pub(in crate::icrc) token_name: String,
682 pub(in crate::icrc) token_symbol: String,
683 pub(in crate::icrc) decimals: u8,
684 pub(in crate::icrc) transfer_fee: String,
685 pub(in crate::icrc) total_supply: String,
686 pub(in crate::icrc) minting_account_owner: Option<String>,
687 pub(in crate::icrc) minting_account_subaccount_hex: Option<String>,
688 pub(in crate::icrc) supported_standards: Vec<IcrcTokenStandardRow>,
689 pub(in crate::icrc) metadata: Vec<IcrcTokenMetadataRow>,
690}
691
692#[cfg(feature = "host")]
698#[derive(Clone, Debug, Eq, PartialEq)]
699pub(in crate::icrc) struct IcrcBalanceData {
700 pub(in crate::icrc) token_symbol: String,
701 pub(in crate::icrc) decimals: u8,
702 pub(in crate::icrc) balance: String,
703}
704
705#[cfg(feature = "host")]
711#[derive(Clone, Debug, Eq, PartialEq)]
712pub(in crate::icrc) struct IcrcAllowanceData {
713 pub(in crate::icrc) token_symbol: String,
714 pub(in crate::icrc) decimals: u8,
715 pub(in crate::icrc) allowance: String,
716 pub(in crate::icrc) expires_at_unix_nanos: Option<String>,
717}
718
719#[cfg(feature = "host")]
725#[derive(Clone, Debug, Eq, PartialEq)]
726pub(in crate::icrc) struct IcrcIndexData {
727 pub(in crate::icrc) index_canister_id: Option<String>,
728 pub(in crate::icrc) index_error: Option<String>,
729}
730
731#[cfg(feature = "host")]
737#[derive(Clone, Debug, PartialEq)]
738pub(in crate::icrc) struct IcrcTransactionsData {
739 pub(in crate::icrc) log_length: Option<String>,
740 pub(in crate::icrc) blocks: Vec<IcrcTransactionBlockRow>,
741 pub(in crate::icrc) archived_blocks: Vec<IcrcArchivedBlocksRow>,
742 pub(in crate::icrc) followed_archive_blocks: Vec<IcrcFollowedArchiveBlockRow>,
743 pub(in crate::icrc) archive_follow_errors: Vec<IcrcArchiveFollowErrorRow>,
744}
745
746#[cfg(feature = "host")]
752#[derive(Clone, Debug, Eq, PartialEq)]
753pub(in crate::icrc) struct IcrcBlockTypesData {
754 pub(in crate::icrc) block_types: Vec<IcrcBlockTypeRow>,
755}
756
757#[cfg(feature = "host")]
763#[derive(Clone, Debug, Eq, PartialEq)]
764pub(in crate::icrc) struct IcrcArchivesData {
765 pub(in crate::icrc) archives: Vec<IcrcArchiveRow>,
766}
767
768#[cfg(feature = "host")]
774#[derive(Clone, Debug, Eq, PartialEq)]
775pub(in crate::icrc) struct IcrcTipCertificateData {
776 pub(in crate::icrc) certificate_hex: Option<String>,
777 pub(in crate::icrc) certificate_bytes: Option<usize>,
778 pub(in crate::icrc) hash_tree_hex: Option<String>,
779 pub(in crate::icrc) hash_tree_bytes: Option<usize>,
780}
781
782#[cfg(feature = "host")]
788#[derive(Clone, Debug, Eq, PartialEq)]
789pub(in crate::icrc) struct IcrcCapabilitiesData {
790 pub(in crate::icrc) supported_standards: Vec<IcrcTokenStandardRow>,
791 pub(in crate::icrc) capabilities: Vec<IcrcCapabilityRow>,
792}
793
794#[cfg(feature = "host")]
795pub(in crate::icrc) fn normalize_subaccount_hex(value: &str) -> Result<String, IcrcError> {
796 let bytes = subaccount_bytes_from_hex(value)?;
797 Ok(hex_bytes(&bytes))
798}
799
800#[cfg(feature = "host")]
801pub(in crate::icrc) fn subaccount_bytes_from_hex(value: &str) -> Result<Vec<u8>, IcrcError> {
802 let value = value.trim();
803 if !value.len().is_multiple_of(2) {
804 return Err(IcrcError::InvalidSubaccountHex {
805 reason: "hex string must contain an even number of characters".to_string(),
806 });
807 }
808 let bytes = (0..value.len())
809 .step_by(2)
810 .map(|index| {
811 u8::from_str_radix(&value[index..index + 2], 16).map_err(|err| {
812 IcrcError::InvalidSubaccountHex {
813 reason: err.to_string(),
814 }
815 })
816 })
817 .collect::<Result<Vec<_>, _>>()?;
818 if bytes.len() != 32 {
819 return Err(IcrcError::InvalidSubaccountLength { bytes: bytes.len() });
820 }
821 Ok(bytes)
822}