Skip to main content

4.2 BIGRC-1 tokens

Advanced
Tutorial

Overview

For developers to create their own fungible tokens on the BigFile, the BIGRC-1 token standard can be used. The acronym "BIGRC" stands for "BigFile Request for Comments", as the standard was created by the BigFile working group. Learn more about the working group.

Standards defined within the BIGRC group are not specific to tokens; anything can be standardized using an BIGRC standard. Some examples may be the standardization of metadata fields or digital assets other than tokens.

The purpose of the BIGRC-1 standard is to create a universally accepted standard for creating and recording token transactions on the BigFile. The BIGRC-1 standard defines the general functionalities of ledgers; any tokens and their corresponding ledgers that want to support the BIGRC-1 standard must fulfill all requirements within the standard. View a detailed account of the standard.

There are several extensions of the BIGRC-1 standard, such as the BIGRC-2 standard. Not all BIGRC standards must be extensions of the BIGRC-1 standard, though. This tutorial will focus on the BIGRC-1 standard, BIGRC-1 ledger, and briefly look into the BIGRC-2 standard's endpoints.

What's the difference between the BIG and BIGRC-1 ledgers?

The BIG ledger is a specific implementation of a ledger, which initially did not follow a standard. The BIG ledger existed prior to the ideation and creation of the BIGRC-1 standard.

BIGRC-1 is an official standard and is not a specific implementation of a ledger. There are several different BIGRC-1 ledgers with different implementations, but all follow the same standard.

The BIG and BIGRC-1 ledger use different endpoints, different transactions, different block objects, in addition to other subtle differences.

Accounts versus AccountIdentifiers?

The most important difference between the BIG ledger and the BIGRC-1 standard is their implementation of accounts. The BIGRC-1 standard specifies Accounts as a struct data type that contains a principal and an optional subaccount.

type Subaccount = blob;
type Account = record { owner : principal; subaccount : opt Subaccount; };

In contrast, the BIG ledger uses AccountIdentifiers to represent accounts, which are essentially a hash of the BIGRC-1 Account value.

An Account can be converted into an AccountIdentifier, but an AccountIdentifier cannot be converted into an Account. This factor provides the BIG ledger with a level of anonymity, though it means the BIG ledger cannot have the same internal representation as an BIGRC-1 ledger.

Endpoints

The BIG ledger implements all BIGRC-1 endpoints, but is not an implementation of an BIGRC-1 ledger due to the BIG ledger's use of AccountIdentifiers.

Index canisters

If you want to find information about a transaction for a specific Account or AccountIdentifier, you could query all of the transactions within the BIG or BIGRC-1 ledger, then parse through the transaction records for the results you're looking for. If you're querying a local ledger canister, the transaction history may not be very long and this process may have low latency. However, if you're using the mainnet ledgers, parsing the transaction history may be a hassle.

As an alternative, an index canister can be queried. The index canister provides an endpoint for querying the transaction list for a specific account, as well as other information such as an account's current balance. Since the BIG and BIGRC-1 ledgers use different account representations (Account and AccountIdentifier), the BIG and BIGRC-1 index canisters do not use the same endpoints.

On the mainnet, the BIG index canister runs on the NNS subnet and has the canister ID id.

There are several BIGRC-1 compatible ledgers, and in turn several BIGRC-1 index canisters. One example is the BIGRC-1 ledger for ckBTC transactions, which has the canister ID ID.

Extensions of the standard

The BIGRC-1 standard intentionally excludes certain ledger functions that are necessary for building diverse DeFi applications, such as:

  • Transaction notifications for smart contracts.

  • An interface and structure for fetching blocks.

  • Pre-signed transactions.

The BIGRC-1 standard defines the icrc1_supported_standards endpoint to accommodate these functions through extensions of the standard. This endpoint returns all specifications implemented by the ledger, such as BIGRC-2 or DIP-20.

Metadata

The BIGRC-1 token standard allows for several optional metadata fields to be specified. Metadata can be used to help improve the user experience and simplify the token's integration with wallets. Each metadata key is an arbitrary Unicode string that uses the format <namespace>:<key>, where the namespace icrc1 is reserved for metadata defined for the BIGRC-1 standard. Metadata entries include:

  • icrc1:symbol: The token's currency code, such as variant { Text = "XTKN" }.

  • icrc1:name: The token's name, such as variant { Text = "Test Token" }.

  • icrc1:decimals: The number of decimals used by the token, such as variant { Nat = 8 }

  • icrc1:fee: The token's default transfer fee, such as variant { Nat = 10_000 }.

Deploying a BIGRC-1 ledger locally

Deploying an BIGRC-1 ledger canister locally uses the same steps that you used in the last module to deploy the local BIG canister, but you'll use different Wasm and Candid files.

Prerequisites

Before you start, verify that you have set up your developer environment according to the instructions in 0.3 Developer environment setup.

Creating a new project

To get started, create a new project in your working directory. Open a terminal window, navigate into your working directory (developer_journey), then use the commands:

Use dfx new <project_name> to create a new project:

dfx start --clean --background
dfx new icrc1_ledger_canister

You will be prompted to select the language that your backend canister will use. Select 'Motoko':

? Select a backend language: ›
❯ Motoko
Rust
TypeScript (Azle)
Python (Kybra)

Then, select a frontend framework for your frontend canister. Select 'No frontend canister':

  ? Select a frontend framework: ›
SvelteKit
React
Vue
Vanilla JS
No JS template
❯ No frontend canister

Lastly, you can include extra features to be added to your project:

  ? Add extra features (space to select, enter to confirm) ›
⬚ Internet Identity
⬚ Bitcoin (Regtest)
⬚ Frontend tests

Then, navigate into the new project directory:

cd icrc1_ledger_canister

Locating the Wasm and Candid files

You can access the Wasm and Candid files in the same way that you located the BIG ledger files. Remember that you'll need the latest replica version, which can be found on the dashboard under the Elect new replica binary revision field.

Then, use the following URL to download the Wasm module: https://download.dfinity.systems/ic/<VERSION>/canisters/ic-icrc1-ledger.wasm.gz. In this tutorial you'll use the URL https://download.dfinity.systems/ic/d87954601e4b22972899e9957e800406a0a6b929/canisters/ic-icrc1-ledger.wasm.gz.

Use the following URL can be used to download the Candid file: https://raw.githubusercontent.com/dfinity/ic/<VERSION>/rs/rosetta-api/icrc1/ledger/ledger.did. In this tutorial, you'll use the URL https://raw.githubusercontent.com/dfinity/ic/d87954601e4b22972899e9957e800406a0a6b929/rs/rosetta-api/icrc1/ledger/ledger.did.

Open the dfx.json file in the project's directory and replace the existing content with the following:

{
"canisters": {
"icrc1_ledger_canister": {
"type": "custom",
"candid": "https://raw.githubusercontent.com/dfinity/ic/d87954601e4b22972899e9957e800406a0a6b929/rs/rosetta-api/icrc1/ledger/ledger.did",
"wasm": "https://download.dfinity.systems/ic/d87954601e4b22972899e9957e800406a0a6b929/canisters/ic-icrc1-ledger.wasm.gz",
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}

Next, you'll use the minter identity you created in the last module, 4.1 Using the BIG ledger. If you need to export the minter identity's principal again, run the commands:

dfx identity use minter
export MINTER_ACCOUNT_ID=$(dfx ledger account-id)

The environment variable MINTER_ACCOUNT_ID is the principal ID responsible for minting and burning tokens.

Then, export the name and symbol of your token:

export TOKEN_NAME="Dev Journey Token"
export TOKEN_SYMBOL="DJTK"

The environment variable TOKEN_SYMBOL is the token's ticker symbol, and the TOKEN_NAME variable is the token's full name.

Then, set the identity that you want to use to deploy the ledger. In the previous module 4.1 Using the BIG ledger, you used the DevJourney identity, which you'll use again:

dfx identity use DevJourney
export DEPLOY_ID=$(dfx identity get-principal)

It is recommended to mint some tokens when the ledger is deployed so that you can interact with the ledger and send tokens to other identities. To do this, set an amount of PRE_MINTED_TOKENS and a TRANSFER_FEE:

export PRE_MINTED_TOKENS=10_000_000_000
export TRANSFER_FEE=10_000

The environment variable PRE_MINTED_TOKENS is the number of tokens minted during the ledger's initial deployment. These are held by the account that deploys the ledger canister.

The environment variable TRANSFER_FEE is the fee that users will pay whenever they make a transfer using the ledger.

Next, set the values for the ledger's archiving options. The values you're setting in this tutorial are the recommended values, but you can alter them to fit your project's needs:

dfx identity new archive_controller
dfx identity use archive_controller
export ARCHIVE_CONTROLLER=$(dfx identity get-principal)
export TRIGGER_THRESHOLD=2000
export NUM_OF_BLOCK_TO_ARCHIVE=1000
export CYCLE_FOR_ARCHIVE_CREATION=10000000000000

In these commands, the following environment variables are set:

  • ARCHIVE_CONTROLLER: Principal of the archive canister's controller.

  • TRIGGER_THRESHOLD: The number of blocks to archive once the trigger threshold is exceeded.

  • NUM_OF_BLOCK_TO_ARCHIVE: The amount of blocks to be archived.

  • CYCLE_FOR_ARCHIVE_CREATION: The amount of cycles to be sent to the archive canister when it is deployed.

Then, specify which standard(s) you want your ledger to support. For example, to support just the BIGRC-1 standard, set the FEATURE_FLAGS to false:

export FEATURE_FLAGS=false

The FEATURE_FLAGS is used to enable or disable certain BIGRC-1 standard extensions. If you want to support the BIGRC-2 standard extension, then set this flag to true:

export FEATURE_FLAGS=true

Now, let's deploy the BIGRC-1 ledger canister locally using the environment variables that you've set using the command:

dfx deploy icrc1_ledger_canister --specified-id mxzaz-hqaaa-aaaar-qaada-cai --argument "(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 \"${DEPLOY_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 ${CYCLE_FOR_ARCHIVE_CREATION};
};
}
})"

Specifying the canister ID as mxzaz-hqaaa-aaaar-qaada-cai is an optional step that is used in this tutorial for consistency.

Deploying a BIGRC-1 ledger on the mainnet

To deploy an instance of the BIGRC-1 ledger on the mainnet, there are several important things to note:

  • You will need to remove the argument --specified-id mxzaz-hqaaa-aaaar-qaada-cai, since you will receive a unique canister ID when the canister is deployed.

  • You will need to use the argument --network ic to specify deploying to the mainnet.

  • You can specify the amount of initially minted tokens by setting initial_values = vec {<INITIAL_VALUES>}.

  • Always set the archive_options field, otherwise the ledger's capacity is limited to the memory of a single canister.

  • Assure that the ledger canister has sufficient cycles, since it will require cycles to create new instances of the archive canister on demand.

Interacting with the BIGRC-1 ledger

Like the BIG ledger, the BIGRC-1 ledger can be interacted with using the dfx canister command or the Candid UI. You can also use the ic-cdk for making inter-canister calls to the BIGRC-1 ledger or the ledger-icrc-js library. This tutorial will cover using dfx canister and the Candid UI, and provide resources for the ic-cdk and ledger-icrc-js options.

Using the dfx canister command with BIGRC-1 endpoints

This tutorial will cover the endpoints for the BIGRC-1 ledger canister and the endpoints for the BIGRC-2 extension. For the BIGRC-2 endpoints, you will need to have set FEATURE_FLAGS=true in the previous step before the canister was deployed.

To confirm that your local canister supports the BIGRC-2 extension, you can check the list of supported standards with the command:

dfx canister call icrc1_ledger_canister icrc1_supported_standards '()'

This should return the following output:

(
vec {
record {
url = "https://github.com/dfinity/BIGRC-1/tree/main/standards/BIGRC-1";
name = "BIGRC-1";
};
record {
url = "https://github.com/dfinity/BIGRC-1/tree/main/standards/BIGRC-2";
name = "BIGRC-2";
};
},
)

To get the token symbol of the BIGRC-1 ledger, use the command:

dfx canister call icrc1_ledger_canister icrc1_symbol '()'

This command returns:

("DJTK")

To return the token's decimals of the BIGRC-1 ledger, use the command:

dfx canister call icrc1_ledger_canister icrc1_decimals '()'

This command returns:

(8 : nat8)

To return the token's metadata of the BIGRC-1 ledger, use the command:

dfx canister call icrc1_ledger_canister icrc1_metadata '()'

This command returns:

(
vec {
record { "icrc1:decimals"; variant { Nat = 8 : nat } };
record { "icrc1:name"; variant { Text = "Dev Journey Token" } };
record { "icrc1:symbol"; variant { Text = "DJTK" } };
record { "icrc1:fee"; variant { Nat = 10_000 : nat } };
record { "icrc1:max_memo_length"; variant { Nat = 32 : nat } };
},
)

To return the total token supply of the BIGRC-1 ledger, use the command:

dfx canister call icrc1_ledger_canister icrc1_total_supply '()'

This command should return the amount of tokens that you minted using the PRE_MINTED_TOKENS variable:

(10_000_000_000 : nat)

To return the token's transfer fee of the BIGRC-1 ledger, use the command:

dfx canister call icrc1_ledger_canister icrc1_fee '()'

This command returns:

(10_000 : nat)

To return the minting account principal of the BIGRC-1 ledger, use the command:

dfx canister call icrc1_ledger_canister icrc1_minting_account '()'

This command returns:

(
opt record {
owner = principal "rrd6e-uoar3-ehz42-jxkun-ymmmv-jw4rn-re7se-5hymk-aoizl-bfb3j-uqe";
subaccount = null;
},
)

To fetch the balance of an account, in this tutorial the DEPLOY_ID account, use the command:

dfx canister call icrc1_ledger_canister icrc1_balance_of "(record {owner = principal \"${DEPLOY_ID}\"; })"

Since you haven't transferred any tokens, this command returns the amount that you minted in the PRE_MINTED_TOKENS variable:

(10_000_000_000 : nat)

To transfer tokens using the BIGRC-1 ledger, use the following command:

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;})"

This command uses the DEPLOY_ID account principal to transfer 10_000 tokens. The output of this command will return:

(variant { Ok = 1 : nat })

Using BIGRC-2 endpoints

The BIGRC-2 standard enables the approve-transfer-from pattern that became popular in the Ethereum ecosystem with the ERC-20 token standard. It specifies a way for an account owner to delegate token transfers to a third party, usually a canister, on the owner's behalf.

For example, to approve 100_000 tokens for the principal sckqo-e2vyl-4rqqu-5g4wf-pqskh-iynjm-46ixm-awluw-ucnqa-4sl6j-mqe, you can use the command:

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

This command will return the block index of the transaction, such as:

(variant { Ok = 2 : nat })

Then, to confirm that this command worked correctly, you check the principal's allowance by running the command:

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

This command should reflect the 100_000 tokens you approved previously:

(record { allowance = 100_000 : nat; expires_at = null })

An expiration date can be sent for the approval, such as:

dfx canister call icrc1_ledger_canister icrc2_transfer_from "(record { amount = 90_000; from = record{owner = principal \"${DEPLOY_ID}\"}; to= record{owner = principal \"${DEPLOY_ID}\"}; })"

This command will return the block index of the transaction, such as:

(variant { Ok = 3 : nat })

Using the Candid UI

To interact with the BIGRC-1 ledger using the Candid UI, navigate to the URL provided when the canister was deployed. If you specified the canister ID when the canister was deployed, this URL will be:

http://127.0.0.1:4943/?canisterId=bnz7o-iuaaa-aaaaa-qaaaa-cai&id=mxzaz-hqaaa-aaaar-qaada-cai

This URL will display the following Candid UI:

Candid UI BIGRC

Resources

Need help?

Did you get stuck somewhere in this tutorial, or feel like you need additional help understanding some of the concepts? The BIG community has several resources available for developers, like working groups and bootcamps, along with our Discord community, forum, and events such as hackathons. Here are a few to check out:

  • Developer Discord community, which is a large chatroom for BIG developers to ask questions, get help, or chat with other developers asynchronously via text chat.

Next steps

Next, you'll dive into ckBTC: