Skip to main content

ic_papi_guard/guards/
patron_pays_icrc2_tokens.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, Principal};
4use ic_cycles_ledger_client::TransferFromArgs;
5use ic_papi_api::{caller::TokenAmount, principal2account, Account};
6
7/// Accepts cycles using an ICRC-2 approve followed by withdrawing the cycles to the current canister.  Withdrawing
8/// cycles to the current canister is specific to the cycles ledger canister; it is not part of the ICRC-2 standard.
9pub struct PatronPaysIcrc2TokensPaymentGuard {
10    /// The ledger for that specific token
11    pub ledger: Principal,
12    /// The patron paying on behalf of the caller
13    pub patron: Account,
14}
15
16impl PaymentGuardTrait for PatronPaysIcrc2TokensPaymentGuard {
17    async fn deduct(&self, cost: TokenAmount) -> Result<(), PaymentError> {
18        let caller = ic_cdk::api::caller();
19        let own_canister_id = ic_cdk::api::id();
20        let spender_subaccount = 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        // Note: The cycles ledger client is ICRC-2 compatible so can be used here.
26        ic_cycles_ledger_client::Service(self.ledger)
27            .icrc_2_transfer_from(&TransferFromArgs {
28                from: self.patron.clone(),
29                to: Account {
30                    owner: ic_cdk::api::id(),
31                    subaccount: None,
32                },
33                amount: Nat::from(cost),
34                spender_subaccount: Some(spender_subaccount),
35                created_at_time: None,
36                memo: None,
37                fee: None,
38            })
39            .await
40            .map_err(|(rejection_code, string)| {
41                eprintln!(
42                    "Failed to reach ledger canister at {}: {rejection_code:?}: {string}",
43                    self.ledger
44                );
45                PaymentError::LedgerUnreachable {
46                    ledger: self.ledger,
47                }
48            })?
49            .0
50            .map_err(|error| {
51                eprintln!(
52                    "Failed to withdraw from ledger canister at {}: {error:?}",
53                    self.ledger
54                );
55                PaymentError::LedgerTransferFromError {
56                    ledger: self.ledger,
57                    error,
58                }
59            })
60            .map(|_| ())
61    }
62}