use crate::{cli::common::CurrentUnixSecsError, hex::hex_bytes, runtime::RuntimeError};
use serde::Serialize;
use serde_json::Value as JsonValue;
use std::io;
use thiserror::Error as ThisError;
#[derive(Debug, ThisError)]
pub enum IcrcError {
#[error("{0}")]
Usage(String),
#[error(transparent)]
Clock(#[from] CurrentUnixSecsError),
#[error("failed to create Tokio runtime for ICRC query: {0}")]
Runtime(#[from] RuntimeError),
#[error("failed to build IC agent for endpoint {endpoint}: {reason}")]
AgentBuild { endpoint: String, reason: String },
#[error("invalid {field}: {reason}")]
InvalidPrincipal { field: &'static str, reason: String },
#[error("invalid subaccount hex: {reason}")]
InvalidSubaccountHex { reason: String },
#[error("invalid subaccount length: expected 32 bytes, got {bytes}")]
InvalidSubaccountLength { bytes: usize },
#[error("failed to encode Candid request for {message}: {reason}")]
CandidEncode {
message: &'static str,
reason: String,
},
#[error("ICRC ledger method {method} failed: {reason}")]
AgentCall {
method: &'static str,
reason: String,
},
#[error("failed to decode Candid response {message}: {reason}")]
CandidDecode {
message: &'static str,
reason: String,
},
#[error(transparent)]
Io(#[from] io::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcTokenRequest {
pub(in crate::icrc) source_endpoint: String,
pub(in crate::icrc) now_unix_secs: u64,
pub(in crate::icrc) ledger_canister_id: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcBalanceRequest {
pub(in crate::icrc) source_endpoint: String,
pub(in crate::icrc) now_unix_secs: u64,
pub(in crate::icrc) ledger_canister_id: String,
pub(in crate::icrc) account_owner: String,
pub(in crate::icrc) subaccount_hex: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcAllowanceRequest {
pub(in crate::icrc) source_endpoint: String,
pub(in crate::icrc) now_unix_secs: u64,
pub(in crate::icrc) ledger_canister_id: String,
pub(in crate::icrc) account_owner: String,
pub(in crate::icrc) account_subaccount_hex: Option<String>,
pub(in crate::icrc) spender_owner: String,
pub(in crate::icrc) spender_subaccount_hex: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcIndexRequest {
pub(in crate::icrc) source_endpoint: String,
pub(in crate::icrc) now_unix_secs: u64,
pub(in crate::icrc) ledger_canister_id: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcTransactionsRequest {
pub(in crate::icrc) source_endpoint: String,
pub(in crate::icrc) now_unix_secs: u64,
pub(in crate::icrc) ledger_canister_id: String,
pub(in crate::icrc) start: u64,
pub(in crate::icrc) limit: u32,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcTokenReport {
pub schema_version: u32,
pub ledger_canister_id: String,
pub fetched_at: String,
pub source_endpoint: String,
pub fetched_by: String,
pub token_name: String,
pub token_symbol: String,
pub decimals: u8,
pub transfer_fee: String,
pub total_supply: String,
pub minting_account_owner: Option<String>,
pub minting_account_subaccount_hex: Option<String>,
pub supported_standards: Vec<IcrcTokenStandardRow>,
pub metadata: Vec<IcrcTokenMetadataRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcBalanceReport {
pub schema_version: u32,
pub ledger_canister_id: String,
pub account_owner: String,
pub subaccount_hex: Option<String>,
pub fetched_at: String,
pub source_endpoint: String,
pub fetched_by: String,
pub token_symbol: String,
pub decimals: u8,
pub balance: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcAllowanceReport {
pub schema_version: u32,
pub ledger_canister_id: String,
pub account_owner: String,
pub account_subaccount_hex: Option<String>,
pub spender_owner: String,
pub spender_subaccount_hex: Option<String>,
pub fetched_at: String,
pub source_endpoint: String,
pub fetched_by: String,
pub token_symbol: String,
pub decimals: u8,
pub allowance: String,
pub expires_at_unix_nanos: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcIndexReport {
pub schema_version: u32,
pub ledger_canister_id: String,
pub fetched_at: String,
pub source_endpoint: String,
pub fetched_by: String,
pub index_canister_id: Option<String>,
pub index_error: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcTransactionsReport {
pub schema_version: u32,
pub ledger_canister_id: String,
pub fetched_at: String,
pub source_endpoint: String,
pub fetched_by: String,
pub requested_start: String,
pub requested_limit: u32,
pub log_length: Option<String>,
pub blocks: Vec<IcrcTransactionBlockRow>,
pub archived_blocks: Vec<IcrcArchivedBlocksRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcTokenStandardRow {
pub name: String,
pub url: String,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcTokenMetadataRow {
pub key: String,
pub value_type: String,
pub value: JsonValue,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcTransactionBlockRow {
pub index: String,
pub block_type: Option<String>,
pub transaction_kind: Option<String>,
pub timestamp_unix_nanos: Option<String>,
pub amount_base_units: Option<String>,
pub raw_block: JsonValue,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcArchivedBlocksRow {
pub callback_canister_id: String,
pub callback_method: String,
pub ranges: Vec<IcrcArchivedRangeRow>,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct IcrcArchivedRangeRow {
pub start: String,
pub length: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcTokenData {
pub(in crate::icrc) token_name: String,
pub(in crate::icrc) token_symbol: String,
pub(in crate::icrc) decimals: u8,
pub(in crate::icrc) transfer_fee: String,
pub(in crate::icrc) total_supply: String,
pub(in crate::icrc) minting_account_owner: Option<String>,
pub(in crate::icrc) minting_account_subaccount_hex: Option<String>,
pub(in crate::icrc) supported_standards: Vec<IcrcTokenStandardRow>,
pub(in crate::icrc) metadata: Vec<IcrcTokenMetadataRow>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcBalanceData {
pub(in crate::icrc) token_symbol: String,
pub(in crate::icrc) decimals: u8,
pub(in crate::icrc) balance: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcAllowanceData {
pub(in crate::icrc) token_symbol: String,
pub(in crate::icrc) decimals: u8,
pub(in crate::icrc) allowance: String,
pub(in crate::icrc) expires_at_unix_nanos: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::icrc) struct IcrcIndexData {
pub(in crate::icrc) index_canister_id: Option<String>,
pub(in crate::icrc) index_error: Option<String>,
}
#[derive(Clone, Debug, PartialEq)]
pub(in crate::icrc) struct IcrcTransactionsData {
pub(in crate::icrc) log_length: Option<String>,
pub(in crate::icrc) blocks: Vec<IcrcTransactionBlockRow>,
pub(in crate::icrc) archived_blocks: Vec<IcrcArchivedBlocksRow>,
}
pub(in crate::icrc) fn normalize_subaccount_hex(value: &str) -> Result<String, IcrcError> {
let bytes = subaccount_bytes_from_hex(value)?;
Ok(hex_bytes(&bytes))
}
pub(in crate::icrc) fn subaccount_bytes_from_hex(value: &str) -> Result<Vec<u8>, IcrcError> {
let value = value.trim();
if !value.len().is_multiple_of(2) {
return Err(IcrcError::InvalidSubaccountHex {
reason: "hex string must contain an even number of characters".to_string(),
});
}
let bytes = (0..value.len())
.step_by(2)
.map(|index| {
u8::from_str_radix(&value[index..index + 2], 16).map_err(|err| {
IcrcError::InvalidSubaccountHex {
reason: err.to_string(),
}
})
})
.collect::<Result<Vec<_>, _>>()?;
if bytes.len() != 32 {
return Err(IcrcError::InvalidSubaccountLength { bytes: bytes.len() });
}
Ok(bytes)
}