Icrc2 transfer from wallet to canister local troubles

I done local deploy

“icp_ledger_canister”: {
“type”: “custom”,
“candid”: “NO LINK ledger.did”,
“wasm”: “NO LINK ic/d87954601e4b22972899e9957e800406a0a6b929/canisters/ledger-canister.wasm.gz”,
“remote”: {
“id”: {
“ic”: “ryjl3-tyaaa-aaaaa-aaaba-cai”
}
}
},
“internet_identity” : {
“type”: “pull”,
“id”: “rdmx6-jaaaa-aaaaa-aaadq-cai”
}

and done appoval
dfx canister call --identity alice icp_ledger_canister icrc2_approve ‘(record{
spender= record {
owner = principal "’$MOTODEX_ADDRESS’";
};
amount= 1_000_000_000_000_000_000: nat;
})’

but this function call:
pub async fn purchase(type_nft: u8, referral: Option, attached_deposit: u128) {
let owner = STATE.with(|s| s.borrow_mut().contract.get_game_server());
let p = owner.owner;
let price_for_type = value_in_main_coin(type_nft);
ic_cdk::print(format!(“price_for_type {}”,price_for_type));
let caller = ic_cdk::caller();
let caller_acc = Account::from(caller);

ic_cdk::print(format!("caller {}",caller));
ic_cdk::print(format!("caller_acc {}",caller_acc));


transfer(
    Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai").unwrap(),//MAINNET_LEDGER_CANISTER_ID,
    TransferArgs {
        memo: Memo(0),
        amount: Tokens::from_e8s(price_for_type as u64),
        fee: Tokens::from_e8s(0),//DEFAULT_FEE,//Tokens::from_e8s(0)
        from_subaccount: None,
        to: AccountIdentifier::new(&p, &DEFAULT_SUBACCOUNT),
        created_at_time: None,
    }
).await.expect("call to ledger failed").expect("transfer failed");

show no balance:
2024-02-28 10:16:37.646674 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] Panicked at ‘transfer failed: InsufficientFunds { balance: Tokens { e8s: 0 } }’, motodex/src/update_method.rs:85:45

balance here:

Can anybody help?

what is the value of $MOTODEX_ADDRESS and what is the balance of alice on the local icp_ledger_canister?

do you have a repo with the minimal steps to reproduce this?

MOTODEX_ADDRESS be2us-64aaa-aaaaa-qaabq-cai

icrcl_balance_of: (record {owner:principal; subaccount:opt vec nat8}) → (nat) query

owner

xopkt-er5ls-7douf-nic2h-u7ch3-1

subaccount

QUERY

RANDOM

(999999999999990000)

I can;t attach images for proof

I have, but it private. Opened for grants team if u in

We updated the example, let me know if that helps.

error[E0277]: the ? operator can only be used in an async function that returns Result or Option (or another type that implements FromResidual)
→ motodex/src/update_method.rs:97:64
|
65 | pub async fn purchase(type_nft: u8, referral: Option, attached_deposit: u128) {
| __________________________________________________________________________________________-
66 | | let owner = STATE.with(|s| s.borrow_mut().contract.get_game_server());
67 | | let to_principal = owner.owner;
68 | | let price_for_type = value_in_main_coin(type_nft);
… |
97 | | .map_err(|e| format!(“failed to call ledger: {:?}”, e))?
| | ^ cannot use the ? operator in an async function that returns ()

I MUST use canister account to receive money first and then transfer from canister to wallet?

The error you’re encountering, error[E0277]: the ? operator can only be used in an async function that returns Result or Option (or another type that implements FromResidual), is due to a misuse of the ? operator in Rust.

The ? operator in Rust is a convenient shorthand for error handling. It is used to either unwrap the value inside an Ok variant of a Result, or return the error (Err variant) from the current function. This operator can only be used in functions that return a Result, Option, or any other type that implements the std::ops::Try trait (through the FromResidual trait in Rust 2021 edition and onwards).

In your case, the function purchase is an async function that implicitly returns (), the unit type, because there’s no explicit return type specified. Since () does not implement the FromResidual trait for the Result or Option types, you cannot use the ? operator within this function.

To fix this error, you have a couple of options:

  1. Change the Return Type of the Function to Result or Option: If your function is intended to perform operations that might fail, you should change its return type to Result<T, E> (where T is the success type and E is the error type) or Option<T> (if the function might not return a value). For example, if the function should return nothing on success but might fail with a string error, you could change the signature to:

    pub async fn purchase(type_nft: u8, referral: Option<u8>, attached_deposit: u128) -> Result<(), String> {
        // Function body
    }
    

    Then, you can use the ? operator as intended, and if there’s an error, return it from the function. For successful execution, return Ok(()).

  2. Handle the Error Without the ? Operator: If changing the function’s return type is not desirable, you will need to handle the error without using the ? operator. This typically involves using pattern matching or methods like match or if let. For example:

    .map_err(|e| format!("failed to call ledger: {:?}", e))
    .map(|_| ()) // Convert the Ok variant to ()
    .unwrap_or_else(|e| {
        // Handle the error e, for example, by logging it
        eprintln!("{}", e);
    });
    

This way, you explicitly handle both the success and error cases without relying on the ? operator, thus avoiding the need for the function to return a Result or Option.

No, you can use an approval as well. The ICP ledger supports the ICRC-2 standard, you can read more here and here.

follow your example:

pub async fn purchase(type_nft: u8, referral: Option, attached_deposit: u64) {
let owner = STATE.with(|s| s.borrow_mut().contract.get_game_server());

let canister_id = ic_cdk::id();
let canister_account = AccountIdentifier::new(&canister_id, &DEFAULT_SUBACCOUNT);

let to_principal = owner.owner;
let price_for_type = value_in_main_coin(type_nft);
ic_cdk::print(format!("price_for_type {}",price_for_type));
let caller = ic_cdk::caller();
let caller_acc = Account::from(caller);

ic_cdk::print(format!("caller {}",caller));
ic_cdk::print(format!("caller_acc {}",caller_acc));

let to_subaccount = DEFAULT_SUBACCOUNT;

ic_cdk::println!(
    "Transferring {} tokens to principal {} subaccount {:?}",
    &price_for_type,
    &to_principal,
    &to_subaccount
);
let transfer_args = ic_ledger_types::TransferArgs {
    memo: Memo(0),
    amount: Tokens::from_e8s(price_for_type),
    fee: Tokens::from_e8s(10_000),
    // The subaccount of the account identifier that will be used to withdraw tokens and send them
    // to another account identifier. If set to None then the default subaccount will be used.
    // See the [Ledger doc](https://internetcomputer.org/docs/current/developer-docs/integrations/ledger/#accounts).
    from_subaccount: None,
    to: canister_account,
    created_at_time: None,
};
ic_ledger_types::transfer(MAINNET_LEDGER_CANISTER_ID, transfer_args)
    .await.expect("TODO: panic message 1 ").expect("TODO: panic message 2");

2024-02-29 13:42:15.570881 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] price_for_type 71428571
2024-02-29 13:42:15.570881 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] caller bzyds-lsngb-2hsp6-cctoq-2mttx-mw6dj-glyta-nl4p2-yenlc-udrvc-yqe
2024-02-29 13:42:15.570881 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] caller_acc bzyds-lsngb-2hsp6-cctoq-2mttx-mw6dj-glyta-nl4p2-yenlc-udrvc-yqe
2024-02-29 13:42:15.570881 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] Transferring 71428571 tokens to principal dx64y-jtncv-qjwys-cpqa6-rc7sn-4bmgi-4a6m2-o5cv2-lijbd-kvkzi-uqe subaccount Subaccount([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
2024-02-29 13:42:15.570881 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] Panicked at ‘TODO: panic message 2: InsufficientFunds { balance: Tokens { e8s: 0 } }’, motodex/src/update_method.rs:101:49

but

› icrc1_balance_of(record {owner=principal “bzyds-lsngb-2hsp6-cctoq-2mttx-mw6dj-glyta-nl4p2-yenlc-udrvc-yqe”; subaccount=null})

(1000000000000000000)

Your query in example to check balance doesn’t work:

Alexs-MBP:icrc7-main alex$ dfx canister call icp_ledger_canister account_balance ‘(record { account = ‘$(python3 -c ‘print(“vec{” + “;”.join([str(b) for b in bytes.fromhex(“bzyds-lsngb-2hsp6-cctoq-2mttx-mw6dj-glyta-nl4p2-yenlc-udrvc-yqe”)]) + “}”)’)’})’
Traceback (most recent call last):
File “”, line 1, in
ValueError: non-hexadecimal number found in fromhex() arg at position 1
error: parser error
┌─ Candid argument:1:21

1 │ (record { account = })
│ ^ Unexpected token

= Expects one of “(”, “blob”, “bool”, “decimal”, “float”, “func”, “hex”,
“null”, “opt”, “principal”, “record”, “service”, “sign”, “text”,
“variant”, “vec”

Error: Failed to create argument blob.
Caused by: Failed to create argument blob.
Invalid data: Unable to serialize Candid values: Invalid argument: Invalid Candid values: Candid parser error: Unrecognized token RBrace found at 20:21
Expected one of “(”, “blob”, “bool”, “decimal”, “float”, “func”, “hex”, “null”,

I try approval with no success too, it was first code mentioned. (and I still can’t use links in answer, even if I quote your message )))

with this, you are transferring to the same account you are transferring from, which doesn’t make sense.

Note that Account and AccountIdentifier are two different concepts that you can’t mix up. You can read more about the differences here. In short, AccountIdentifiers are used by the ICP ledger standard, Accounts are used by the ICRC1 standard.

If you want to transfer funds from the canisters account to the owners account, you have to use
to: AccountIdentifier::new(&to_principal, &DEFAULT_SUBACCOUNT) in ic_ledger_types::TransferArgs.

Note that you are trying to transfer funds from the canister, not from bzyds-lsngb-2hsp6-cctoq-2mttx-mw6dj-glyta-nl4p2-yenlc-udrvc-yqe. This is why you transfer fails, because if you would check the canisters balance with icrc1_balance_of(record {owner=principal “be2us-64aaa-aaaaa-qaabq-cai”; subaccount=null}) it would be 0. So you have to transfer funds from bzyds-lsngb-2hsp6-cctoq-2mttx-mw6dj-glyta-nl4p2-yenlc-udrvc-yqe to be2us-64aaa-aaaaa-qaabq-cai first to send funds to the canister, then you can call transfer to send them from the canister.

The python script here is not intended to be used with principals, but with AccountIdentifiers. You can get your current identies AccountIdentifier using dfx ledger account-id like in this step.

let canister_id = ic_cdk::id();
let canister_account = AccountIdentifier::new(&canister_id, &DEFAULT_SUBACCOUNT);

This is how I grab canister id to where I try send money. Looks like its a sender account, bcs you are telling me that i send to the same address? I so, how to grab own canister id and account to send money from sender wallet to canister?

left: Tokens { e8s: 10000 },\n right: Tokens { e8s: 0 }: Fee for burning should be zero’

Not sure that we expect burning?

How to grab correct canister AccountIdentifier to send money to canister?

2024-03-04 14:10:46.425471 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] Transferring 71428571 tokens to principal kittb-2snro-3ct6c-hvamc-cmjse-iv63f-5kweq-2r3ja-sfe5y-b5iz5-kae subaccount Subaccount([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
2024-03-04 14:10:46.425471 UTC: [Canister be2us-64aaa-aaaaa-qaabq-cai] Panicked at ‘TODO: panic message 2: InsufficientFunds { balance: Tokens { e8s: 0 } }’, motodex/src/update_method.rs:101:49

Follow the steps here, especially step 12.

I dont know what that means

See step 11.

This again indicates that you have not transferred any tokens to your canister yet. If the canister doesn’t hold any tokens, you cannot call transfer on it.