Skip to main content

ic_papi_guard/guards/
caller_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::{caller::TokenAmount, cycles::cycles_ledger_canister_id, 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.
9#[derive(Default)]
10pub struct CallerPaysIcrc2CyclesPaymentGuard {}
11
12impl PaymentGuardTrait for CallerPaysIcrc2CyclesPaymentGuard {
13    async fn deduct(&self, fee: TokenAmount) -> Result<(), PaymentError> {
14        let caller = ic_cdk::caller();
15        let own_canister_id = ic_cdk::api::id();
16        let payer_account = Account {
17            owner: caller,
18            subaccount: None,
19        };
20        // The patron must not be the vendor itself (this canister).
21        if payer_account.owner == own_canister_id {
22            return Err(PaymentError::InvalidPatron);
23        }
24        // The cycles ledger has a special `withdraw_from` method, similar to `transfer_from`,
25        // but that adds the cycles to the canister rather than putting it into a ledger account.
26        ic_cycles_ledger_client::Service(cycles_ledger_canister_id())
27            .withdraw_from(&WithdrawFromArgs {
28                to: own_canister_id,
29                amount: Nat::from(fee),
30                from: payer_account,
31                spender_subaccount: None,
32                created_at_time: None,
33            })
34            .await
35            .map_err(|(rejection_code, string)| {
36                eprintln!(
37                    "Failed to reach ledger canister at {}: {rejection_code:?}: {string}",
38                    cycles_ledger_canister_id()
39                );
40                PaymentError::LedgerUnreachable {
41                    ledger: cycles_ledger_canister_id(),
42                }
43            })?
44            .0
45            .map_err(|error| {
46                eprintln!(
47                    "Failed to withdraw from ledger canister at {}: {error:?}",
48                    cycles_ledger_canister_id()
49                );
50                PaymentError::LedgerWithdrawFromError {
51                    ledger: cycles_ledger_canister_id(),
52                    error,
53                }
54            })
55            .map(|_| ())
56    }
57}