Skip to content

Block Headers Search

← Back to Indexer Client

This example demonstrates how to search for block headers using the IndexerClient searchForBlockHeaders() method.

  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example indexer_client/15-block-headers.ts

View source on GitHub

15-block-headers.ts
/**
* Example: Block Headers Search
*
* This example demonstrates how to search for block headers using
* the IndexerClient searchForBlockHeaders() 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 Unix timestamp to a human-readable date
*/
function formatTimestamp(timestamp: number): string {
return new Date(timestamp * 1000).toISOString()
}
async function main() {
printHeader('Block Headers Search Example')
// Create clients
const indexer = createIndexerClient()
const algorand = createAlgorandClient()
// =========================================================================
// Step 1: Basic searchForBlockHeaders() call
// =========================================================================
printStep(1, 'Basic searchForBlockHeaders() call')
try {
// Search for recent block headers with a limit
const result = await indexer.searchForBlockHeaders({ limit: 5 })
printSuccess(`Retrieved ${result.blocks.length} block header(s)`)
printInfo(`Current round: ${result.currentRound}`)
printInfo('')
printInfo('Block headers (results are returned in ascending round order):')
for (const block of result.blocks) {
printInfo(` Round ${block.round}:`)
printInfo(` Timestamp: ${block.timestamp} (${formatTimestamp(block.timestamp)})`)
if (block.proposer) {
printInfo(` Proposer: ${shortenAddress(block.proposer.toString())}`)
} else {
printInfo(' Proposer: (not available)')
}
}
printInfo('')
printInfo('Note: Results are returned in ascending round order (oldest first)')
} catch (error) {
printError(`searchForBlockHeaders failed: ${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: Get the current round to define search ranges
// =========================================================================
printStep(2, 'Getting current round for search range examples')
let currentRound: bigint
try {
const health = await indexer.healthCheck()
currentRound = BigInt(health.round)
printSuccess(`Current indexer round: ${currentRound}`)
printInfo('')
} catch (error) {
printError(`Failed to get current round: ${error instanceof Error ? error.message : String(error)}`)
return
}
// =========================================================================
// Step 3: Filter by minRound and maxRound
// =========================================================================
printStep(3, 'Filtering by minRound and maxRound')
try {
// Search for blocks in a specific round range
const minRound = currentRound > 10n ? currentRound - 10n : 1n
const maxRound = currentRound - 5n > 0n ? currentRound - 5n : currentRound
printInfo(`Searching for blocks between round ${minRound} and ${maxRound}...`)
const result = await indexer.searchForBlockHeaders({
minRound,
maxRound,
limit: 10,
})
printSuccess(`Found ${result.blocks.length} block(s) in range`)
printInfo('')
if (result.blocks.length > 0) {
printInfo('Block rounds found:')
for (const block of result.blocks) {
printInfo(` Round ${block.round} - ${formatTimestamp(block.timestamp)}`)
}
}
printInfo('')
printInfo('minRound and maxRound parameters:')
printInfo(' - minRound: Only include blocks at or after this round')
printInfo(' - maxRound: Only include blocks at or before this round')
} catch (error) {
printError(`Round range search failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 4: Filter by beforeTime and afterTime
// =========================================================================
printStep(4, 'Filtering by beforeTime and afterTime')
try {
// Get a timestamp from a recent block to use as a reference
const recentBlocks = await indexer.searchForBlockHeaders({ limit: 1 })
if (recentBlocks.blocks.length > 0) {
const refTimestamp = recentBlocks.blocks[0].timestamp
// Search for blocks in a time window (before the reference time)
// Create a time 1 hour before the reference
const beforeDate = new Date(refTimestamp * 1000)
const afterDate = new Date((refTimestamp - 3600) * 1000) // 1 hour before
printInfo(`Searching for blocks between:`)
printInfo(` After: ${afterDate.toISOString()}`)
printInfo(` Before: ${beforeDate.toISOString()}`)
printInfo('')
const result = await indexer.searchForBlockHeaders({
afterTime: afterDate.toISOString(),
beforeTime: beforeDate.toISOString(),
limit: 5,
})
printSuccess(`Found ${result.blocks.length} block(s) in time range`)
printInfo('')
if (result.blocks.length > 0) {
printInfo('Blocks found:')
for (const block of result.blocks) {
printInfo(` Round ${block.round} - ${formatTimestamp(block.timestamp)}`)
}
}
}
printInfo('')
printInfo('Time filter parameters (RFC 3339 / ISO 8601 format):')
printInfo(' - afterTime: Only include blocks created after this timestamp')
printInfo(' - beforeTime: Only include blocks created before this timestamp')
printInfo(' - Example format: "2026-01-26T10:00:00.000Z"')
} catch (error) {
printError(`Time range search failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 5: Create transactions to have blocks with a known proposer
// =========================================================================
printStep(5, 'Creating transactions to populate blocks with proposer info')
let proposerAddress: string | 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 to generate activity
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 headers 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 recent block headers to find a proposer
const recentHeaders = await indexer.searchForBlockHeaders({ limit: 10 })
// Find a block with a proposer
for (const block of recentHeaders.blocks) {
if (block.proposer) {
proposerAddress = block.proposer.toString()
printSuccess(`Found block with proposer: ${shortenAddress(proposerAddress)}`)
printInfo(` Block round: ${block.round}`)
break
}
}
if (!proposerAddress) {
printInfo('No blocks with proposer info found (proposer may not be set on LocalNet)')
}
printInfo('')
} 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: Filter by proposers array to find blocks by specific accounts
// =========================================================================
printStep(6, 'Filtering by proposers array')
try {
if (proposerAddress) {
printInfo(`Searching for blocks proposed by: ${shortenAddress(proposerAddress)}`)
const result = await indexer.searchForBlockHeaders({
proposers: [proposerAddress],
limit: 5,
})
printSuccess(`Found ${result.blocks.length} block(s) proposed by this account`)
printInfo('')
if (result.blocks.length > 0) {
printInfo('Blocks found:')
for (const block of result.blocks) {
printInfo(` Round ${block.round} - Proposer: ${block.proposer ? shortenAddress(block.proposer.toString()) : '(unknown)'}`)
}
}
} else {
printInfo('No proposer address available to demonstrate filtering')
printInfo('On MainNet/TestNet, you would use a known validator address')
}
printInfo('')
printInfo('proposers parameter:')
printInfo(' - Array of addresses to filter by block proposer')
printInfo(' - Find all blocks proposed by specific validator accounts')
printInfo(' - Useful for analyzing validator participation')
} catch (error) {
printError(`Proposers filter search failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 7: Demonstrate additional filters (expired and absent)
// =========================================================================
printStep(7, 'Demonstrating expired and absent participation filters')
try {
printInfo('The searchForBlockHeaders() method also supports:')
printInfo('')
printInfo('expired parameter:')
printInfo(' - Array of addresses to filter by expired participation accounts')
printInfo(' - Finds blocks where specified accounts had their participation keys expire')
printInfo('')
printInfo('absent parameter:')
printInfo(' - Array of addresses to filter by absent participation accounts')
printInfo(' - Finds blocks where specified accounts were marked absent')
printInfo(' - Absent accounts are those that failed to participate in consensus')
printInfo('')
// Try searching with these filters (likely no results on LocalNet)
if (proposerAddress) {
const expiredResult = await indexer.searchForBlockHeaders({
expired: [proposerAddress],
limit: 5,
})
printInfo(`Blocks with expired participation for this address: ${expiredResult.blocks.length}`)
const absentResult = await indexer.searchForBlockHeaders({
absent: [proposerAddress],
limit: 5,
})
printInfo(`Blocks with absent status for this address: ${absentResult.blocks.length}`)
}
printInfo('')
printInfo('Note: On LocalNet, these filters typically return no results')
printInfo('as participation tracking is primarily relevant on MainNet/TestNet')
} catch (error) {
printError(`Participation filter search failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 8: Demonstrate pagination for fetching multiple block headers
// =========================================================================
printStep(8, 'Demonstrating pagination')
try {
printInfo('Fetching block headers with pagination (3 per page)...')
printInfo('')
let nextToken: string | undefined
let pageCount = 0
let totalBlocks = 0
const maxPages = 3
do {
const result = await indexer.searchForBlockHeaders({
limit: 3,
next: nextToken,
})
pageCount++
totalBlocks += result.blocks.length
printInfo(`Page ${pageCount}: Retrieved ${result.blocks.length} block(s)`)
for (const block of result.blocks) {
printInfo(` Round ${block.round} - ${formatTimestamp(block.timestamp)}`)
}
nextToken = result.nextToken
if (nextToken) {
printInfo(` Next token: ${nextToken.substring(0, 30)}...`)
} else {
printInfo(' No more pages')
}
printInfo('')
} while (nextToken && pageCount < maxPages)
printSuccess(`Retrieved ${totalBlocks} total block(s) across ${pageCount} page(s)`)
printInfo('')
printInfo('Pagination parameters:')
printInfo(' - limit: Maximum number of results per page')
printInfo(' - next: Token from previous response to get next page')
printInfo(' - Response includes nextToken if more results are available')
} catch (error) {
printError(`Pagination demo failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 9: Combining multiple filters
// =========================================================================
printStep(9, 'Combining multiple filters')
try {
// Combine round range with limit
const minRound = currentRound > 20n ? currentRound - 20n : 1n
const maxRound = currentRound
printInfo('Combining filters: round range + limit')
printInfo(` minRound: ${minRound}`)
printInfo(` maxRound: ${maxRound}`)
printInfo(' limit: 5')
printInfo('')
const result = await indexer.searchForBlockHeaders({
minRound,
maxRound,
limit: 5,
})
printSuccess(`Found ${result.blocks.length} block(s)`)
printInfo('')
if (result.blocks.length > 0) {
printInfo('Blocks found:')
for (const block of result.blocks) {
const proposerStr = block.proposer ? shortenAddress(block.proposer.toString()) : '(unknown)'
printInfo(` Round ${block.round} - Proposer: ${proposerStr}`)
}
}
printInfo('')
printInfo('Multiple filters can be combined:')
printInfo(' - Round range (minRound, maxRound) + time range (beforeTime, afterTime)')
printInfo(' - Proposers filter + round/time range')
printInfo(' - Any combination to narrow down results')
} catch (error) {
printError(`Combined filters search failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Summary
// =========================================================================
printHeader('Summary')
printInfo('This example demonstrated:')
printInfo(' 1. Basic searchForBlockHeaders() call')
printInfo(' 2. Getting current round for search ranges')
printInfo(' 3. Filtering by minRound and maxRound')
printInfo(' 4. Filtering by beforeTime and afterTime (RFC 3339 format)')
printInfo(' 5. Creating transactions to populate blocks')
printInfo(' 6. Filtering by proposers array')
printInfo(' 7. Additional filters: expired and absent participation')
printInfo(' 8. Pagination with limit and next parameters')
printInfo(' 9. Combining multiple filters')
printInfo('')
printInfo('searchForBlockHeaders() parameters:')
printInfo(' - limit: Maximum number of results to return')
printInfo(' - next: Pagination token from previous response')
printInfo(' - minRound: Only include blocks at or after this round')
printInfo(' - maxRound: Only include blocks at or before this round')
printInfo(' - beforeTime: Only include blocks created before this timestamp (RFC 3339)')
printInfo(' - afterTime: Only include blocks created after this timestamp (RFC 3339)')
printInfo(' - proposers: Array of addresses to filter by block proposer')
printInfo(' - expired: Array of addresses to filter by expired participation')
printInfo(' - absent: Array of addresses to filter by absent participation')
printInfo('')
printInfo('BlockHeadersResponse fields:')
printInfo(' - currentRound: Round at which results were computed (bigint)')
printInfo(' - nextToken: Pagination token for next page (optional string)')
printInfo(' - blocks: Array of Block objects')
printInfo('')
printInfo('Key Block header fields:')
printInfo(' - round: Block round number (bigint)')
printInfo(' - timestamp: Unix timestamp in seconds (number)')
printInfo(' - proposer: Block proposer address (optional, newer blocks)')
printInfo(' - genesisId: Genesis block identifier (string)')
printInfo(' - genesisHash: Hash of genesis block (Uint8Array)')
printInfo('')
printInfo('Note: Results are returned in ascending round order (oldest first)')
}
main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})