Skip to content

Application Boxes Lookup

← Back to Indexer Client

This example demonstrates how to query application boxes using the IndexerClient searchForApplicationBoxes() and lookupApplicationBoxByIdAndName() methods.

  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example indexer_client/13-application-boxes.ts

View source on GitHub

13-application-boxes.ts
/**
* Example: Application Boxes Lookup
*
* This example demonstrates how to query application boxes using
* the IndexerClient searchForApplicationBoxes() and lookupApplicationBoxByIdAndName() methods.
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { algo, getApplicationAddress } from '@algorandfoundation/algokit-utils'
import {
createAlgodClient,
createAlgorandClient,
createIndexerClient,
loadTealSource,
printError,
printHeader,
printInfo,
printStep,
printSuccess,
shortenAddress,
} from '../shared/utils.js'
/**
* Decode box name bytes to a displayable string
* Shows as a UTF-8 string if printable, otherwise as hex
*/
function decodeBoxName(nameBytes: Uint8Array): string {
try {
const decoded = new TextDecoder().decode(nameBytes)
// Check if it's printable ASCII/UTF-8
if (/^[\x20-\x7E\s]+$/.test(decoded)) {
return `"${decoded}"`
}
} catch {
// Fall through to hex display
}
// Display as hex for binary data
const hex = Array.from(nameBytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
return `0x${hex} (${nameBytes.length} bytes)`
}
/**
* Decode box value bytes to a displayable string
* Shows as a UTF-8 string if printable, otherwise as hex
*/
function decodeBoxValue(valueBytes: Uint8Array): string {
try {
const decoded = new TextDecoder().decode(valueBytes)
// Check if it's printable ASCII/UTF-8
if (/^[\x20-\x7E\s]+$/.test(decoded)) {
return `"${decoded}"`
}
} catch {
// Fall through to hex display
}
// Display as hex for binary data
const hex = Array.from(valueBytes)
.map((b) => b.toString(16).padStart(2, '0'))
.join('')
return `0x${hex} (${valueBytes.length} bytes)`
}
async function main() {
printHeader('Application Boxes Lookup Example')
// Create clients
const indexer = createIndexerClient()
const algorand = createAlgorandClient()
const algod = createAlgodClient()
// =========================================================================
// Step 1: Get a funded account from LocalNet
// =========================================================================
printStep(1, 'Getting a funded account from LocalNet')
let creatorAddress: string
let creatorAccount: Awaited<ReturnType<typeof algorand.account.kmd.getLocalNetDispenserAccount>>
try {
creatorAccount = await algorand.account.kmd.getLocalNetDispenserAccount()
algorand.setSignerFromAccount(creatorAccount)
creatorAddress = creatorAccount.addr.toString()
printSuccess(`Using dispenser account: ${shortenAddress(creatorAddress)}`)
} catch (error) {
printError(`Failed to get dispenser account: ${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: Deploy an application that uses box storage
// =========================================================================
printStep(2, 'Deploying an application that uses box storage')
let appId: bigint
try {
// Load approval program from shared artifacts that supports box operations:
// - On create: does nothing (just succeeds)
// - On call with arg "create_box": creates a box with name from arg[1] and value from arg[2]
// - On call with arg "delete_box": deletes a box with name from arg[1]
const approvalSource = loadTealSource('approval-box-ops.teal')
// Load clear state program from shared artifacts
const clearSource = loadTealSource('clear-state-approve.teal')
// Compile TEAL programs
printInfo('Compiling TEAL programs...')
const approvalResult = await algod.tealCompile(approvalSource)
const approvalProgram = new Uint8Array(Buffer.from(approvalResult.result, 'base64'))
const clearResult = await algod.tealCompile(clearSource)
const clearStateProgram = new Uint8Array(Buffer.from(clearResult.result, 'base64'))
printInfo(`Approval program: ${approvalProgram.length} bytes`)
printInfo(`Clear state program: ${clearStateProgram.length} bytes`)
printInfo('')
// Create application using AlgorandClient's high-level API
printInfo('Creating application...')
const result = await algorand.send.appCreate({
sender: creatorAccount.addr,
approvalProgram,
clearStateProgram,
schema: {
globalInts: 0,
globalByteSlices: 0,
localInts: 0,
localByteSlices: 0,
},
})
appId = result.appId
printSuccess(`Created application with ID: ${appId}`)
// Fund the application account for box storage MBR
const appAddress = getApplicationAddress(appId)
printInfo(`Funding application account: ${shortenAddress(appAddress.toString())}`)
await algorand.send.payment({
sender: creatorAccount.addr,
receiver: appAddress.toString(),
amount: algo(1), // 1 ALGO for box storage
})
printSuccess('Funded application account with 1 ALGO for box storage')
printInfo('')
} catch (error) {
printError(`Failed to create application: ${error instanceof Error ? error.message : String(error)}`)
printInfo('')
printInfo('If LocalNet errors occur, try: algokit localnet reset')
return
}
// =========================================================================
// Step 3: Handle case where application has no boxes
// =========================================================================
printStep(3, 'Handling case where application has no boxes')
try {
// searchForApplicationBoxes() returns an empty array when no boxes exist
const emptyResult = await indexer.searchForApplicationBoxes(appId)
printInfo(`Application ID: ${emptyResult.applicationId}`)
printInfo(`Number of boxes: ${emptyResult.boxes.length}`)
if (emptyResult.boxes.length === 0) {
printSuccess('Correctly returned empty boxes array for new application')
printInfo('Applications start with no boxes - boxes are created via app calls')
}
} catch (error) {
printError(`searchForApplicationBoxes failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 4: Create several boxes with different names and values
// =========================================================================
printStep(4, 'Creating several boxes with different names and values')
// Box data to create
const boxData = [
{ name: 'user_count', value: '42' },
{ name: 'settings', value: '{"theme":"dark","lang":"en"}' },
{ name: 'metadata', value: 'v1.0.0-production' },
{ name: 'box_alpha', value: 'First box in alphabetical order' },
{ name: 'box_beta', value: 'Second box in alphabetical order' },
{ name: 'box_gamma', value: 'Third box in alphabetical order' },
]
try {
for (const box of boxData) {
const boxNameBytes = new TextEncoder().encode(box.name)
const boxValueBytes = new TextEncoder().encode(box.value)
await algorand.send.appCall({
sender: creatorAccount.addr,
appId: appId,
args: [new TextEncoder().encode('create_box'), boxNameBytes, boxValueBytes],
boxReferences: [{ appId: appId, name: boxNameBytes }],
})
printInfo(`Created box "${box.name}" with value: "${box.value}"`)
}
printSuccess(`Created ${boxData.length} boxes for demonstration`)
printInfo('')
} catch (error) {
printError(`Failed to create boxes: ${error instanceof Error ? error.message : String(error)}`)
return
}
// =========================================================================
// Step 5: Search for application boxes with searchForApplicationBoxes()
// =========================================================================
printStep(5, 'Searching for application boxes with searchForApplicationBoxes()')
// Wait for the indexer to catch up with the algod transactions
printInfo('Waiting for indexer to sync...')
await new Promise((resolve) => setTimeout(resolve, 3000))
try {
// searchForApplicationBoxes() returns all box names for an application
const boxesResult = await indexer.searchForApplicationBoxes(appId)
printSuccess(`Retrieved boxes for application ${boxesResult.applicationId}`)
printInfo(`Total boxes found: ${boxesResult.boxes.length}`)
printInfo('')
if (boxesResult.boxes.length > 0) {
printInfo('Box names (sorted lexicographically):')
for (let i = 0; i < boxesResult.boxes.length; i++) {
const boxDescriptor = boxesResult.boxes[i]
const nameDisplay = decodeBoxName(boxDescriptor.name)
printInfo(` [${i}] ${nameDisplay}`)
}
printInfo('')
printInfo('Note: searchForApplicationBoxes returns only box names (BoxDescriptor[]),')
printInfo('not the values. Use lookupApplicationBoxByIdAndName to get values.')
}
if (boxesResult.nextToken) {
printInfo(`More results available (nextToken: ${boxesResult.nextToken.substring(0, 20)}...)`)
}
} catch (error) {
printError(`searchForApplicationBoxes failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 6: Lookup specific box by name with lookupApplicationBoxByIdAndName()
// =========================================================================
printStep(6, 'Looking up specific box values with lookupApplicationBoxByIdAndName()')
try {
// lookupApplicationBoxByIdAndName() requires the box name as Uint8Array
const boxName = new TextEncoder().encode('settings')
printInfo('Looking up box with name "settings"...')
printInfo(`Box name as Uint8Array: [${Array.from(boxName).join(', ')}]`)
printInfo('')
const boxResult = await indexer.lookupApplicationBoxByIdAndName(appId, boxName)
printSuccess('Retrieved box details:')
printInfo(` Round: ${boxResult.round}`)
printInfo(` Name: ${decodeBoxName(boxResult.name)}`)
printInfo(` Value: ${decodeBoxValue(boxResult.value)}`)
printInfo(` Value size: ${boxResult.value.length} bytes`)
printInfo('')
// Try parsing as JSON since we know this box contains JSON
const valueStr = new TextDecoder().decode(boxResult.value)
if (valueStr.startsWith('{')) {
try {
const parsed = JSON.parse(valueStr)
printInfo('Parsed as JSON:')
for (const [key, val] of Object.entries(parsed)) {
printInfo(` ${key}: ${JSON.stringify(val)}`)
}
} catch {
// Not valid JSON
}
}
} catch (error) {
printError(`lookupApplicationBoxByIdAndName failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 7: Show how to properly encode box names using Uint8Array
// =========================================================================
printStep(7, 'Demonstrating box name encoding with Uint8Array')
try {
printInfo('Box names are Uint8Array (raw bytes). Common encoding methods:')
printInfo('')
// Example 1: String to Uint8Array using TextEncoder
const stringName = 'user_count'
const stringNameBytes = new TextEncoder().encode(stringName)
printInfo(`1. String "${stringName}" to Uint8Array:`)
printInfo(` new TextEncoder().encode("${stringName}")`)
printInfo(` Result: [${Array.from(stringNameBytes).join(', ')}]`)
printInfo('')
// Lookup this box
const box1 = await indexer.lookupApplicationBoxByIdAndName(appId, stringNameBytes)
printInfo(` Box value: ${decodeBoxValue(box1.value)}`)
printInfo('')
// Example 2: Direct byte array
printInfo('2. Direct Uint8Array from known bytes:')
const directBytes = new Uint8Array([109, 101, 116, 97, 100, 97, 116, 97]) // "metadata"
printInfo(` new Uint8Array([${Array.from(directBytes).join(', ')}])`)
printInfo(` Decodes to: "${new TextDecoder().decode(directBytes)}"`)
const box2 = await indexer.lookupApplicationBoxByIdAndName(appId, directBytes)
printInfo(` Box value: ${decodeBoxValue(box2.value)}`)
printInfo('')
// Example 3: Hexadecimal string to Uint8Array
printInfo('3. Hex string to Uint8Array (for binary box names):')
const hexStr = '73657474696e6773' // "settings" in hex
const hexBytes = new Uint8Array(hexStr.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)))
printInfo(` Hex "${hexStr}" to bytes`)
printInfo(` Decodes to: "${new TextDecoder().decode(hexBytes)}"`)
const box3 = await indexer.lookupApplicationBoxByIdAndName(appId, hexBytes)
printInfo(` Box value: ${decodeBoxValue(box3.value)}`)
} catch (error) {
printError(`Box name encoding demo failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Step 8: Handle case where box is not found
// =========================================================================
printStep(8, 'Handling case where box is not found')
try {
const nonExistentBoxName = new TextEncoder().encode('does_not_exist')
printInfo('Attempting to lookup non-existent box "does_not_exist"...')
await indexer.lookupApplicationBoxByIdAndName(appId, nonExistentBoxName)
// If we get here, the box was found (unexpected)
printInfo('Box was found (unexpected)')
} catch (error) {
printSuccess('Correctly caught error for non-existent box')
if (error instanceof Error) {
printInfo(`Error message: ${error.message}`)
}
printInfo('')
printInfo('Always handle the case where a box may not exist.')
printInfo('The indexer throws an error when the box is not found.')
}
// =========================================================================
// Step 9: Demonstrate pagination for applications with many boxes
// =========================================================================
printStep(9, 'Demonstrating pagination with limit and next parameters')
try {
// First page with limit of 2
printInfo('Fetching first page of boxes (limit: 2)...')
const page1 = await indexer.searchForApplicationBoxes(appId, { limit: 2 })
printInfo(`Page 1: Retrieved ${page1.boxes.length} box(es)`)
for (const box of page1.boxes) {
printInfo(` - ${decodeBoxName(box.name)}`)
}
// Check if there are more results
if (page1.nextToken) {
printInfo(`Next token available: ${page1.nextToken.substring(0, 20)}...`)
printInfo('')
// Fetch second page using next token
printInfo('Fetching second page using next token...')
const page2 = await indexer.searchForApplicationBoxes(appId, {
limit: 2,
next: page1.nextToken,
})
printInfo(`Page 2: Retrieved ${page2.boxes.length} box(es)`)
for (const box of page2.boxes) {
printInfo(` - ${decodeBoxName(box.name)}`)
}
if (page2.nextToken) {
printInfo(`More results available (nextToken present)`)
printInfo('')
// Fetch remaining boxes
printInfo('Fetching remaining boxes...')
const page3 = await indexer.searchForApplicationBoxes(appId, {
limit: 10,
next: page2.nextToken,
})
printInfo(`Page 3: Retrieved ${page3.boxes.length} box(es)`)
for (const box of page3.boxes) {
printInfo(` - ${decodeBoxName(box.name)}`)
}
if (!page3.nextToken) {
printInfo('No more results (no nextToken)')
}
} else {
printInfo('No more results (no nextToken)')
}
} else {
printInfo('No pagination needed (all results fit in one page)')
}
} catch (error) {
printError(`Pagination demo failed: ${error instanceof Error ? error.message : String(error)}`)
}
// =========================================================================
// Summary
// =========================================================================
printHeader('Summary')
printInfo('This example demonstrated:')
printInfo(' 1. Deploying an application that uses box storage')
printInfo(' 2. Creating several boxes via app calls')
printInfo(' 3. Handling the case where application has no boxes')
printInfo(' 4. searchForApplicationBoxes(appId) - List all box names')
printInfo(' 5. lookupApplicationBoxByIdAndName(appId, boxName) - Get specific box value')
printInfo(' 6. Properly encoding box names using Uint8Array')
printInfo(' 7. Displaying box values after decoding from Uint8Array')
printInfo(' 8. Handling the case where box is not found')
printInfo(' 9. Pagination with limit and next parameters')
printInfo('')
printInfo('Key searchForApplicationBoxes response fields (BoxesResponse):')
printInfo(' - applicationId: The application identifier (bigint)')
printInfo(' - boxes: Array of BoxDescriptor objects (just names, not values)')
printInfo(' - nextToken: Pagination token for next page (optional)')
printInfo('')
printInfo('Key BoxDescriptor fields:')
printInfo(' - name: Box name as Uint8Array (raw bytes)')
printInfo('')
printInfo('Key lookupApplicationBoxByIdAndName response fields (Box):')
printInfo(' - round: Round at which box was retrieved (bigint)')
printInfo(' - name: Box name as Uint8Array')
printInfo(' - value: Box value as Uint8Array')
printInfo('')
printInfo('searchForApplicationBoxes() filter parameters:')
printInfo(' - limit: Maximum results per page')
printInfo(' - next: Pagination token from previous response')
printInfo('')
printInfo('Note: Box names are returned sorted lexicographically by the indexer.')
}
main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})