Skip to main content

ic_papi_guard/guards/
patron_pays_icrc2_cycles.rs

1//! Code to receive cycles as payment, credited to the canister, using ICRC-2 and a cycles-ledger specific withdrawal method.
2use super::{PaymentError, PaymentGuardTrait};
3use candid::Nat;
4use ic_cycles_ledger_client::WithdrawFromArgs;
5use ic_papi_api::{
6    caller::TokenAmount, cycles::cycles_ledger_canister_id, principal2account, Account,
7};
8
9/// Accepts cycles using an ICRC-2 approve followed by withdrawing the cycles to the current canister.  Withdrawing
10/// cycles to the current canister is specific to the cycles ledger canister; it is not part of the ICRC-2 standard.
11pub struct PatronPaysIcrc2CyclesPaymentGuard {
12    /// The patron paying on behalf of the caller.
13    pub patron: Account,
14}
15
16impl PaymentGuardTrait for PatronPaysIcrc2CyclesPaymentGuard {
17    async fn deduct(&self, fee: TokenAmount) -> Result<(), PaymentError> {
18        let own_canister_id = ic_cdk::api::id();
19        let caller = ic_cdk::caller();
20        let spender_subaccount = Some(principal2account(&caller));
21        // The patron must not be the vendor itself (this canister).
22        if self.patron.owner == own_canister_id {
23            return Err(PaymentError::InvalidPatron);
24        }
25        // The cycles ledger has a special `withdraw_from` method, similar to `transfer_from`,
26        // but that adds the cycles to the canister rather than putting it into a ledger account.
27        ic_cycles_ledger_client::Service(cycles_ledger_canister_id())
28            .withdraw_from(&WithdrawFromArgs {
29                to: own_canister_id,
30                amount: Nat::from(fee),
31                from: self.patron.clone(),
32                spender_subaccount,
33                created_at_time: None,
34            })
35            .await
36            .map_err(|(rejection_code, string)| {
37                eprintln!(
38                    "Failed to reach ledger canister at {}: {rejection_code:?}: {string}",
39                    cycles_ledger_canister_id()
40                );
41                PaymentError::LedgerUnreachable {
42                    ledger: cycles_ledger_canister_id(),
43                }
44            })?
45            .0
46            .map_err(|error| {
47                eprintln!(
48                    "Failed to withdraw from ledger canister at {}: {error:?}",
49                    cycles_ledger_canister_id()
50                );
51                PaymentError::LedgerWithdrawFromError {
52                    ledger: cycles_ledger_canister_id(),
53                    error,
54                }
55            })
56            .map(|_| ())
57    }
58}