Application Boxes
Description
Section titled “Description”This example demonstrates how to query application boxes using the AlgodClient methods: applicationBoxes() and applicationBoxByName()
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 algod_client/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)})Other examples in Algod Client
Section titled “Other examples in Algod Client”- Node Health and Status
- Version and Genesis Information
- Ledger Supply Information
- Account Information
- Transaction Parameters
- Send and Confirm Transaction
- Pending Transactions
- Block Data
- Asset Information
- Application Information
- Application Boxes
- TEAL Compile and Disassemble
- Transaction Simulation
- Ledger State Deltas
- Transaction Proof
- Light Block Header Proof
- State Proof
- DevMode Timestamp Offset
- Sync Round Management