Block Lookup
Description
Section titled “Description”This example demonstrates how to lookup block information using the IndexerClient lookupBlock() method.
Prerequisites
Section titled “Prerequisites”- LocalNet running (via
algokit localnet start)
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example indexer_client/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)})