Skip to main content

Create a token

Intermediate
Tutorial

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 export several environmental variables. 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.

export PRE_MINTED_TOKENS=10_000_000_000
dfx identity use default
export DEFAULT=$(dfx identity get-principal)

TRANSFER_FEE

Fee that users of the ledger will have to pay anytime they want to make a transfer.

export TRANSFER_FEE=10_000

ARCHIVE_CONTROLLER

The controller principal of the archive canisters.

dfx identity new archive_controller
dfx identity use archive_controller
export ARCHIVE_CONTROLLER=$(dfx identity get-principal)

TRIGGER_THRESHOLD

The number of blocks to archive when the trigger threshold is exceeded.

export TRIGGER_THRESHOLD=2000

CYCLE_FOR_ARCHIVE_CREATION

The number of cycles that will be sent to the archive canister when it is created.

export CYCLE_FOR_ARCHIVE_CREATION=10000000000000

NUM_OF_BLOCK_TO_ARCHIVE

The number of blocks that will be archived.

export NUM_OF_BLOCK_TO_ARCHIVE=1000

TOKEN_NAME

The name of your token.

export TOKEN_NAME="My Token"

TOKEN_SYMBOL

The ticker symbol of your new token.

export TOKEN_SYMBOL="XMTK"

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
export MINTER=$(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.

export FEATURE_FLAGS=false

Edit the project's dfx.json file

Next, insert these environment variables into your project's dfx.json file as init arguments for your ledger canister:

{
"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 = ${FEATURE_FLAGS}};
     initial_balances = vec { record { record { owner = principal \"$DEFAULT\"; }; \"$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 \"$CYCLE_FOR_ARCHIVE_CREATION\";
     };
 }
})"
},
},
"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 the ledger.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 the cycles_for_archive_creation.

Next steps

Learn how to interact with the ICRC-1 ledger

Resources