Skip to content
Algorand Developer Portal

Block Lookup

← Back to Indexer Client

This example demonstrates how to lookup block information using the IndexerClient lookupBlock() method.

  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example indexer_client/14-block-lookup.ts

View source on GitHub

14-block-lookup.ts
/**
* Example: Block Lookup
*
* This example demonstrates how to lookup block information using
* the IndexerClient lookupBlock() method.
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { algo } from '@algorandfoundation/algokit-utils';
import {
createAlgorandClient,
createIndexerClient,
printError,
printHeader,
printInfo,
printStep,
printSuccess,
shortenAddress,
} from '../shared/utils.js';
/**
* Format a Uint8Array as a hex string
*/
function formatBytes(bytes: Uint8Array): string {
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
/**
* Format a Unix timestamp to a human-readable date
*/
function formatTimestamp(timestamp: number): string {
return new Date(timestamp * 1000).toISOString();
}
async function main() {
printHeader('Block Lookup Example');
// Create clients
const indexer = createIndexerClient();
const algorand = createAlgorandClient();
// =========================================================================
// Step 1: Get the current round from the indexer to find a recent block
// =========================================================================
printStep(1, 'Getting a recent block round from the indexer');
let recentRound: bigint;
try {
// Use health check to get the current round
const health = await indexer.healthCheck();
recentRound = BigInt(health.round);
printSuccess(`Current indexer round: ${recentRound}`);
printInfo('');
// Use a block that's a few rounds back to ensure it's fully indexed
if (recentRound > 5n) {
recentRound = recentRound - 3n;
printInfo(`Using block ${recentRound} (a few rounds back for stability)`);
}
} catch (error) {
printError(
`Failed to get current round: ${error instanceof Error ? error.message : String(error)}`,
);
printInfo('');
printInfo('Make sure LocalNet is running: algokit localnet start');
printInfo('If issues persist, try: algokit localnet reset');
return;
}
// =========================================================================
// Step 2: Lookup block with lookupBlock() to get full block details
// =========================================================================
printStep(2, 'Looking up block with lookupBlock(roundNumber)');
try {
const block = await indexer.lookupBlock(recentRound);
printSuccess(`Retrieved block ${block.round}`);
printInfo('');
// Display basic block fields
printInfo('Basic Block Fields:');
printInfo(` Round: ${block.round}`);
printInfo(` Timestamp: ${block.timestamp} (${formatTimestamp(block.timestamp)})`);
printInfo(` Genesis ID: ${block.genesisId}`);
printInfo(` Genesis Hash: ${formatBytes(block.genesisHash)}`);
printInfo(` Previous Block Hash: ${formatBytes(block.previousBlockHash)}`);
printInfo('');
// Display optional proposer info (may not be present on all networks)
if (block.proposer) {
printInfo('Proposer Information:');
printInfo(` Proposer: ${shortenAddress(block.proposer.toString())}`);
if (block.feesCollected !== undefined) {
printInfo(` Fees Collected: ${block.feesCollected} µALGO`);
}
if (block.bonus !== undefined) {
printInfo(` Bonus: ${block.bonus} µALGO`);
}
if (block.proposerPayout !== undefined) {
printInfo(` Proposer Payout: ${block.proposerPayout} µALGO`);
}
printInfo('');
}
// Display transaction counter
if (block.txnCounter !== undefined) {
printInfo(
`Transaction Counter: ${block.txnCounter} (total txns committed in ledger up to this block)`,
);
printInfo('');
}
} catch (error) {
printError(`lookupBlock failed: ${error instanceof Error ? error.message : String(error)}`);
}
// =========================================================================
// Step 3: Display block header info - seed, txnCommitments, participationUpdates
// =========================================================================
printStep(3, 'Displaying block header information');
try {
const block = await indexer.lookupBlock(recentRound);
printInfo('Seed and Transaction Commitments:');
printInfo(` Seed (Sortition): ${formatBytes(block.seed)}`);
printInfo(` Transactions Root: ${formatBytes(block.transactionsRoot)}`);
if (block.transactionsRootSha256) {
printInfo(` Txn Root SHA256: ${formatBytes(block.transactionsRootSha256)}`);
}
if (block.transactionsRootSha512) {
printInfo(` Txn Root SHA512: ${formatBytes(block.transactionsRootSha512)}`);
}
if (block.previousBlockHash512) {
printInfo(` Prev Block Hash 512: ${formatBytes(block.previousBlockHash512)}`);
}
printInfo('');
// Display participation updates
printInfo('Participation Updates:');
const updates = block.participationUpdates;
if (updates.absentParticipationAccounts && updates.absentParticipationAccounts.length > 0) {
printInfo(` Absent Accounts: ${updates.absentParticipationAccounts.length} account(s)`);
for (const account of updates.absentParticipationAccounts.slice(0, 3)) {
printInfo(` - ${shortenAddress(account.toString())}`);
}
if (updates.absentParticipationAccounts.length > 3) {
printInfo(` ... and ${updates.absentParticipationAccounts.length - 3} more`);
}
} else {
printInfo(' Absent Accounts: None');
}
if (updates.expiredParticipationAccounts && updates.expiredParticipationAccounts.length > 0) {
printInfo(
` Expired Accounts: ${updates.expiredParticipationAccounts.length} account(s)`,
);
for (const account of updates.expiredParticipationAccounts.slice(0, 3)) {
printInfo(` - ${shortenAddress(account.toString())}`);
}
if (updates.expiredParticipationAccounts.length > 3) {
printInfo(` ... and ${updates.expiredParticipationAccounts.length - 3} more`);
}
} else {
printInfo(' Expired Accounts: None');
}
printInfo('');
// Display rewards info
printInfo('Block Rewards:');
printInfo(` Fee Sink: ${shortenAddress(block.rewards.feeSink.toString())}`);
printInfo(` Rewards Pool: ${shortenAddress(block.rewards.rewardsPool.toString())}`);
printInfo(` Rewards Level: ${block.rewards.rewardsLevel}`);
printInfo(` Rewards Rate: ${block.rewards.rewardsRate}`);
printInfo(` Rewards Residue: ${block.rewards.rewardsResidue}`);
printInfo(` Rewards Calc Round: ${block.rewards.rewardsCalculationRound}`);
printInfo('');
// Display upgrade state
printInfo('Upgrade State:');
printInfo(` Current Protocol: ${block.upgradeState.currentProtocol}`);
if (block.upgradeState.nextProtocol) {
printInfo(` Next Protocol: ${block.upgradeState.nextProtocol}`);
printInfo(` Next Protocol Vote: ${block.upgradeState.nextProtocolVoteBefore}`);
printInfo(` Next Protocol Switch: ${block.upgradeState.nextProtocolSwitchOn}`);
printInfo(` Next Protocol Approvals: ${block.upgradeState.nextProtocolApprovals}`);
} else {
printInfo(' Next Protocol: None (no upgrade pending)');
}
// Display upgrade vote if present
if (block.upgradeVote) {
printInfo('');
printInfo('Upgrade Vote:');
if (block.upgradeVote.upgradePropose) {
printInfo(` Proposed Protocol: ${block.upgradeVote.upgradePropose}`);
}
if (block.upgradeVote.upgradeDelay !== undefined) {
printInfo(` Upgrade Delay: ${block.upgradeVote.upgradeDelay}`);
}
printInfo(` Upgrade Approve: ${block.upgradeVote.upgradeApprove ?? false}`);
}
printInfo('');
// Display state proof tracking if present
if (block.stateProofTracking && block.stateProofTracking.length > 0) {
printInfo('State Proof Tracking:');
for (const tracking of block.stateProofTracking) {
printInfo(` Type: ${tracking.type}`);
if (tracking.nextRound !== undefined) {
printInfo(` Next Round: ${tracking.nextRound}`);
}
if (tracking.onlineTotalWeight !== undefined) {
printInfo(` Online Weight: ${tracking.onlineTotalWeight}`);
}
if (tracking.votersCommitment) {
printInfo(` Voters Commitment: ${formatBytes(tracking.votersCommitment)}`);
}
}
printInfo('');
}
} catch (error) {
printError(
`Failed to display block header: ${error instanceof Error ? error.message : String(error)}`,
);
}
// =========================================================================
// Step 4: Show transactions included in the block if any
// =========================================================================
printStep(4, 'Showing transactions included in the block');
try {
const block = await indexer.lookupBlock(recentRound);
printInfo(`Block ${block.round} contains ${block.transactions.length} transaction(s)`);
printInfo('');
if (block.transactions.length > 0) {
printInfo('Transactions in this block:');
// Show up to 5 transactions for brevity
const txnsToShow = block.transactions.slice(0, 5);
for (let i = 0; i < txnsToShow.length; i++) {
const txn = txnsToShow[i];
printInfo(` [${i}] ID: ${txn.id}`);
printInfo(` Type: ${txn.txType}`);
printInfo(` Sender: ${shortenAddress(txn.sender.toString())}`);
printInfo(` Fee: ${txn.fee} µALGO`);
if (txn.paymentTransaction) {
printInfo(
` Receiver: ${shortenAddress(txn.paymentTransaction.receiver.toString())}`,
);
printInfo(` Amount: ${txn.paymentTransaction.amount} µALGO`);
}
if (txn.assetTransferTransaction) {
printInfo(` Asset ID: ${txn.assetTransferTransaction.assetId}`);
printInfo(
` Receiver: ${shortenAddress(txn.assetTransferTransaction.receiver.toString())}`,
);
printInfo(` Amount: ${txn.assetTransferTransaction.amount}`);
}
printInfo('');
}
if (block.transactions.length > 5) {
printInfo(` ... and ${block.transactions.length - 5} more transaction(s)`);
printInfo('');
}
} else {
printInfo('This block has no transactions (empty block).');
printInfo('Empty blocks are common on LocalNet when there is no activity.');
printInfo('');
}
} catch (error) {
printError(
`Failed to show transactions: ${error instanceof Error ? error.message : String(error)}`,
);
}
// =========================================================================
// Step 5: Create some transactions to have blocks with transactions
// =========================================================================
printStep(5, 'Creating transactions to demonstrate blocks with transactions');
let blockWithTxns: bigint | undefined;
try {
// Get a funded account
const dispenser = await algorand.account.kmd.getLocalNetDispenserAccount();
algorand.setSignerFromAccount(dispenser);
const dispenserAddress = dispenser.addr.toString();
printInfo(`Using dispenser: ${shortenAddress(dispenserAddress)}`);
// Create a few transactions
printInfo('Creating 3 payment transactions...');
const receiver = algorand.account.random();
for (let i = 0; i < 3; i++) {
await algorand.send.payment({
sender: dispenser.addr,
receiver: receiver.addr,
amount: algo(0.1),
note: `Block lookup example payment ${i + 1}`,
});
}
printSuccess('Created 3 transactions');
// Wait a moment for indexer to catch up
printInfo('Waiting for indexer to index the transactions...');
await new Promise(resolve => setTimeout(resolve, 2000));
// Get the current round which should contain our transactions
const health = await indexer.healthCheck();
blockWithTxns = BigInt(health.round);
printInfo(`Current round after transactions: ${blockWithTxns}`);
printInfo('');
// Look up a recent block that might contain our transactions
// Check a few recent blocks to find one with transactions
for (let r = blockWithTxns; r > blockWithTxns - 5n && r > 0n; r--) {
const block = await indexer.lookupBlock(r);
if (block.transactions.length > 0) {
blockWithTxns = r;
printSuccess(`Found block ${r} with ${block.transactions.length} transaction(s)`);
printInfo('');
// Show the transactions
printInfo('Transactions in this block:');
for (let i = 0; i < Math.min(block.transactions.length, 3); i++) {
const txn = block.transactions[i];
const txnId = txn.id ?? 'unknown';
printInfo(` [${i}] ${txnId.substring(0, 20)}... (${txn.txType})`);
}
if (block.transactions.length > 3) {
printInfo(` ... and ${block.transactions.length - 3} more`);
}
break;
}
}
} catch (error) {
printError(
`Failed to create transactions: ${error instanceof Error ? error.message : String(error)}`,
);
printInfo('This step requires LocalNet - continuing with other demonstrations...');
printInfo('');
}
// =========================================================================
// Step 6: Demonstrate headerOnly parameter to get only block header
// =========================================================================
printStep(6, 'Demonstrating headerOnly parameter');
try {
const roundToLookup = blockWithTxns ?? recentRound;
printInfo(`Looking up block ${roundToLookup} with headerOnly=false (default):`);
const fullBlock = await indexer.lookupBlock(roundToLookup);
printInfo(` Transactions included: ${fullBlock.transactions.length}`);
printInfo('');
printInfo(`Looking up block ${roundToLookup} with headerOnly=true:`);
const headerOnly = await indexer.lookupBlock(roundToLookup, { headerOnly: true });
printInfo(` Transactions included: ${headerOnly.transactions.length}`);
printInfo('');
if (fullBlock.transactions.length > 0 && headerOnly.transactions.length === 0) {
printSuccess('headerOnly=true correctly excludes transactions from the response');
} else if (fullBlock.transactions.length === 0) {
printInfo('This block has no transactions, so headerOnly has no visible effect');
printInfo(
'headerOnly=true is useful to reduce response size for blocks with many transactions',
);
}
printInfo('');
printInfo('headerOnly parameter:');
printInfo(' - false (default): Returns full block including all transactions');
printInfo(' - true: Returns only block header without transactions array');
printInfo(' - Use headerOnly=true when you only need block metadata for better performance');
} catch (error) {
printError(`headerOnly demo failed: ${error instanceof Error ? error.message : String(error)}`);
}
// =========================================================================
// Step 7: Handle the case where block is not found
// =========================================================================
printStep(7, 'Handling the case where block is not found');
try {
// Try to look up a block from the far future
const futureRound = 999999999999n;
printInfo(`Attempting to lookup block ${futureRound} (far future)...`);
await indexer.lookupBlock(futureRound);
// If we get here, the block was found (unexpected)
printInfo('Block was found (unexpected)');
} catch (error) {
printSuccess('Correctly caught error for non-existent block');
if (error instanceof Error) {
printInfo(`Error message: ${error.message}`);
}
printInfo('');
printInfo('Always handle the case where a block may not exist yet.');
printInfo('The indexer throws an error when the block round has not been reached.');
}
// =========================================================================
// Summary
// =========================================================================
printHeader('Summary');
printInfo('This example demonstrated:');
printInfo(' 1. Getting a recent block round from the indexer health check');
printInfo(' 2. lookupBlock(roundNumber) - Get full block details');
printInfo(' 3. Block header info: seed, transaction commitments, participation updates');
printInfo(' 4. Displaying transactions included in a block');
printInfo(' 5. Creating transactions to populate blocks');
printInfo(' 6. headerOnly parameter - Get block header without transactions');
printInfo(' 7. Handling the case where block is not found');
printInfo('');
printInfo('Key Block fields:');
printInfo(' - round: Block round number (bigint)');
printInfo(' - timestamp: Unix timestamp in seconds');
printInfo(' - genesisId: Genesis block identifier string');
printInfo(' - genesisHash: 32-byte hash of genesis block (Uint8Array)');
printInfo(' - previousBlockHash: 32-byte hash of previous block (Uint8Array)');
printInfo(' - seed: 32-byte sortition seed (Uint8Array)');
printInfo(' - transactionsRoot: Merkle root of transactions (Uint8Array)');
printInfo(' - transactions: Array of Transaction objects');
printInfo(' - participationUpdates: Participation account updates');
printInfo(' - rewards: Block rewards info (feeSink, rewardsPool, etc.)');
printInfo(' - upgradeState: Protocol upgrade state');
printInfo('');
printInfo('Optional Block fields:');
printInfo(' - proposer: Block proposer address (newer blocks)');
printInfo(' - feesCollected: Total fees collected in block');
printInfo(' - bonus: Bonus payout for block');
printInfo(' - proposerPayout: Amount paid to proposer');
printInfo(' - txnCounter: Cumulative transaction count');
printInfo(' - stateProofTracking: State proof tracking info');
printInfo(' - upgradeVote: Protocol upgrade vote');
printInfo('');
printInfo('lookupBlock() parameters:');
printInfo(' - roundNumber: Block round to lookup (required)');
printInfo(' - headerOnly: If true, exclude transactions from response (optional)');
}
main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});