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
11pub type Subaccount = [u8; 32];
16
17pub const DEFAULT_SUBACCOUNT: &Subaccount = &[0; 32];
18
19#[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 #[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 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
117fn 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}