Skip to main content

ICP ledger local setup

Intermediate
Ledgers

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 approved AccountIdentifier.

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.

Why does the ledger use AccountIdentifiers 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:

  1. 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 ID ryjl3-tyaaa-aaaaa-aaaba-cai. This solution is fast and straightforward, but also heavyweight.

  2. 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

  • 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.

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
}

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 symbol LICP.

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
}

Learn more about init args.

  • 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

Learn more about interacting with the ICP ledger.