How to deploy the ICP Ledger locally?

I have been trying to deploy the ICP Ledger locally; But it seems I might do something wrong.

Reproduce step:

    "ledger": {
      "type": "custom",
      "candid": "/Users/unknown/nns-ifaces/ledger.did",
      "wasm": "/Users/unknown/ic/rs/rosetta-api/ledger_canister/wasm/ledger-archive-node-canister.wasm",
      "build": ""

# previously executed: dfx canister create ledger
% dfx canister install --argument 'record {minting_account="15a32831331d752b5c4cff5b9351bea8015d90f2c3831c6f3fca9c5287a4dbff"; initial_values=vec {record{"15a32831331d752b5c4cff5b9351bea8015d90f2c3831c6f3fca9c5287a4dbff";record{e8s=1000000000;}}}; max_message_size_bytes=null;transaction_window=null;archive_options=null;send_whitelist=vec{};}' --mode=reinstall ledger
Reinstalling code for canister ledger, with canister_id rkp4c-7iaaa-aaaaa-aaaca-cai
The invocation to the wallet call forward method failed with the error: An error happened during the call: 5: Canister rkp4c-7iaaa-aaaaa-aaaca-cai trapped explicitly: Panicked at 'Deserialization Failed: "Deserialize error: bytes only takes principal or vec nat8"', /Users/lifted/Projects/dfinity/rs/rust_canisters/dfn_core/src/

I don’t think my argument are wrong (I actually used didc random to check how to write HashMap in Candid)

1 Like

Let me know if you get this working and what you did, so I can give it a jam when I get home. I had asked somewhere else how to launch a local ledger canister was told not possible so happy to see this!

What are you guys working on @flyq @dpdp ? Would be cool to collab where possible.

In dfx.json → ledger → wasm, you are using the archive node wasm, you should use the ledger canister wasm.


Link in this page on how to deploy locally is broken.

How to deploy locally?

1 Like

This is the current official guide to installing the ledger locally. I’ll go put in a redirect


with dfx 0.12.0 shouldn’t those instructions give a hint in the beginning regarding dfx nns install and dfx nns import?

We’re not quite sure what we want to do with dfx nns install/import since it’s a maintainability nightmare the way it is right now. But yes, it would make sense to add a note there once it’s a bit more clear what will happen to those commands


this doesn’t work for me, i get 404s for the did files and an Access Denied message for the actual Wasm

EDIT: no it wasn’t, but the shell script didn’t work for me.
apparently ledger-canister was renamed to icp-canister. will make a PR

I hope that we find a way to make these commands work. Being able to trivially deploy an ICP ledger is critical to efficiency for local replica tests. For lack of this feature, several of my past projects have either a) manually mocked an ICP ledger, b) run tests on mainnet. <3

1 Like

I don’t know if it is applicable, but if using dfx nns install with other canisters, I have to rebuild those other canisters each time after dfx nns install is called again or those canister’s won’t compile if importing one of those nns canisters.

This sounds sort-of expected. dfx nns install means that you did a dfx start --clean, right? In this case, dfx knows that since the state got wiped, canister IDs are not valid anymore and therefore the canisters should be recompiled. I guess (haven’t tested) if you did dfx canister install instead of deploy it would bypass that check if you don’t feel like recompiling.

I should have been more clear. If I have a non-nns canister in the project that imports one of the nns-canisters, after rerunning dfx stop dfx start --clean dfx nns install the first time I try to (create,build)/deploy the non-nns canister, it will always fail. If I run deploy again, it will succeed–in other words, I have to run deploy twice in a row in order for it to work.

Specifically I made a bare-bones project where the default backend Motoko canister imports the nns-ledger, the nns-ledger is added as a dependency to that backend Motoko canister in the project’s dfx.json (after dfx nns import is run the first time, since it only needs to be run once for the dfx.json to be configured for the nns canisters), to create a script that resets the project’s replica state (rerunning dfx nns install and dfx deploy backend_canister). It will compile and the backend_canister can successfully query the balance of an account identifier, but as I said dfx deploy backend_canister has to be run twice in a row since it fails to compile the first time around (I’m guessing because the nns-ledger is not deployed as are the non-nns canisters, so it is not linked correctly automatically without attempting to build it first?). Instead of dfx deploy the first time, dfx canister create <canister> dfx build <canister> also produces the same error even before dfx install <canister> would be called. I don’t have the error handy, but iirc it’s specifically related to the import of the nns-canister in the backend canister Motoko file (just the standard default import statement that does work the second time deploy is run).

That sounds like a bug. Would you have a repro handy? If I can avoid it, I’d like to not recreate the setup myself.

Hi, I have deployed the local ledger following this guide, however when I try to use it in my via this import:

import Ledger “canister:ledger”;

I get the error:
file /.dfx/local/lsp/rno2w-sqaaa-aaaaa-aaacq-cai.did uses Candid types without corresponding Motoko type Motoko

If you could help that would be great.

That’s not enough info for me to fix the problem - can you maybe reduce it down to a specific example?

So I followed the instructions for setting up a local ledger canister here:

If I run the check:

dfx canister call ledger account_balance ‘(record { account = ‘$(python3 -c ‘print(“vec{” + “;”.join([f"{b}:nat8" for b in bytes.fromhex("’$LEDGER_ACC’")]) + “}”)’)’ })’

I get the result:
(record { 5_035_232 = 100_000_000_000 : nat64 })

Which makes me think the canister is setup correctly.

All I do next is go over to my file, which you can see here:

and I get the error:
uses Candid types without corresponding Motoko type

Does any of this help?


I agree, looks fine to me too.

I don’t have time to dig deep into your code right now. I’ll ask the Motoko team if they have any advice to debug this further.

1 Like

I believe that the problem might be with the did file that motoko is importing (ledger.public.did). I can’t see it in the repo, but I guess it’s downloaded from here:

(If you can share your version of the ledger did, used by Motoko, that would be helpful.)

This line ic/ledger.did at master · dfinity/ic · GitHub of that did file includes
the service argument, used by dfx when installing the canister. The installed canister, that motoko wants to talk to, shouldn’t have the argument and the did file used needs to look something like this (note the commented out argument to the service):

type Tokens = record {
     e8s : nat64;

type Duration = record {
    secs: nat64;
    nanos: nat32;

// Number of nanoseconds from the UNIX epoch in UTC timezone.
type TimeStamp = record {
    timestamp_nanos: nat64;

type ArchiveOptions = record {
    trigger_threshold : nat64;
    num_blocks_to_archive : nat64;
    node_max_memory_size_bytes: opt nat64;
    max_message_size_bytes: opt nat64;
    controller_id: principal;
    cycles_for_archive_creation: opt nat64;

// Height of a ledger block.
type BlockIndex = nat64;

// A number associated with a transaction.
// Can be set by the caller in `send` call as a correlation identifier.
type Memo = nat64;

// Account identifier encoded as a 64-byte ASCII hex string.
type AccountIdentifier = text;

// Subaccount is an arbitrary 32-byte byte array.
type SubAccount = blob;

type Transfer = variant {
    Burn: record {
        from: AccountIdentifier;
        amount: Tokens;
    Mint: record {
        to: AccountIdentifier;
        amount: Tokens;
    Send: record {
        from: AccountIdentifier;
        to: AccountIdentifier;
        amount: Tokens;

type Transaction = record {
    transfer: Transfer;
    memo: Memo;
    created_at: BlockIndex;

// Arguments for the `send_dfx` call.
type SendArgs = record {
    memo: Memo;
    amount: Tokens;
    fee: Tokens;
    from_subaccount: opt SubAccount;
    to: AccountIdentifier;
    created_at_time: opt TimeStamp;

// Arguments for the `notify` call.
type NotifyCanisterArgs = record {
    // The of the block to send a notification about.
    block_height: BlockIndex;
    // Max fee, should be 10000 e8s.
    max_fee: Tokens;
    // Subaccount the payment came from.
    from_subaccount: opt SubAccount;
    // Canister that received the payment.
    to_canister: principal;
    // Subaccount that received the payment.
    to_subaccount: opt SubAccount;

type AccountBalanceArgs = record {
    account: AccountIdentifier;

type LedgerCanisterInitPayload = record {
    minting_account: AccountIdentifier;
    initial_values: vec record {AccountIdentifier; Tokens};
    max_message_size_bytes: opt nat64;
    transaction_window: opt Duration;
    archive_options: opt ArchiveOptions;
    send_whitelist: vec principal;
    transfer_fee: opt Tokens;
    token_symbol: opt text;
    token_name: opt text;

service: /* (LedgerCanisterInitPayload) -> */ { // <--- remove the argument type!
  send_dfx : (SendArgs) -> (BlockIndex);
  notify_dfx: (NotifyCanisterArgs) -> ();
  account_balance_dfx : (AccountBalanceArgs) -> (Tokens) query;
  notify_pb: () -> ();
1 Like