Skip to main content

Asset flow

Intermediate
Tutorial

Overview

When interacting with the ICP or ICRC-1 ledgers, there are different asset flow operations that can be executed. These operations are:

  • Transfer: A simple transfer between two entities.
  • Approve: Approving tokens to be spent by another entity.
  • Mint: Creating new tokens (Requires minting account privileges).
  • Burn: Destroying tokens.
  • TransferFrom: Spend approved tokens on behalf of another entity.

Both ledger types support these operations. There are several ways on how to create transactions corresponding to each asset flow operation. This guide will demonstrate how to use dfx using Rust code.

Prerequisites

Transfer tokens

To transfer tokens, your transaction will need the following required fields:

  • to: The account that will receive the tokens.
  • amount: The amount of tokens to be sent.

Additionally, the transaction takes the following optional fields:

  • from_subaccount: The subaccount of the approver.
  • fee: The fee paid by the sender for the transfer. If not set, the default fee will have to be paid.
  • memo: An arbitrary 32 bytes array to ensure transaction hash uniqueness.
  • created_at_time: A timestamp that is used to trigger the deduplication mechanism.

dfx

Here is an example command for a Transfer transaction using dfx:

dfx canister call icrc1_ledger_canister icrc1_transfer "(record { to = record { owner = principal \"sckqo-e2vyl-4rqqu-5g4wf-pqskh-iynjm-46ixm-awluw-ucnqa-4sl6j-mqe\";};  amount = 10_000;})"

Rust

Here is an example Rust code snippet for making a Transfer transaction:

let to = icrc_ledger_types::icrc1::account::Account{ owner:ic_principal::Principal::from_slice(&[1, 2, 3, 4]) , subaccount: None};
let amount = candid::Nat::from(500u64);
let transfer_arg = icrc_ledger_types::icrc1::transfer::TransferArg{
from_subaccount: None,
to,
amount,
fee: None,
memo: : None,
created_at_time: : None,
}
candid::Decode!(&ic_agent.update("icrc1_transfer", &candid::Encode!(&transfer_arg)?).await.unwrap(), Result<candid::Nat, icrc_ledger_types::icrc1::transfer::TransferError>).unwrap();

Mint and burn operations are technically speaking also transfers. They work exactly like regular transfers with certain conditions that have to be met. They are the following:

  • For mint operations, the sender of the transfer has to be the minter account. This means that if you want to make mint transactions, you will need to know the private key of the minter account. The recipient will receive the newly minted tokens.
  • For burn operations, the receiver needs to be set to the mint account while the sender can be any account. You can request the minter account by calling the icrc1_minting_account endpoint of the ledger.

Approve tokens

To approve tokens, your transaction will need the following required fields:

  • spender: The account that is allowed to spend the tokens.
  • amount: The maximum amount of tokens to be spent by the spender.

Additionally, the transaction takes the following optional fields:

  • from_subaccount: The subaccount of the approver.
  • expected_allowance: If set, this field serves as a guarantee that before the new approved amount is set, the expected allowance reflects the previously set approved amount.
  • expires_at: The timestamp at which the allowance will expire.
  • fee: The fee paid by the approver for the approval. If not set, the default fee will have to be paid.
  • memo: An arbitrary 32 bytes array to ensure transaction hash uniqueness.
  • created_at_time: A timestamp that is used to trigger the deduplication mechanism.

dfx

Here is an example command for an Approve transaction using dfx:

dfx canister call icrc1_ledger_canister icrc2_approve "(record { amount = 500; spender = record{owner = principal \"sckqo-e2vyl-4rqqu-5g4wf-pqskh-iynjm-46ixm-awluw-ucnqa-4sl6j-mqe\";} })"

Rust

Here is an example Rust code snippet for making an Approve transaction:

let spender = icrc_ledger_types::icrc1::account::Account{ owner:ic_principal::Principal::from_slice(&[1, 2, 3, 4]) , subaccount: None};
let amount = candid::Nat::from(500u64);
let approve_args = icrc_ledger_types::icrc1::approve::ApproveArgs{
from_subaccount: None,
spender,
amount,
expected_allowance: None,
expires_at: None,
fee: None,
memo: : None,
created_at_time: : None,
}
candid::Decode!(&ic_agent.update("icrc2_approve", &candid::Encode!(&approve_args)?).await.unwrap(), Result<candid::Nat, icrc_ledger_types::icrc2::approve::ApproveError>).unwrap();

Transferring approved tokens

To transfer approved tokens, your transaction will need the following required fields:

  • to: The account that will receive the tokens.
  • from: The approvers account who's tokens will be transferred.
  • amount: The amount of tokens to be sent. Additionally, the transaction takes the following optional fields:
  • spender_subaccount: The subaccount of the spender.
  • fee: The fee paid by the sender for the transfer. If not set the default fee will have to be paid.
  • memo: An arbitrary 32 bytes array to ensure transaction hash uniqueness.
  • created_at_time: A timestamp that is used to trigger the deduplication mechanism.

dfx

Here is an example command for a TransferFrom transaction using dfx:

dfx canister call icrc1_ledger_canister icrc2_transfer_from "(record { from = record { owner = principal \"ltyfs-qiaaa-aaaak-aan3a-cai\";}; to = record { owner = principal \"sckqo-e2vyl-4rqqu-5g4wf-pqskh-iynjm-46ixm-awluw-ucnqa-4sl6j-mqe\";};  amount = 500;})"

Rust

Here is an example Rust code snippet for making a TransferFrom transaction:

let to = icrc_ledger_types::icrc1::account::Account{ owner:ic_principal::Principal::from_slice(&[1, 2, 3, 4]) , subaccount: None};
let from = icrc_ledger_types::icrc1::account::Account{ owner:ic_principal::Principal::from_slice(&[5, 6, 7, 8]) , subaccount: None};
let amount = candid::Nat::from(500u64);
let transfer_from_arg = icrc_ledger_types::icrc1::transfer_from::TransferFromArgs{
spender_subaccount: None,
from,
to,
amount,
fee: None,
memo: : None,
created_at_time: : None,
}
candid::Decode!(&ic_agent.update("icrc2_transfer_from", &candid::Encode!(&transfer_from_arg)?).await.unwrap(), Result<candid::Nat, icrc_ledger_types::icrc2::transfer_from::TransferFromError>).unwrap();