Create a token
Overview
Custom tokens can be created and deployed on ICP using the ICRC-1 or ICRC-2 token standards.
The ICRC-1 standard is considered the 'base' standard, as it defines a set of general functionalities that all ledgers must adhere to. Any tokens and their corresponding ledgers must fulfill all requirements of the ICRC-1 standard. Read about token standards to learn more about what token standards are and the functionalities each one supports.
This guide will provide a quickstart on how you can create and deploy your own custom token using the ICRC-1 standard.
Setup a local ICRC-1 ledger
To create a new ICRC-1 token, you will need to first download and configure a local project for the ICRC-1 ledger canister. This is because each token will need its own ledger canister to record the token's balances and transactions. Follow the ICRC-1 ledger documentation for instructions.
Export token settings
The initialization arguments of the ICRC-1 ledger are not specified in the standard. Thus, the arguments defined in this section are dependent on the reference implementation of the ICRC-1 ledger. If you build your own ICRC-1 ledger, you may use different initialization arguments.
To create a token, you will need to record several values. The full list can be found below:
PRE_MINTED_TOKENS
Amount of tokens that are minted during deployment for a specific account. In this tutorial, it will be the DEFAULT
account.
dfx identity use default
echo $(dfx identity get-principal)
TRANSFER_FEE
Fee that users of the ledger will have to pay anytime they want to make a transfer.
ARCHIVE_CONTROLLER
The controller principal of the archive canisters.
dfx identity new archive_controller
dfx identity use archive_controller
echo $(dfx identity get-principal)
TRIGGER_THRESHOLD
The number of blocks to archive when the trigger threshold is exceeded.
CYCLE_FOR_ARCHIVE_CREATION
The number of cycles that will be sent to the archive canister when it is created.
NUM_OF_BLOCK_TO_ARCHIVE
The number of blocks that will be archived.
TOKEN_NAME
The name of your token.
TOKEN_SYMBOL
The ticker symbol of your new token.
MINTER
The account of the principal responsible for minting and burning tokens (see the ICRC-1 specification). Transfers from the minting account will generate Mint
transactions, resulting in the creation of new tokens. To mint tokens, the minting account must call the icrc1_transfer
function, specifying the recipient in the 'to' value.
Transfers sent to the minting account will generate Burn
transactions, destroying tokens. An icrc1_transfer
call from any principal with the 'to' value set as the minting account will burn tokens.
dfx identity new minter
dfx identity use minter
echo $(dfx identity get-principal)
FEATURE_FLAGS
A flag for enabling or disabling certain extension standards to the ICRC-1 standard. In this case, the reference implementation can also support ICRC-2 transactions.
If you only want to support the ICRC-1 standard, then you can set the flag to false
. If you want to also support the ICRC-2 standard, set it to true
.
Edit the project's dfx.json file
Replace the existing content in the dfx.json
with the following, updating the values of TOKEN_SYMBOL
, TOKEN_NAME
, MINTER
, TRANSFER_FEE
, DEFAULT_ACCOUNT_ID
, PRE_MINTED_TOKENS
, NUM_OF_BLOCKS_TO_ARCHIVE
, TRIGGER_THRESHOLD
, and ARCHIVE_CONTROLLER
with the values obtained in the previous steps:
dfx.json
does not support referring to values through environment variables. Values must be hardcoded in plain text.
{
"canisters": {
"icrc1_ledger_canister": {
"type": "custom",
"candid": "https://raw.githubusercontent.com/dfinity/ic/<REVISION>/rs/ledger_suite/icrc1/ledger/ledger.did",
"wasm": "https://download.dfinity.systems/ic/<REVISION>/canisters/ic-icrc1-ledger.wasm.gz",
"init_arg": "(variant {Init = record { token_symbol = "TOKEN_SYMBOL"; token_name = "TOKEN_NAME"; minting_account = record { owner = principal "MINTER" transfer_fee = TRANSFER_FEE; metadata = vec {}; feature_flags = opt record{icrc2 = false}; initial_balances = vec { record { record { owner = principal "DEFAULT_ACCOUNT_ID"; }; "PRE_MINTED_TOKENS"; }; }; archive_options = record { num_blocks_to_archive = "NUM_OF_BLOCK_TO_ARCHIVE"; trigger_threshold = "TRIGGER_THRESHOLD"; controller_id = principal "ARCHIVE_CONTROLLER"; cycles_for_archive_creation = opt "10000000000000"; }; }} })" },
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}
Alternatively, you can pass these init arguments to the canister through the command line. Learn more about canister init arguments.
Deploy the ledger locally
Deploying to the playground is ICP's equivalent of deploying to a testnet network.
In this workflow, you cannot deploy this canister to the playground (using the flag dfx deploy --playground
) because it does not accept gzipped Wasm files.
Deploy the ICRC-1 ledger canister locally and pass in the arguments to create your token.
dfx start --clean --background
dfx deploy icrc1_ledger_canister --specified-id mxzaz-hqaaa-aaaar-qaada-cai
For additional details, you can learn more in the ICRC-1 ledger documentation.
Your token has been created locally. To interact with your token, learn how to interact with the ICRC-1 ledger.
Deploying your token to the mainnet
If you want to deploy your ICRC-1 token on the mainnet, follow these steps:
Remove the
--specified-id
flag. Your ledger canister will receive a unique canister ID when deployed on the mainnet.Specify the initially minted token value by setting
initial_values = vec {<INITIAL_VALUES>}
. View theledger.did
file for the details of the argument.Add the
--network ic
flag to the command to deploy to the mainnet.Confirm that the
archive_options
field is set. If archiving is disabled, your ledger's memory capacity will be limited to that of a single canister.Be sure that your ledger canister has cycles attached to the
create_canister
messages, using thecycles_for_archive_creation
.
Next steps
Learn how to interact with the ICRC-1 ledger