Query_blocks <-- ICP ledger Candid 🙌

query_blocks for the ICP ledger canister works. It even gives you an index for archive canisters if the record is too old.

Looks like this was added to the repo March 3rd. No idea when it went live on mainnet, but it is there now.

DFINITY folks, you’ve got to shout this kind of stuff from the rooftops. This feature drastically reduces complexity and basically enables all kinds of new functionality. THANK YOU!!!

If I missed the announcement for this please point me to it, I don’t want to miss stuff like this again.

To query the main ledger(last couple thousand blocks or so, if not in range you get the index:

dfx canister --network ic call ryjl3-tyaaa-aaaaa-aaaba-cai query_blocks '(record {start = 1: nat64; length = 2:nat64})'

To query the index:

dfx canister --network ic call qjdve-lqaaa-aaaaa-aaaeq-cai get_blocks '(record {start = 1: nat64; length = 2:nat64})'

I’m using the get_blocks() method and everything functions without any errors, but I’m only getting empty arrays back, no blocks are being returned. what am I missing? Below is the relevant code.

how I instantiate the ledger index canister:

private let ledgerIndex : Ledger.InterfaceIndex = actor("qjdve-lqaaa-aaaaa-aaaeq-cai");

Where I call the function:

let tipOfChainIndex = await tipOfChainDetails();
let startIndex : Nat64 = tipOfChainIndex.0 - 1000;
let queryLength : Nat64 = 999;
let queryResult = await ledgerIndex.get_blocks({
    start = startIndex;
    length = queryLength;

On the main node it is query_blocks. On the archive nodes it is get_blocks. Make sure you have your did file right, AccountIdentfiers are blobs in these structures.

1 Like

Are you able to answer any of the following:

how far back on the blockchain we can query?

What’s the maximum range?

How does one go about retrieving the transaction history of a particle principal?

Up to 2000 blocks on the main node, all the way back on the archive.

The maximum size of a response is 2MB, sonic it bigger than that you’ll have to chunk.

Scan the chain and index it so you don’t have to donut a second time. I’d love for dfinity to offer some meta data Canisters for stuff like this. I think the Rosetta server lets you do those queries, but your canister can’t get to them.


When I try to use the web Candid UI to interact with the ledger archive canister, I get an error that I can’t resolve.

If I enter for example 1 at start and 2 at length (like you did in the example above), I get the response Not an option type.

What am I missing here?

I’m not sure where this candid came from, but in the latest version the to and from is a blob an and not a [Nat8]

Try this one: https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.ic0.app/?id=ryjl3-tyaaa-aaaaa-aaaba-cai

Thank you. That’s the ledger canister, it doesn’t store all the blocks but only the latest ones. The one that I was referring to is the archive which stores all ledger transactions. Did you try the candid UI and get the same error? I’m not sure what I’m doing wrong when calling the candid directly with the candid UI.

In any case, I get the same error when calling the query_blocks method on the ledger canister. It tells me the error Not an option type.

Does dfx work? ………………

Yes, dfx works. That’s why I’m a bit lost about what may be wrong here.

I’m trying query the transaction history via an httpAgent in typescript. See a small example below

import {Actor, HttpAgent} from '@dfinity/agent';

const {idlFactory: ledgerIDL} = require('../lib/canisters/ledger.did.js');
const {idlFactory: archiveIDL} = require('../lib/canisters/ledger-archive.did.js');

const agent = new HttpAgent({host: 'https://ic0.app'});
const ledger = Actor.createActor(ledgerIDL, {
  canisterId: 'ryjl3-tyaaa-aaaaa-aaaba-cai',
const ledgerArchive = Actor.createActor(archiveIDL, {
  canisterId: 'qjdve-lqaaa-aaaaa-aaaeq-cai',

const queryBlocks = async (start: bigint, length: bigint) => {
  const GetBlocksArgs = {
    start: start,
    length: length,
  const res: any = await ledger.query_blocks(GetBlocksArgs);

const queryArchiveBlocks = async (start: bigint, length: bigint) => {
  const GetBlocksArgs = {
    start: start,
    length: length,
  const res: any = await ledgerArchive.get_blocks(GetBlocksArgs);

queryBlocks(20000n, 2n); // returns an object with no blocks
queryArchiveBlocks(20000n, 2n); // returns the error "Not an option type"

The candid of the ledger and archive canisters are taken from here

Do you see any issue with the code?

Is it possible that the candid of the ledger canister on canlista and icscan are not correct? Does anyone know the latest version?

I got mine from the latest version of the ic repo. GitHub.com/dfinity/ic/rs/Rosetta-api/somewhereinthere.

Hello All … found the candid on the ledger canister while trying to verify a block to/from/amount for payment verification … found @quint ’ s comments and his example canister in github and while I was learning what he did I noticed the one you found (query_blocks) and was off the races … now I am coming back and trying to add a link to the UI to the Internet Computer Dashboard … i.e.:


that’s a random transaction, but if I have the block can anyone confirm how to get the “Hash” to build this URL … is the “Hash” the “parent_hash” in the “Block” type?:

type Block = record {
    parent_hash : opt blob;
    transaction : Transaction;
    timestamp : TimeStamp;

any confirmation that is correct and/or example converting the blob into hash would be wicked. … or if I am missing something let me know as well, might be an obvious thing …

Running into the same issue

I can do:

$ dfx canister --network ic call "qjdve-lqaaa-aaaaa-aaaeq-cai" get_blocks '(record {start = 4 : nat64; length = 20 : nat64} )'

But I can’t do:

const ARCHIVE_CANISTER_ID = 'qjdve-lqaaa-aaaaa-aaaeq-cai';

const archiveCanister: ActorSubclass<_SERVICE> = Actor.createActor(idlFactory, {
    agent: new HttpAgent({ host: "https://ic0.app" }),
    canisterId: Principal.fromText(ARCHIVE_CANISTER_ID),
const result = await archiveCanister.get_blocks({start: 4n, length: 20n});
Error: Not an option type
 ❯ OptClass.decodeValue node_modules/@dfinity/candid/src/idl.ts:920:13
 ❯ RecordClass.decodeValue node_modules/@dfinity/candid/src/idl.ts:1038:35
 ❯ RecordClass.decodeValue node_modules/@dfinity/candid/src/idl.ts:1038:35
 ❯ VecClass.decodeValue node_modules/@dfinity/candid/src/idl.ts:857:28
 ❯ RecordClass.decodeValue node_modules/@dfinity/candid/src/idl.ts:1038:35
 ❯ VariantClass.decodeValue node_modules/@dfinity/candid/src/idl.ts:1230:34
 ❯ node_modules/@dfinity/candid/src/idl.ts:1805:14
 ❯ Object.decode node_modules/@dfinity/candid/src/idl.ts:1804:27
 ❯ decodeReturnValue node_modules/@dfinity/agent/src/actor.ts:292:28

My interface file was downloaded from Canlista, and the type for both start and length is set to bigint.

It seems to be broken on the candid interface for the archive canister as well so my guess is that it’s a bug in the typescript implementation on the decoding result side of things.

Strangely, it seems to be working on the candid interface for the ledger canister with the sister function query_blocks

EDIT: seems it only works in typescript when length is set to 0n. Which is useless.

Here’s kind of a dirty workaround if you want to stick to typescript and you’re using node

const getBlocksWorkaround = (start: bigint, length: bigint) => {
    const result: Buffer = execSync(
        `dfx canister --network ic call "qjdve-lqaaa-aaaaa-aaaeq-cai" ` +
        `get_blocks '(record {start = 4 : nat64; length = 20 : nat64} )' ` +
        `--candid ${process.cwd()}/src/nns_interfaces/archive/archive.did`
    return result.toString();

Unfortunately it looks like you’ll have to parse or serialize this into JSON yourself, unless there’s some feature of DFX I’m unaware of.

icx looks like it could be useful, but i think it only serializes messages that are meant to be sent.

And if you want to use https://github.com/dfinity/idl2json:

const getBlocksWorkaround = (start: bigint, length: bigint) => {
    const path_to_idl2json_binary = '/Users/asdfpath/debug/idl2json';
    const cmd = `dfx canister --network ic call "${ARCHIVE_CANISTER_ID}" ` +
        `get_blocks '(record {start = ${start} : nat64; length = ${length} : nat64} )' ` +
        `--candid ${process.cwd()}/src/nns_interfaces/archive/archive.did` +
        `| ${path_to_idl2json_binary}`;
    const result: Buffer = execSync(cmd);
    const parsed_result = JSON.parse(result.toString());
    return parsed_result.Ok;

console.log(getBlocksWorkaround(4n, 20n));
console.log(getBlocksWorkaround(4n, 20n).blocks[0]);

Seems as if they ran into the same exact issue in this thread