$ dfx ledger help
Ledger Accounts
The ledger is canister on the IC that is so important that the dfx tool has
dedicated commands for calling its methods:
On the IC, the ledger has the principal ryjl3-tyaaa-aaaaa-aaaba-cai, which
is a checksummed and encoded form of:
00000000000000020101
Being a canister, its principal must end with 0x01. The low value of the remainder of the ID indicates it was one of the first canisters deployed on the IC.
We can examine the ledger in the same way as any other canister:
$ dfx canister --network ic info ryjl3-tyaaa-aaaaa-aaaba-cai Controllers: r7inp-6aaaa-aaaaa-aaabq-cai Module hash: 0x61a312e4bf4f86899edabf6b3cedc504b2b0825d9b7956abea65582095692a9a
Local Ledgers
To avoid moving real tokens around, during development, we run a local ledger canister.
The dfx ledger commands ordinarily call canister with the true ledger’s ID.
Later versions of dfx support the --ledger-canister-id option to override
this.
If we forget to record the local ledger’s ID on deployment, we can look it up:
$ cat .dfx/local/canister_ids.json
For example:
"ledger": {
"local": "rrkah-fqaaa-aaaaa-aaaaq-cai"
},
We define a couple of aliases:
$ LEDGER="rrkah-fqaaa-aaaaa-aaaaq-cai" $ alias balance="dfx ledger balance --ledger-canister-id $LEDGER" $ alias transfer="dfx ledger transfer --ledger-canister-id $LEDGER"
Assuming we’ve followed the instructions for deploying the local ledger and
that we’ve given 1000 ICP to the user default:
$ balance default 1000.00000000 ICP
Account IDs
We can run dfx to find the default account number of a user. We demonstrate
on our zoo account:
$ dfx identity use zoo $ dfx ledger account-id e77eab8d01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21
We transfer 42 ICP from default to zoo:
$ dfx identity use default $ transfer e77eab8d01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21 --amount 42 --memo 0 $ balance e77eab8d01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21 42.00000000 ICP
Let’s compute this account ID manually. It is derived from a principal (which
is why the account-id command has no need for --ledger-canister-id) as
follows:
-
Concatenate "\naccount-id" with the principal (without a checksum).
-
Append 32 zero bytes.
-
Compute the SHA224 hash.
-
Prepend its CRC-32 checksum.
Recall the principal of the zoo key is
4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b02. Then its raw
unchecksummed default account ID is:
$ (printf "\naccount-id"; (echo 4ff2c79f70067d24bbba4a164737e0eddd62802ac603531fa0fc855b02 ; printf %064d 0) | xxd -r -p) | sha224sum 01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21
We abuse gzip to compute its CRC-32 checksum:
$ echo 01a5102d67bbc0b3ba120151f005debecdeadfb3c91f4b4fe804db21 | xxd -r -p | gzip | tail -c 8 | head -c 4 | xxd -p | tac -rs .. ; echo e77eab8d
Thankfully, this agrees with dfx ledger account-id.
Subaccounts
Above, in step 2, instead of 32 zero bytes, we can in fact use any arbitrary 32 bytes, known as the subaccount, resulting in an account we control. The zero subaccount is simply the default.
Subaccounts are related to salts in the context of password hashes. If we supply a subaccount along with the account derived from our principal and subaccount, then the ledger lets us operate on the account.
In effect, we have as many accounts as we want. We can use a fresh one for each new application, making it harder for independent transactions to stomp over each other.
Minting Cycles
Topping up cycles on a canister involves the ledger.
First, we transfer ICP to a certain subaccount of the cycle-minting canister: rkp4c-7iaaa-aaaaa-aaaca-cai. The subaccount is a representation of the target canister ID: the first byte is the length of the ID, followed by the raw principal, then as many zero bytes as needed to make 32 bytes. The memo field is set to 0x50555054 ("TPUP").
A successful transfer returns the block height at which the transaction
executed. We convert the ICP to cycles by calling notify_top_up which
takes an argument of type:
record
{ block_index : nat64
; canister_id : principal
}
Canister creation is similar, except the memo is 0x43524541 ("CREA") and
we call notify_create_canister, which takes an argument of type:
record
{ controller : principal
; block_index : nat64
}