Skip to content

Application Boxes

← Back to Algod Client

This example demonstrates how to query application boxes using the AlgodClient methods: applicationBoxes() and applicationBoxByName()

  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example algod_client/11-application-boxes.ts

View source on GitHub

11-application-boxes.ts
/**
* Example: Application Boxes
*
* This example demonstrates how to query application boxes using
* the AlgodClient methods: applicationBoxes() and applicationBoxByName()
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { getApplicationAddress } from '@algorandfoundation/algokit-utils'
import {
createAlgodClient,
createAlgorandClient,
getFundedAccount,
loadTealSource,
printError,
printHeader,
printInfo,
printStep,
printSuccess,
shortenAddress,
} from '../shared/utils.js'
async function main() {
printHeader('Application Boxes Example')
// Create an Algod client connected to LocalNet
const algod = createAlgodClient()
// Create an AlgorandClient for application deployment
const algorand = createAlgorandClient()
// =========================================================================
// Step 1: Get a Funded Account from LocalNet
// =========================================================================
printStep(1, 'Getting a funded account from LocalNet')
let creator: Awaited<ReturnType<typeof getFundedAccount>>
try {
creator = await getFundedAccount(algorand)
printSuccess(`Got funded account: ${shortenAddress(creator.addr.toString())}`)
} catch (error) {
printError(`Failed to get funded account: ${error instanceof Error ? error.message : String(error)}`)
printInfo('Make sure LocalNet is running with `algokit localnet start`')
printInfo('If issues persist, try `algokit localnet reset`')
process.exit(1)
}
// =========================================================================
// Step 2: Deploy an Application that Uses Box Storage
// =========================================================================
printStep(2, 'Deploying an application that uses box storage')
// 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]
// Box operations require the box to be referenced in the transaction
const approvalSource = loadTealSource('approval-box-ops.teal')
// Load clear state program from shared artifacts
const clearSource = loadTealSource('clear-state-approve.teal')
try {
printInfo('Compiling TEAL programs...')
const approvalCompiled = await algod.tealCompile(approvalSource)
const clearCompiled = await algod.tealCompile(clearSource)
printSuccess(`Approval program hash: ${approvalCompiled.hash}`)
printInfo('Deploying application...')
const result = await algorand.send.appCreate({
sender: creator.addr,
approvalProgram: Buffer.from(approvalCompiled.result, 'base64'),
clearStateProgram: Buffer.from(clearCompiled.result, 'base64'),
schema: {
globalInts: 0,
globalByteSlices: 0,
localInts: 0,
localByteSlices: 0,
},
})
const appId = result.appId
printSuccess(`Application deployed with ID: ${appId}`)
printInfo('')
// =========================================================================
// Step 3: Handle Case Where Application Has No Boxes
// =========================================================================
printStep(3, 'Querying boxes when application has no boxes')
const emptyBoxes = await algod.applicationBoxes(appId)
printInfo('Boxes response for new application:')
printInfo(` Total boxes: ${emptyBoxes.boxes.length}`)
if (emptyBoxes.boxes.length === 0) {
printSuccess('Correctly shows 0 boxes for new application')
printInfo('Applications start with no boxes - boxes are created via app calls')
}
printInfo('')
// =========================================================================
// Step 4: Create Several Boxes with Different Names and Values
// =========================================================================
printStep(4, 'Creating several boxes with different names and values')
// Box names and values to create
const boxData = [
{ name: 'user_count', value: 'counter:42' },
{ name: 'settings', value: '{"theme":"dark","lang":"en"}' },
{ name: 'metadata', value: 'v1.0.0-production' },
]
// Need to fund the app account for box storage MBR
printInfo('Funding application account for box storage MBR...')
const { algo } = await import('@algorandfoundation/algokit-utils')
const appAddress = getApplicationAddress(appId)
await algorand.send.payment({
sender: creator.addr,
receiver: appAddress.toString(),
amount: algo(1), // 1 ALGO should cover several boxes
})
printSuccess(`Funded app account: ${shortenAddress(appAddress.toString())}`)
for (const box of boxData) {
printInfo(`Creating box "${box.name}" with value "${box.value}"...`)
const boxNameBytes = new TextEncoder().encode(box.name)
const boxValueBytes = new TextEncoder().encode(box.value)
await algorand.send.appCall({
sender: creator.addr,
appId: appId,
args: [new TextEncoder().encode('create_box'), boxNameBytes, boxValueBytes],
boxReferences: [{ appId: appId, name: boxNameBytes }],
})
printSuccess(`Created box "${box.name}"`)
}
printInfo('')
// =========================================================================
// Step 5: Demonstrate applicationBoxes() to List All Boxes
// =========================================================================
printStep(5, 'Listing all boxes with applicationBoxes(appId)')
const boxesResponse = await algod.applicationBoxes(appId)
printInfo('BoxesResponse structure:')
printInfo(` boxes: BoxDescriptor[] (length: ${boxesResponse.boxes.length})`)
printInfo('')
printInfo('All boxes for this application:')
for (let i = 0; i < boxesResponse.boxes.length; i++) {
const boxDescriptor = boxesResponse.boxes[i]
// The name is a Uint8Array - decode it for display
const nameStr = new TextDecoder().decode(boxDescriptor.name)
const nameHex = Buffer.from(boxDescriptor.name).toString('hex')
printInfo(` [${i}] Name: "${nameStr}"`)
printInfo(` Raw (hex): 0x${nameHex}`)
printInfo(` Raw (bytes): ${boxDescriptor.name.length} bytes`)
}
printInfo('')
printInfo('applicationBoxes() returns BoxDescriptor[] with just the names')
printInfo('To get the actual values, use applicationBoxByName()')
printInfo('')
// =========================================================================
// Step 6: Demonstrate applicationBoxByName() to Get Specific Box
// =========================================================================
printStep(6, 'Getting specific box values with applicationBoxByName()')
for (const box of boxData) {
const boxNameBytes = new TextEncoder().encode(box.name)
const boxResult = await algod.applicationBoxByName(appId, boxNameBytes)
printInfo(`Box "${box.name}":`)
printInfo(` Round: ${boxResult.round}`)
printInfo(` Name: "${new TextDecoder().decode(boxResult.name)}"`)
printInfo(` Value: "${new TextDecoder().decode(boxResult.value)}"`)
printInfo(` Size: ${boxResult.value.length} bytes`)
printInfo('')
}
// =========================================================================
// Step 7: Show Box Structure and Decoding
// =========================================================================
printStep(7, 'Understanding Box structure and decoding')
const exampleBox = await algod.applicationBoxByName(appId, new TextEncoder().encode('settings'))
printInfo('Box type structure:')
printInfo(' {')
printInfo(` round: bigint = ${exampleBox.round}`)
printInfo(` name: Uint8Array = ${exampleBox.name.length} bytes`)
printInfo(` value: Uint8Array = ${exampleBox.value.length} bytes`)
printInfo(' }')
printInfo('')
printInfo('Different decoding methods:')
printInfo(` As UTF-8 string: "${new TextDecoder().decode(exampleBox.value)}"`)
printInfo(` As hex: 0x${Buffer.from(exampleBox.value).toString('hex')}`)
printInfo(` As base64: ${Buffer.from(exampleBox.value).toString('base64')}`)
printInfo('')
// Parse JSON if it looks like JSON
const valueStr = new TextDecoder().decode(exampleBox.value)
if (valueStr.startsWith('{')) {
try {
const parsed = JSON.parse(valueStr)
printInfo(' As parsed JSON:')
for (const [key, val] of Object.entries(parsed)) {
printInfo(` ${key}: ${JSON.stringify(val)}`)
}
} catch {
// Not valid JSON
}
}
printInfo('')
printInfo('Box values are raw bytes - the encoding/format is application-defined')
printInfo('')
// =========================================================================
// Step 8: Handle Box Not Found Error
// =========================================================================
printStep(8, 'Handling non-existent box')
try {
const nonExistentBox = new TextEncoder().encode('does_not_exist')
printInfo('Querying non-existent box "does_not_exist"...')
await algod.applicationBoxByName(appId, nonExistentBox)
printError('Expected an error but none was thrown')
} catch (error) {
printSuccess('Correctly caught error for non-existent box')
if (error instanceof Error) {
printInfo(` Error message: ${error.message}`)
}
printInfo('Always handle the case where a box may not exist')
}
printInfo('')
// =========================================================================
// Step 9: Box Costs and MBR
// =========================================================================
printStep(9, 'Understanding box storage costs')
printInfo('Box storage requires minimum balance (MBR) in the app account:')
printInfo(' - Base cost per box: 2,500 microAlgo (0.0025 ALGO)')
printInfo(' - Cost per byte: 400 microAlgo per byte')
printInfo(' - Formula: 2500 + (400 * (box_name_length + box_value_length))')
printInfo('')
// Calculate MBR for our boxes
printInfo('MBR for boxes we created:')
for (const box of boxData) {
const nameLen = new TextEncoder().encode(box.name).length
const valueLen = new TextEncoder().encode(box.value).length
const mbr = 2500 + 400 * (nameLen + valueLen)
printInfo(` "${box.name}": ${mbr.toLocaleString('en-US')} µALGO (name: ${nameLen}B, value: ${valueLen}B)`)
}
printInfo('')
} catch (error) {
printError(`Failed to complete example: ${error instanceof Error ? error.message : String(error)}`)
printInfo('If LocalNet errors occur, try `algokit localnet reset`')
process.exit(1)
}
// =========================================================================
// Summary
// =========================================================================
printHeader('Summary')
printInfo('This example demonstrated:')
printInfo(' 1. Deploying an application that uses box storage')
printInfo(' 2. Creating boxes via app calls with boxReferences')
printInfo(' 3. applicationBoxes(appId) - List all box names for an app')
printInfo(' 4. applicationBoxByName(appId, boxName) - Get specific box value')
printInfo(' 5. Handling the case where application has no boxes')
printInfo(' 6. Error handling for non-existent boxes')
printInfo(' 7. Understanding box storage costs (MBR)')
printInfo('')
printInfo('Key types and methods:')
printInfo(' - applicationBoxes(appId, { max? }) -> BoxesResponse { boxes: BoxDescriptor[] }')
printInfo(' - applicationBoxByName(appId, boxName: Uint8Array) -> Box { round, name, value }')
printInfo(' - BoxDescriptor: { name: Uint8Array }')
printInfo(' - Box: { round: bigint, name: Uint8Array, value: Uint8Array }')
printInfo('')
printInfo('Box storage notes:')
printInfo(' - Boxes must be referenced in transaction boxReferences to be accessed')
printInfo(' - Box names can be any bytes (not just UTF-8 strings)')
printInfo(' - Box values are raw bytes - format is application-defined')
printInfo(' - App account must have sufficient MBR for box storage')
printInfo(' - Max box name: 64 bytes, max box value: 32,768 bytes')
}
main().catch((error) => {
console.error('Fatal error:', error)
process.exit(1)
})