Questions about accountIdentifier and subaccount in ICPledger?

I want to transfer money to the hex code of accountIdentifier, but I don’t know how to parse out the subaccount?

My working steps are:

First: Generate a hexadecimal ICP address

  public query func generateHexIcpAaddress(identityToken : T.IdentityToken) : async Hex.Hex {
    let { addressId; token } = identityToken;
    let accountIdentifier = LedgerAccount.accountIdentifier(installer.caller, LedgerAccount.generateSubaccount(addressId));

Step 2: Deposit to this address:

  public func topUpICP(address : Hex.Hex, amount : Nat) : async ICPledger.TransferResult {
    switch (Hex.decode(address)) {
      case (#ok(accountIdentifier)) {
        let (isOk, accIdPart) = LedgerAccount.validateAccountIdentifier(accountIdentifier);
        if (not isOk) {
          Debug.trap("AccountIdentifier verification failed");
        let principal = Principal.fromBlob(Blob.fromArray(accountIdentifier));
        Debug.print(debug_show (principal));
        await ICPledger.icrc1_transfer({
          from_subaccount = null; // ICP balance already exists in the Canister id
          to = {
            owner = principal; // ??
            subaccount = // ??;
          amount = amount * 10 ** 8;
          fee = null;
          memo = null;
          created_at_time = null;
      case (#err(err)) {

The problem is in the second step, I don’t know how to correctly parse out to.owner and to.subaccount?
Hope to get support, thank you!

An ICP account hash is a one-way hash of account principal and a sub account. You can’t get the principal and sub account from this address.

In comparison the newer ICRC1 address format is not a hash but a textual format that describes the principal and sub account. In this format you can still get the principal and sub account from the address.

So for sending ICP to a non ICRC1 address (old hash format in hex) you can use the older transfer method instead of icrc1_transfer on the ledger canister.

I see, thanks a lot!
But at the same time a new problem arises,

The old transfer function is not included in the candid and wasm files in the official example.
Because I found that the common ICP addresses on the market are all in the traditional Hex encoding format.

Can you please provide a candid and wasm file address that is compatible with the old method?

Thanks again! ! !

1 Like

So what is the diff from Ledger canister and newer ICRC1 canister? Seems like ICRC1 is an extension of the Ledger canister.

  "canisters": {
  "ledger": {
    "type": "custom",
    "candid": "",
    "wasm": "",
    "remote": {
      "id": {
        "ic": "ryjl3-tyaaa-aaaaa-aaaba-cai"
    "replica": {

Is there a wasm url address in a similar configuration file?

Be aware if you use system subnet type replica, the port will be 8080 instead of the default 4943.

Thank you. We have clarified the structure of the IC project and found the relevant address.

"candid": "",
"wasm": ""

I can see the transfer method in the .did file you’ve linked, but locally deploying this canister with the new wasm doesn’t work for me. Deployed the ledger locally but still getting:

DestinationInvalid, "IC0302: Canister has no update method 'transfer'"

Using the above “candid” and “wasm” files here, transfer works normally

I deleted my .dfx folder but now I can’t deploy this. At first it complains record field send_whitelist not found. After adding this in I get cannot be of type variant { Upgrade : opt UpgradeArgs; Init : InitArgs }

What command did you use to deploy this version locally?

Figured it out, this version works with:

dfx deploy ledger --argument '(variant {Init = record {minting_account = "${MINTER_ACC}"; initial_values = vec { record { "${DEPLOY_ACC}"; record { e8s=100_000_000_000 } }; }; send_whitelist = vec {}; archive_options = opt record { trigger_threshold = 2000; num_blocks_to_archive = 1000; controller_id = principal "'${DEPLOY_PRINCIPAL}'" }}})'