How to set allowance for icrc2_transfer_from?

my backend code


  const depositCkusdt = async () => {
    const res = await backendActor.deposit_ckusdt();
}
async fn transfer_from(amount: Nat, from: Principal, to: Principal) -> Result<BlockIndex, Error> {
    let args = TransferFromArgs {
        spender_subaccount: None,
        from: Account {
            owner: from,
            subaccount: None,
        },
        to: Account {
            owner: to,
            subaccount: None,
        },
        amount,
        fee: None,
        memo: None,
        created_at_time: None,
    };
    let res = ic_cdk::call::<(TransferFromArgs, ), (Result<BlockIndex, TransferFromError>, )>(
        Principal::from_text("xevnm-gaaaa-aaaar-qafnq-cai").unwrap(),
        "icrc2_transfer_from",
        (args, ),
    )
        .await
        .map_err(|(_, message)| Error::IcCdkError { message })?
        .0
        .map_err(|e| Error::IcCdkError {
            message: format!("{:?}", e),
        });
    res
}


#[update]
async fn deposit_ckusdt() -> Result<Wallet, Error> {
    let mut wallet = Wallet::get(caller());
    let balance = get_user_balance().await;
    if balance.is_err() {
        return Err(Error::IcCdkError {
            message: format!("{:?}", balance),
        });
    }
    let balance = balance.unwrap();
    if balance > Nat::from(3000000 as u64) {
        let fee: Nat = get_fee().await;
        let _ = transfer_from(balance.clone() - fee, caller(), ic_cdk::id()).await?;
        wallet
            .deposit(
                nat_to_u64(balance.clone() / Nat::from(1000000 as u64)) as f64,
                "ExternalWallet".to_string(),
                ExchangeType::Deposit,
            )
            .map_err(|e| Error::IcCdkError {
                message: format!("{:?}", e),
            })?;
        let message = format!("You received {} CKUSD", balance);
        Notification::new(
            format!("{:?}", time::SystemTime::now()),
            caller(),
            NoteContent::ReceivedDeposit(message),
        )
            .save();
    }
    Ok(wallet.clone())
}

my frontend code


const approveTransaction = async (balance, fee_value) => {
    const approveArg: ApproveArgs = {
      fee: [BigInt(fee_value)],
      memo: [],
      from_subaccount: [],
      created_at_time: [],
      amount: BigInt(Number(balance)),
      expected_allowance: [BigInt(Number(balance))],
      expires_at: [],
      spender: {
        owner: Principal.fromText(canisterId),
        subaccount: [],
      },
    };

    return await ckUSDCActor.icrc2_approve(approveArg);
  };



      const fee_value = await fetchFee();
      const account = { owner: Principal.fromText(profile.id), subaccount: [] };
      const balance = await fetchBalance(account);
      console.log({ balance });
      if (balance > BigInt(1000000)) {

        // -------------------- icrc2_approve --------------------
        let approve = await approveTransaction(balance, fee_value);
        console.log({ approve,fee_value });


        // -------------------- icrc2_allowance --------------------
        const allowance: AllowanceArgs = {
          account: {
            owner: Principal.fromText(canisterId),
            subaccount: [],
          },
          spender: {
            owner: Principal.fromText(canisterId),
            subaccount: [],
          },
        };
        let allowance_res: Allowance =
          await ckUSDCActor?.icrc2_allowance(allowance);
        console.log({ allowance_res });

        // -------------------- transfer from user to odoc wallet --------------------
        let deposit = await depositCkusdt();
        console.log({ deposit });
      }

This doesn’t make sense AFAIU. This basically means

if current_allowance = balance {
  allowance = balance;
}

I think this is why your allowance does not properly update (first object in the console screenshot). The full semantics are documented here

In summary I should just expected_allowance :[]

Yes, to get it working this should do it. But if you have high security expectations you probably should still use expected_allowance

what about

const allowance: AllowanceArgs = {
          account: {
            owner: Principal.fromText(canisterId),
            subaccount: [],
          },
          spender: {
            owner: Principal.fromText(canisterId),
            subaccount: [],
          },
        };
``` the owner should be the `caller()` right ? should I call it from the backend ?
I also got error `"InsufficientAllowance { allowance: Nat(20000) }"` despite the transaction was actaully done

You should set the allowance (approve) through the frontend where the canister is the spender, also take into account that an approve is a transaction and a fee payed.

When fetching the allowance you need to specify the user principal where you set the allowance for as the “account” and the “spender” as the canister principal.

So if you want to transfer_from in the user his behalf, make sure to also subtract the fee 10_000 e8s for ICP

Here is an example in rust, do note that these are modified for a specific usecase (using a subaccount for example)

1 Like

u siad we set allowance from the frontend but this is a backend code ?
I tried this

let subaccount : Subaccount = Principal.fromText(profile.id).toUint8Array();
        // -------------------- icrc2_allowance --------------------
        const allowance: AllowanceArgs = {
          account: {
            owner: Principal.fromText(profile.id),
            subaccount: [],
          },
          spender: {
            owner: Principal.fromText(canisterId),
            subaccount: [subaccount],
            // subaccount: [],
          },
        };
        let allowance_res: Allowance =
          await ckUSDCActor?.icrc2_allowance(allowance);
        console.log({ allowance_res });

but I got issue with subaccount format rejected

The subaccount isn’t required and I would only recommend starting to use it once you fully understand the flow because it brings extra complexity to what needs to be solved;

Usually a flow would go like this;

  1. Frontend → icrc2_approve
    (set spender (canister) and amount on behalf of the caller)

  2. Backend → icrc2_allowance
    (get allowance set for and account + spender (in the case the frontend caller is the account and the spender the backend canister)

  3. Backend → icrc2_transfer_from
    (Transfer funds on behalf of the caller from the spender account)

I guess I should have included the transfer_from example as well ic-toolkit-utils/src/helpers/transactions.rs at a3c6c6afce5c2c2f1798eb107df95df825475587 · toolkit-development/ic-toolkit-utils · GitHub

Note that “2” is optional and can be called from both the frontend and backend because it’s public data as long as you know the account and spender. Just mentioned is as backend so the flow makes more sense