Icrc2 transfer from wallet to canister local troubles

this example show cli way, but I am looking do same from Rust code. Please share rust example?

unfortunately, im lookin Rust example too.

I’m looking for a way to do it directly in Rust while call one purchase function. Is it impossible? Players must send amount to address, type by hands canister id and amount?

Somehow you have to get the ICP into your canister, the common way to do this locally is to have a minter account that mints tokens to the canister. You can also add another entry to initial_values that you set to the canisters default AccountIdentifer in step 7, this way you don’t have to transfer funds to it via the cli.

You can get the canisters AccountIdentifier with dfx ledger account-id --of-canister <your_canisters_name>.

This way can’t reproduce real life transaction flow, which i try to avoid. Testnet build must cover common case on mainnet to save time for development/tests

This is the alternative.

Isn’t, unfortunately. Issue not in get AccountIdentifer to where money send - I can grab it from client side while. Issue in general errors during simple transaction from client balance to canister balance. And still no solution in docs and here in answers.

1 Like

Did you clone the repo and follow the steps outlined? I just tested them and they should work. From there you can adapt and move to your desired project

It may works on your repo but doesn’t work at mine. This is a problem.

1 Like

Then do as I said, start from the example repo and work your way to the desired outcome. Somewhere along the way you’re making a mistake.
What you are trying to do is definitely possible, maybe you take some time to reread our thread and play around with it a bit more?
Another possibility is that your provide a repository with a small example showing how to reproduce your issue.

please share your github username

after deploy done with demo.sh i call:

dfx canister call --identity alice motodex purchase ‘(0, null , 100_000: nat64)’

I fixed your code in this branch. As the code is not publicly available I will try to walk others through the steps.

we use the following steps to deploy the ICP ledger locally:

# we create a new identity for alice
dfx identity new alice --disable-encryption || true

# we store allice account identifier in a variable
ALICE_ACCOUNT_ID=$(dfx --identity alice ledger account-id)

# we deploy the ledger and assign an initial balance of 10_000_000_000 ICP to alice
dfx deploy --specified-id ryjl3-tyaaa-aaaaa-aaaba-cai icp_ledger_canister --argument "
  (variant {
    Init = record {
      minting_account = \"$MINTER_ACCOUNT_ID\";
      initial_values = vec {
        record {
          \"$ALICE_ACCOUNT_ID\";
          record {
            e8s = 1_000_000_000_000_000_000 : nat64;
          };
        };
      };
      send_whitelist = vec {};
      transfer_fee = opt record {
        e8s = 10_000 : nat64;
      };
      token_symbol = opt \"LICP\";
      token_name = opt \"Local ICP\";
      feature_flags = opt record {
        icrc2 = true;
      };
    }
  })
"

# alice approves the motodex canister default account to spend 100 ICP on her behalf
dfx canister call --identity alice icp_ledger_canister icrc2_approve "(
  record {
    spender= record {
      owner = principal \"$MOTODEX_ADDRESS\";
    };
    amount = 10_000_000_000: nat;
  }
)"

# alice calls the motodex canister to purchase an NFT
dfx canister call --identity alice motodex purchase "(1, null, 10_000_000_000: nat64)"

the implementation of purchase looks like this

pub async fn purchase(
    type_nft: u8,
    referral: Option<Account>,
    attached_deposit: u64,
) -> Result<Nat, String> {
    let owner = STATE.with(|s| s.borrow_mut().contract.get_game_server());

    let to_principal = owner.owner;
    ic_cdk::print(format!("to_principal: {}", to_principal));

    let price_for_type = value_in_main_coin(type_nft);
    ic_cdk::print(format!("price_for_type {}", price_for_type));

    let caller_acc = Account::from(ic_cdk::caller());
    ic_cdk::print(format!("caller_acc {}", caller_acc));

    ic_cdk::println!(
        "Transferring {} tokens from {} to {}",
        &price_for_type,
        &caller_acc,
        &to_principal,
    );

    let transfer_from_args = TransferFromArgs {
        from: caller_acc,
        to: Account::from(to_principal),
        amount: price_for_type.into(),
        fee: None,
        memo: None,
        spender_subaccount: None,
        created_at_time: None,
    };
    // Attempt the asynchronous call to another canister function.
    let transfer_result =
        ic_cdk::call::<(TransferFromArgs,), (Result<BlockIndex, TransferFromError>,)>(
            Principal::from_text("ryjl3-tyaaa-aaaaa-aaaba-cai")
                .expect("Could not decode the principal."),
            "icrc2_transfer_from",
            (transfer_from_args,),
        )
        .await
        .map_err(|e| format!("failed to call ledger: {:?}", e))?
        .0;

    // Check the result of the transfer.
    match transfer_result {
        Ok(block_index) => {
            // If the transfer was successful, execute the additional code.
            // STATE.with(|s| {
            //     s.borrow_mut()
            //         .contract
            //         .purchase(type_nft, referral, attached_deposit);
            // });
            ic_cdk::print(format!("transfer successfull in block {}", block_index));
            Ok(block_index)
        }
        Err(e) => {
            // If there was an error with the transfer, handle it accordingly.
            // For example, you might log the error or return it.
            Err(format!("ledger transfer error: {:?}", e))
        }
    }
}

the crucial points are that we have to use TransferFromArgs and not TransferArgs and the icrc2_transfer_from method instead of icrc1_transfer. the reason being is that we approved the motodex canister to spend funds on behalf of alice, which does not work using icrc1_transfer as there is no way of specifying which account we want to transfer from.
to get the correct types for TransferFromArgs and TransferFromError we use the icrc_ledger_types crate.

2 Likes

thanks a lot, its amazing and works well now

1 Like