Asset flow
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.
- For burn operations, the receiver needs to be set to the mint 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();