ICP ledger local setup
The Internet Computer Protocol (ICP) implements its utility token (ticker "ICP") using a specialized canister called the ICP ledger canister. It runs alongside other canisters the NNS subnet. The ICP ledger canister holds blocks, each containing a single transaction and maintains a traceable history of all transactions starting from its initial state.
These transactions represent either:
Minting ICP tokens.
Transferring ICP tokens from one account to another.
Burning ICP tokens , which eliminates them from existence.
Approving ICP tokens to be spent by some other
AccountIdentifier
, and/or transferring ICP from a previously approvedAccountIdentifier
.
Each block is associated with a unique index number. The entire chain is regularly signed by the network. The signature used to authenticate the chain can be verified by any third party using the root public key of the Internet Computer. Specific transactions can be retrieved by querying the ICP ledger directly.
If you are testing your project in a local development environment, you won't be able to test it using the ICP ledger running on the mainnet. Instead, you need to deploy a local instance of the ICP ledger canister. The history and balances of the mainnet ICP ledger will not be not available in a local instance.
Accounts
An account on the ICP ledger is represented and stored as an AccountIdentifier
, which is derived from a principal ID and subaccount identifier by computing a hash of the two.
You can think of principal IDs as a rough equivalent to the hash of a user’s public key for chains like Bitcoin or Ethereum. The corresponding secret key is used to sign messages and authenticate to the ledger canister.
An ICP ledger account belongs to and is controlled by the account owner, who must have a valid principal ID. Accounts cannot be owned by two or more principals (no "joint accounts"). However, since a principal can also refer to a canister, canisters with multiple controllers can be used as joint accounts, in which case the AccountIdentifier
is derived from the canister’s principal.
AccountIdentifier
s and not just principal IDs?Accounts allow a principal to control multiple accounts (subaccounts). While this could be abstracted away for a user by the wallet software, this functionality is not possible for canisters.
Deploying the ICP ledger locally
There are two ways of deploying an ICP ledger locally:
Use
dfx nns
to deploy the entire NNS locally. Since the ICP ledger is part of the NNS, this command will install an instance of the ICP ledger with canister IDryjl3-tyaaa-aaaaa-aaaba-cai
. This solution is fast and straightforward, but also heavyweight.Deploy the ICP ledger locally using the canister's Wasm and Candid files. This method gives you more control over the deployment and it is lightweight. For instance, you can define the
minting account
, you have control over the initialization arguments, and you have control over which Wasm version of the ICP ledger you want to interact with.
::caution The ICP ledger is not meant to be used for other token deployments.
If you want to deploy your own token, read the guide on setting up an ICRC-1 ledger. :::
Using dfx nns install
Step 1: Install the
nns
extension.
dfx extension install nns
Step 2: Use the command
dfx nns install
to deploy an instance of the entire NNS locally, including the ICP ledger canister.
This installation will include two accounts that are initialized with ICP for testing. One uses a secp256k1
key, which is convenient for command line usage, another uses an ed25519
key, which is more convenient in web applications.
To use the secp256k1
key, create a new file called ident-1.pem
that contains a secp256k1
key.
Step 3: Create an identity using the
secp256k1
key.
dfx identity import ident-1 ident-1.pem
Step 4: Run tests with ICP tokens.
dfx ledger balance
Using the most recent ICP ledger Wasm and Candid files
- Prerequisites
Step 1: Create a new
dfx
project or open an existing one.Step 2: Obtain the latest ledger Wasm and Candid file URLs.
Go to the releases overview and obtain the latest ICP ledger release version. Then, replace <RELEASE>
in the following URLs with that release version:
https://github.com/dfinity/ic/releases/download/<RELEASE>/ledger-canister_notify-method.wasm.gz
https://github.com/dfinity/ic/releases/download/<RELEASE>/ledger.did
The Wasm and Candid files can be referenced in your project via their public URLs shown above or they can be referenced via local files. If you want to download the Wasm and Candid files, use the following script. Please ensure that you have jq
installed, as the script relies on it.
curl -o download_latest_icp_ledger.sh "https://raw.githubusercontent.com/dfinity/ic/aba60ffbc46acfc8990bf4d5685c1360bd7026b9/rs/rosetta-api/scripts/download_latest_icp_ledger.sh"
chmod +x download_latest_icp_ledger.sh
./download_latest_icp_ledger.sh
Step 3: Create a new identity that will act as a minting account.
dfx identity new minter
dfx identity use minter
echo $(dfx ledger account-id)
Record the output of these commands as your minting account ID. Transfers from the minting account will create Mint
transactions. Transfers to the minting account will create Burn
transactions.
Step 4: Switch back to your primary developer identity and record its ledger account ID.
dfx identity use MyIdentity
echo $(dfx ledger account-id)
Record the output of these commands as your developer account ID.
Step 5: Configure the
dfx.json
file.
Open the dfx.json
file in your project's directory. Replace or edit the existing content with the following, updating the values of MINTER_ACCOUNT_ID
and DEVELOPER_ACCOUNT_ID
with the values obtained in the previous steps.
In an existing project you only need to add the icp_ledger_canister
canister to the canisters
section instead of replacing the entire content of dfx.json
.
dfx.json
does not support referring to values through environment variables. Values must be hardcoded in plain text.
- Using file URLs
- Using file paths
Replace <RELEASE>
with the latest replica release hash obtained from the dashboard.
{
"canisters": {
"icp_ledger_canister": {
"type": "custom",
"candid": "https://github.com/dfinity/ic/releases/download/<RELEASE>/ledger.did",
"wasm": "https://github.com/dfinity/ic/releases/download/<RELEASE>/ledger-canister_notify-method.wasm.gz",
"remote": {
"id": {
"ic": "ryjl3-tyaaa-aaaaa-aaaba-cai"
}
},
"init_arg" : "(variant { Init = record { minting_account = \"MINTER_ACCOUNT_ID\"; initial_values = vec { record { \"DEFAULT_ACCOUNT_ID\"; record { e8s = 10_000_000_000 : nat64; }; }; }; send_whitelist = vec {}; transfer_fee = opt record { e8s = 10_000 : nat64; }; token_symbol = opt \"LICP\"; token_name = opt \"Local ICP\"; } })"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}
{
"canisters": {
"icp_ledger_canister": {
"type": "custom",
"candid": icp_ledger.did,
"wasm" : icp_ledger.wasm.gz,
"remote": {
"id": {
"ic": "ryjl3-tyaaa-aaaaa-aaaba-cai"
}
},
"init_arg" : "(variant { Init = record { minting_account = \"MINTER_ACCOUNT_ID\"; initial_values = vec { record { \"DEFAULT_ACCOUNT_ID\"; record { e8s = 10_000_000_000 : nat64; }; }; }; send_whitelist = vec {}; transfer_fee = opt record { e8s = 10_000 : nat64; }; token_symbol = opt \"LICP\"; token_name = opt \"Local ICP\"; } })"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}
This dfx.json
file defines the init arguments for the ledger canister:
- Sets the minting account to the account identifier you saved in a previous step (
MINTER_ACCOUNT_ID
). - Mints 100 ICP tokens to the
DEFAULT_ACCOUNT_ID
(1 ICP is equal to 10^8 e8s). - Sets the transfer fee to 0.0001 ICP.
- Names the token
Local ICP
with ticker symbolLICP
.
You can also pass init args to the canister using the command line or by storing them in a file, then referencing that file in your dfx.json
configuration:
{
"canisters": {
"icp_ledger_canister": {
"type": "custom",
"candid": "https://github.com/dfinity/ic/releases/download/<RELEASE>/ledger.did",
"wasm": "https://github.com/dfinity/ic/releases/download/<RELEASE>/ledger-canister_notify-method.wasm.gz",
"remote": {
"id": {
"ic": "ryjl3-tyaaa-aaaaa-aaaba-cai"
}
},
"init_arg_file" : "init-args.did"
}
},
"defaults": {
"build": {
"args": "",
"packtool": ""
}
},
"output_env_file": ".env",
"version": 1
}
Step 6: Start a local development environment.
dfx start --clean --background
Step 7: Deploy the ledger canister locally.
You will deploy the local ICP ledger canister to the same canister ID as the mainnet ledger canister. This is to facilitate switching between local and mainnet deployments.
You cannot deploy this canister to the playground (using the flag dfx deploy --playground
) because it does not accept gzipped Wasm files.
dfx deploy --specified-id ryjl3-tyaaa-aaaaa-aaaba-cai icp_ledger_canister
Step 8: Interact with the canister.
You can interact with the canister by running CLI commands, such as:
dfx canister call ryjl3-tyaaa-aaaaa-aaaba-cai symbol '()'
Or, you can interact with it using the Candid UI by navigating to the Candid URL returned when the canister was deployed, such as:
http://127.0.0.1:4943/?canisterId=bnz7o-iuaaa-aaaaa-qaaaa-cai&id=ryjl3-tyaaa-aaaaa-aaaba-cai