Skip to content

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)
})