Skip to main content

icrc_ledger_client/
lib.rs

1use async_trait::async_trait;
2use candid::Principal;
3use candid::types::number::Nat;
4use candid::utils::{ArgumentDecoder, ArgumentEncoder};
5use icrc_ledger_types::icrc::generic_metadata_value::MetadataValue as Value;
6use icrc_ledger_types::icrc::metadata_key::MetadataKey;
7use icrc_ledger_types::icrc1::account::Account;
8use icrc_ledger_types::icrc1::transfer::{BlockIndex, TransferArg, TransferError};
9use icrc_ledger_types::icrc2::approve::{ApproveArgs, ApproveError};
10use icrc_ledger_types::icrc2::transfer_from::{TransferFromArgs, TransferFromError};
11
12// Abstraction over the runtime. Implement this in terms of cdk call if you use
13// the cdk or dfn_* if you use dfn_* call.
14#[async_trait]
15pub trait Runtime {
16    async fn call<In, Out>(
17        &self,
18        id: Principal,
19        method: &str,
20        args: In,
21    ) -> Result<Out, (i32, String)>
22    where
23        In: ArgumentEncoder + Send,
24        Out: for<'a> ArgumentDecoder<'a>;
25}
26
27pub struct ICRC1Client<R: Runtime> {
28    pub runtime: R,
29    pub ledger_canister_id: Principal,
30}
31
32// Note (MP): you can check that the bindings are correct by running
33// $ didc bind -t rs rs/ledger_suite/icrc1/ledger/ledger.did
34// The reason why we don't just generate the bindings is to avoid duplicating
35// the in and out structures as we can use the ones defined in ic_icrc1::endpoints.
36
37impl<R: Runtime> ICRC1Client<R> {
38    pub async fn balance_of(&self, account: Account) -> Result<Nat, (i32, String)> {
39        self.runtime
40            .call(self.ledger_canister_id, "icrc1_balance_of", (account,))
41            .await
42            .map(untuple)
43    }
44
45    pub async fn decimals(&self) -> Result<u8, (i32, String)> {
46        self.runtime
47            .call(self.ledger_canister_id, "icrc1_decimals", ())
48            .await
49            .map(untuple)
50    }
51
52    pub async fn name(&self) -> Result<String, (i32, String)> {
53        self.runtime
54            .call(self.ledger_canister_id, "icrc1_name", ())
55            .await
56            .map(untuple)
57    }
58
59    pub async fn metadata(&self) -> Result<Vec<(MetadataKey, Value)>, (i32, String)> {
60        self.runtime
61            .call(self.ledger_canister_id, "icrc1_metadata", ())
62            .await
63            .map(untuple)
64    }
65
66    pub async fn symbol(&self) -> Result<String, (i32, String)> {
67        self.runtime
68            .call(self.ledger_canister_id, "icrc1_symbol", ())
69            .await
70            .map(untuple)
71    }
72
73    pub async fn total_supply(&self) -> Result<Nat, (i32, String)> {
74        self.runtime
75            .call(self.ledger_canister_id, "icrc1_total_supply", ())
76            .await
77            .map(untuple)
78    }
79
80    pub async fn fee(&self) -> Result<Nat, (i32, String)> {
81        self.runtime
82            .call(self.ledger_canister_id, "icrc1_fee", ())
83            .await
84            .map(untuple)
85    }
86
87    pub async fn minting_account(&self) -> Result<Option<Account>, (i32, String)> {
88        self.runtime
89            .call(self.ledger_canister_id, "icrc1_minting_account", ())
90            .await
91            .map(untuple)
92    }
93
94    pub async fn transfer(
95        &self,
96        args: TransferArg,
97    ) -> Result<Result<BlockIndex, TransferError>, (i32, String)> {
98        let result: Result<Nat, TransferError> = self
99            .runtime
100            .call(self.ledger_canister_id, "icrc1_transfer", (args,))
101            .await
102            .map(untuple)?;
103        Ok(result)
104    }
105
106    pub async fn transfer_from(
107        &self,
108        args: TransferFromArgs,
109    ) -> Result<Result<BlockIndex, TransferFromError>, (i32, String)> {
110        let result: Result<Nat, TransferFromError> = self
111            .runtime
112            .call(self.ledger_canister_id, "icrc2_transfer_from", (args,))
113            .await
114            .map(untuple)?;
115        Ok(result)
116    }
117
118    pub async fn approve(
119        &self,
120        args: ApproveArgs,
121    ) -> Result<Result<BlockIndex, ApproveError>, (i32, String)> {
122        let result: Result<Nat, ApproveError> = self
123            .runtime
124            .call(self.ledger_canister_id, "icrc2_approve", (args,))
125            .await
126            .map(untuple)?;
127        Ok(result)
128    }
129}
130
131// extract the element from an unary tuple
132fn untuple<T>(t: (T,)) -> T {
133    t.0
134}