canic_types/
account.rs

1use crate::cdk::icrc_ledger_types::icrc1::account::Account as IcrcAccount;
2use candid::{CandidType, Principal};
3use serde::{Deserialize, Serialize};
4use std::{
5    cmp::Ordering,
6    fmt::{self, Display},
7    hash::{Hash, Hasher},
8    str::FromStr,
9};
10
11///
12/// Subaccount
13///
14
15pub type Subaccount = [u8; 32];
16
17pub const DEFAULT_SUBACCOUNT: &Subaccount = &[0; 32];
18
19///
20/// Account
21///
22/// [Account](https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-3/README.md#value)
23/// representation of ledgers supporting the ICRC-1 standard.
24///
25/// Code ported from icrc-ledger-types as we don't want to include that one, it's out of
26/// date and has a lot of extra dependencies
27///
28
29#[derive(CandidType, Clone, Copy, Debug, Deserialize, Serialize)]
30pub struct Account {
31    pub owner: Principal,
32    pub subaccount: Option<Subaccount>,
33}
34
35impl Account {
36    pub fn new<P: Into<Principal>, S: Into<Subaccount>>(owner: P, subaccount: Option<S>) -> Self {
37        Self {
38            owner: owner.into(),
39            subaccount: subaccount.map(Into::into),
40        }
41    }
42
43    /// The effective subaccount of an account - the subaccount if it is set, otherwise the default
44    /// subaccount of all zeroes.
45    #[inline]
46    #[must_use]
47    pub fn effective_subaccount(&self) -> &Subaccount {
48        self.subaccount.as_ref().unwrap_or(DEFAULT_SUBACCOUNT)
49    }
50}
51
52impl Display for Account {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        // https://github.com/dfinity/ICRC-1/blob/main/standards/ICRC-1/TextualEncoding.md#textual-encoding-of-icrc-1-accounts
55        match &self.subaccount {
56            None => write!(f, "{}", self.owner),
57            Some(subaccount) if subaccount == &[0; 32] => write!(f, "{}", self.owner),
58            Some(subaccount) => {
59                let checksum = full_account_checksum(self.owner.as_slice(), subaccount.as_slice());
60                let hex_subaccount = hex::encode(subaccount.as_slice());
61                let hex_subaccount = hex_subaccount.trim_start_matches('0');
62                write!(f, "{}-{}.{}", self.owner, checksum, hex_subaccount)
63            }
64        }
65    }
66}
67
68impl Eq for Account {}
69
70impl PartialEq for Account {
71    fn eq(&self, other: &Self) -> bool {
72        self.owner == other.owner && self.effective_subaccount() == other.effective_subaccount()
73    }
74}
75
76impl From<Principal> for Account {
77    fn from(owner: Principal) -> Self {
78        Self {
79            owner,
80            subaccount: None,
81        }
82    }
83}
84
85impl FromStr for Account {
86    type Err = String;
87
88    fn from_str(s: &str) -> Result<Self, Self::Err> {
89        let acc = IcrcAccount::from_str(s).map_err(|e| e.to_string())?;
90
91        Ok(Self::new(acc.owner, acc.subaccount))
92    }
93}
94
95impl Hash for Account {
96    fn hash<H: Hasher>(&self, state: &mut H) {
97        self.owner.hash(state);
98        self.effective_subaccount().hash(state);
99    }
100}
101
102impl Ord for Account {
103    fn cmp(&self, other: &Self) -> Ordering {
104        self.owner.cmp(&other.owner).then_with(|| {
105            self.effective_subaccount()
106                .cmp(other.effective_subaccount())
107        })
108    }
109}
110
111impl PartialOrd for Account {
112    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
113        Some(self.cmp(other))
114    }
115}
116
117// make your internal code public, dfinity!
118fn full_account_checksum(owner: &[u8], subaccount: &[u8]) -> String {
119    let mut crc32hasher = crc32fast::Hasher::new();
120    crc32hasher.update(owner);
121    crc32hasher.update(subaccount);
122    let checksum = crc32hasher.finalize().to_be_bytes();
123
124    base32::encode(base32::Alphabet::Rfc4648 { padding: false }, &checksum).to_lowercase()
125}