How to use dfx to query the balance of an account identifier

Following the ledger.did specification, I run:

dfx canister --network ic call ryjl3-tyaaa-aaaaa-aaaba-cai account_balance '(record {account = e7a879ea563d273c46dd28c1584eaa132fad6f3e316615b3eb657d067f3519b5: blob})' --candid ./ledger_archive.did

dfx cannot understand the principal_id (below full error). Probably I have to encode it but I am not sure how to do it. Could you help me?


error: parser error
  ┌─ Candid argument:1:20
  │
1 │ (record {account = e7a879ea563d273c46dd28c1584eaa132fad6f3e316615b3eb657d067f3519b5: blob})
  │                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Unexpected token
  │
  = Expects one of "(", "blob", "bool", "decimal", "float", "func", "hex",
    "null", "opt", "principal", "record", "service", "sign", "text",
    "variant", "vec"

Error: Failed to create argument blob.
Caused by: Failed to create argument blob.
  Invalid argument: Invalid Candid values: Candid parser error: Unrecognized token `Id("e7a879ea563d273c46dd28c1584eaa132fad6f3e316615b3eb657d067f3519b5")` found at 19:83
Expected one of "(", "blob", "bool", "decimal", "float", "func", "hex", "null", "opt", "principal", "record", "service", "sign", "text", "variant" or "vec"```

What you are trying to query is the balance of an account identifier, not a principal.

You can find more info about the blob type here.

This command works

dfx canister --network ic call ryjl3-tyaaa-aaaaa-aaaba-cai account_balance '(record {account = blob "\e7\a8\79\ea\56\3d\27\3c\46\dd\28\c1\58\4e\aa\13\2f\ad\6f\3e\31\66\15\b3\eb\65\7d\06\7f\35\19\b5"})'

Note that if you don’t add the \ to specify the byte sequence, e7 for example will be interpreted as 65 37 – the respective byte encoding of the chars – instead of 231 – the hex encoding.

3 Likes

Hi @cryptoschindler ,

I have started to use your IC-PY python package. I would like to use it to query the ledger canister, but I don’t know how to encode the ‘account’ parameters when calling account_balance. Could you please indicate me below what I am doing wrong?

ledger_did = open("ledger.did").read()
ledger = Canister(agent=agent, canister_id="ryjl3-tyaaa-aaaaa-aaaba-cai", candid=ledger_did)
res = ledger.account_balance(
    {
        'account': b"\e7\a8\79\ea\56\3d\27\3c\46\dd\28\c1\58\4e\aa\13\2f\ad\6f\3e\31\66\15\b3\eb\65\7d\06\7f\35\19\b5"
    }
)

By the way, the following much simpler call to the ledger canister also fails:

ledger_archive_did = open("ledger_archive.did").read()
#qjdve-lqaaa-aaaaa-aaaeq-cai get_blocks '(record {start = 100000: nat64; length = 10:nat64})' 
ledger_archive = Canister(agent=agent, canister_id="qjdve-lqaaa-aaaaa-aaaeq-cai", candid=ledger_archive_did)
res = ledger_archive.get_blocks(
    {
        'start': 100000,
        'length': 2
    }
)

the last call generates the following error:

ValueError                                Traceback (most recent call last)
/home/ildefons/node1/clientic/notebooks/getting_transactions_from_archive_canister.ipynb Cell 32 in <cell line: 14>()
     12 #qjdve-lqaaa-aaaaa-aaaeq-cai get_blocks '(record {start = 100000: nat64; length = 10:nat64})' 
     13 ledger_archive = Canister(agent=agent, canister_id="qjdve-lqaaa-aaaaa-aaaeq-cai", candid=ledger_archive_did)
---> 14 res = ledger_archive.get_blocks(
     15     {
     16         'start': 100000,
     17         'length': 2
     18     }
     19 )
     20 res

File ~/anaconda3/envs/tr38/lib/python3.8/site-packages/ic/canister.py:57, in CaniterMethod.__call__(self, *args, **kwargs)
     55 effective_cansiter_id = args[0]['canister_id'] if self.canister_id == 'aaaaa-aa' and len(args) > 0 and type(args[0]) == dict and 'canister_id' in args[0] else self.canister_id
     56 if self.anno == 'query':
---> 57     res = self.agent.query_raw(
     58         self.canister_id,
     59         self.name, 
     60         encode(arguments),
     61         self.rets,
     62         effective_cansiter_id
     63         )
     64 else:
     65     res = self.agent.update_raw(
...
--> 590     raise ValueError("Not an option type")
    591 flag = safeReadByte(b)
    592 if flag == b'\x00':

ValueError: Not an option type

Hey there,
it’s a bit tricky indeed.

First, you can just import the Ledger canister interface from ic-py directly.

from ic.client import Client
from ic.agent import Agent
from ic.identity import Identity
from ic.common.ledger import Ledger

# Identity and Client are dependencies of Agent
iden = Identity()  # creates a random keypair
client = Client()  # creates a client to talk to the IC
# creates an agent, combination of client and identity
agent = Agent(iden, client)
# creates the ledger canister with the matching interface provided
ledger = Ledger(agent)


def convert_wallet_address_to_bytes_array(wallet_address):
    final_text = list(bytes.fromhex(wallet_address))
    return final_text


result = ledger.account_balance({  # type: ignore
    "account":
    convert_wallet_address_to_bytes_array(
        "a40c72396b11001b235b6fba8ec879defeafd7e53f2ff967c118bdb7b767d045")
})

e8s = result[0]['e8s']
icp = e8s / 100000000

print(icp)
1 Like

The call to the archive canister doesn’t seem to work due to a bug either in Candid or both the JS and Python agents when decoding the response :thinking:

I’m able to get the response decoded correctly with the following dfx command

❯ dfx canister --network ic call qjdve-lqaaa-aaaaa-aaaeq-cai get_blocks '(record { start = 10 : nat64; length = 1 : nat64 })' --candid cron/archive.did
dfx.json not found, using default.
(
  variant {
    Ok = record {
      blocks = vec {
        record {
          transaction = record {
            memo = 0 : nat64;
            operation = opt variant {
              Mint = record {
                to = blob ">\eb\e1e\bcqS!+z\bf \27\fd\f4d\ae\b3\c8\7f\c9K\ba\b5\a6\d2R\8c \12\e4\f1";
                amount = record { e8s = 2_124_797_959_183 : nat64 };
              }
            };
            created_at_time = record {
              timestamp_nanos = 1_620_328_630_192_468_691 : nat64;
            };
          };
          timestamp = record {
            timestamp_nanos = 1_620_328_630_192_468_691 : nat64;
          };
          parent_hash = opt blob "\d7\b9\baf~\b7\b0\d1}\81]*\ea\8f9w=\d4\f8\b6p\12\00\04?\89o2\94!\5cv";
        };
      };
    }
  },
)

The archive.did is the same used in the ic-py call where the decoding fails with ValueError: Not an option type. The same call using candid ui also fails with Not an option type.

The candid used is this one.

@chenyan @kpeacock @ccyanxyz

Note that dfx ledger balance exists for this purpose.

1 Like

Seems like the candid file is wrong?

@roman-kashitsyn

1 Like

Replied in the GitHub issue: ValueError: Not an option type · Issue #85 · rocklabs-io/ic-py · GitHub.

1 Like

Ok, so if the Candid file is correct and should be parsed correctly, then the agent.js either is buggy for the subtypes case or an old version is used for candid ui.

tagging both @chenyan and @kpeacock as they are (at least i think so) the owners of those. Thanks for your help @roman-kashitsyn

agent.js hasn’t implemented the full subtyping check yet. As a workaround, it’s best to use the exact did file in JS (the ones that removes the opt type).

1 Like

is there an easy way to convert from principal inline call

dfx canister --network ic call ryjl3-tyaaa-aaaaa-aaaba-cai account_balance '(record {account =$(dfx tools principal-to-account-id...

been trying to test out acting as a canister rather than a local identity
using --wallet but not sure if this is how to

want to transfer ICP tokens to and from a canister as events are triggered

There’s dfx ledger account-id with flags --of-principal, --of-canister, and --subaccount to create account ids

2 Likes

Excuse my naivety, but why does the syntax have to be so complex? :stuck_out_tongue:

This is the correct command:

dfx canister call ctiya-peaaa-aaaaa-qaaja-cai account_balance '(record {account = vec {173; 28; 45; 16; 24; 232; 136; 228; 151; 20; 36; 252; 35; 66; 231; 72; 105; 85; 145; 95; 29; 202; 66; 143; 36; 153; 161; 127; 71; 86; 122; 169} })'

Why can’t I just run:

dfx canister call ctiya-peaaa-aaaaa-qaaja-cai account_balance <account_id>

it’d be so much more intuitive! Or even better yet:

dfx canister call ctiya-peaaa-aaaaa-qaaja-cai account_balance <principal>

with an optional --subaccount flag.

I’m pretty new to this, but I have yet to understand the difference between those 2 formats:

  • ad1c2d1018e888e4971424fc2342e7486955915f1dca428f2499a17f47567aa9
  • vec {173; 28; 45; 16; 24; 232; 136; 228; 151; 20; 36; 252; 35; 66; 231; 72; 105; 85; 145; 95; 29; 202; 66; 143; 36; 153; 161; 127; 71; 86; 122; 169}

Is there a difference between account id and account identifier ?

The reason is Candid, the language/encoding we use to make the interface. It can do some automatic conversions (e.g. from a naked string to ("text") or choosing the right number type), but some things are more than what is possible (or reasonable) automatically. It is also a matter of tradeoff between what it does automagically and how likely it is to do something wrong when the user makes an input error.

The content is the same, but in different encoding. The first is hex, the second a candid vector

id is short for identifier, these two are the same

1 Like

Not unrelated, I can’t get this to work. What’s wrong with my syntax?

dfx canister call ctiya-peaaa-aaaaa-qaaja-cai transfer '(record { to = vec {173; 28; 45; 16; 24; 232; 136; 228; 151; 20; 36; 252; 35; 66; 231; 72; 105; 85; 145; 95; 29; 202; 66; 143; 36; 153; 161; 127; 71; 86; 122; 169} }; memo = 1:nat64; amount = record {e8s = 17_000 }; fee = record { e8s = 10_000 }; })' 
error: parser error
  ┌─ Candid argument:1:166
  │
1 │ (record { to = vec {173; 28; 45; 16; 24; 232; 136; 228; 151; 20; 36; 252; 35; 66; 231; 72; 105; 85; 145; 95; 29; 202; 66; 143; 36; 153; 161; 127; 71; 86; 122; 169} }; memo = 1:nat64; amount = record {e8s = 17_000 }; fee = record { e8s = 10_000 }; })
  │                                                                                                                                                                      ^ Unexpected token
  │
  = Expects one of ")", ","

Error: Failed to create argument blob.
Caused by: Failed to create argument blob.
  Invalid data: Unable to serialize Candid values: Invalid argument: Invalid Candid values: Candid parser error: Unrecognized token `Semi` found at 165:166
Expected one of ")" or ","

I don’t understand why it’s expecting a “)” or a “,”.
Cheers!

The error message is accurate. Candid tuples take commas to separate the values, not semicolons. It wants you to either close the (unary) tuple or to continue with a new value (i.e. put a comma and then the next value. So it should look like this:

(record { to = vec {...} }, memo = 1:nat64, amount = record {e8s = 17_000 }, fee = record { e8s = 10_000 })

Also, you have one too many } at the end

Thanks a lot for your help.
Looks like there’s still something wrong though :flushed:

dfx canister call ctiya-peaaa-aaaaa-qaaja-cai transfer '(record { to = vec {173; 28; 45; 16; 24; 232; 136; 228; 151; 20; 36; 252; 35; 66; 231; 72; 105; 85; 145; 95; 29; 202; 66; 143; 36; 153; 161; 127; 71; 86; 122; 169} }, memo = 1:nat64, amount = record {e8s = 17_000 }, fee = record { e8s = 1_000 } )' 
error: parser error
  ┌─ Candid argument:1:168
  │
1 │ (record { to = vec {173; 28; 45; 16; 24; 232; 136; 228; 151; 20; 36; 252; 35; 66; 231; 72; 105; 85; 145; 95; 29; 202; 66; 143; 36; 153; 161; 127; 71; 86; 122; 169} }, memo = 1:nat64, amount = record {e8s = 17_000 }, fee = record { e8s = 1_000 } )
  │                                                                                                                                                                        ^^^^ Unexpected token
  │
  = Expects one of "(", ")", "blob", "bool", "decimal", "float", "func",
    "hex", "null", "opt", "principal", "record", "service", "sign",
    "text", "variant", "vec"

Error: Failed to create argument blob.
Caused by: Failed to create argument blob.
  Invalid data: Unable to serialize Candid values: Invalid argument: Invalid Candid values: Candid parser error: Unrecognized token `Id("memo")` found at 167:171
Expected one of "(", ")", "blob", "bool", "decimal", "float", "func", "hex", "null", "opt", "principal", "record", "service", "sign", "text", "variant" or "vec"

For the record, my (incorrect) syntax with the semi colons comes from the documentation

Ah, I misunderstood what you were trying to do. The ledger’s transfer definition is this:

type TransferArgs = record {
  to : vec nat8;
  fee : Tokens;
  memo : nat64;
  from_subaccount : opt vec nat8;
  created_at_time : opt TimeStamp;
  amount : Tokens;
};
transfer : (TransferArgs) -> (Result_5);

And I gave you the syntax for

transfer: (record {to : vec nat8}, fee: Tokens, memo: nat64, ...)

In that case the only error was one too many closing braces here: 122; 169} }; memo = 1:nat64. If you change this to 122; 169}; memo = 1:nat64 then the record will continue until the end, and in a record ; is correct to separate the fields. Then it also makes sense why you had an extra closing brace at the end

Thanks, that worked :pray: