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

1 Like