Skip to content

App Manager

← Back to Algorand Client

This example demonstrates the AppManager functionality for querying application information, state, box storage, and TEAL compilation:

  • algorand.app.getById() to fetch application information
  • algorand.app.getGlobalState() to read global state
  • algorand.app.getLocalState() to read account’s local state
  • algorand.app.getBoxNames() to list all box names for an app
  • algorand.app.getBoxValue() to read a specific box value
  • algorand.app.getBoxValues() to read multiple box values
  • algorand.app.getBoxValuesFromABIType() to decode typed box values
  • algorand.app.compileTeal() to compile TEAL source code
  • algorand.app.compileTealTemplate() to compile TEAL with template variables
  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example algorand_client/12-app-manager.ts

View source on GitHub

12-app-manager.ts
/**
* Example: App Manager
*
* This example demonstrates the AppManager functionality for querying
* application information, state, box storage, and TEAL compilation:
* - algorand.app.getById() to fetch application information
* - algorand.app.getGlobalState() to read global state
* - algorand.app.getLocalState() to read account's local state
* - algorand.app.getBoxNames() to list all box names for an app
* - algorand.app.getBoxValue() to read a specific box value
* - algorand.app.getBoxValues() to read multiple box values
* - algorand.app.getBoxValuesFromABIType() to decode typed box values
* - algorand.app.compileTeal() to compile TEAL source code
* - algorand.app.compileTealTemplate() to compile TEAL with template variables
*
* Prerequisites:
* - LocalNet running (via `algokit localnet start`)
*/
import { ABIType } from '@algorandfoundation/algokit-utils/abi'
import { OnApplicationComplete } from '@algorandfoundation/algokit-utils/transact'
import { AlgorandClient, algo } from '@algorandfoundation/algokit-utils'
import { loadTealSource, printError, printHeader, printInfo, printStep, printSuccess, shortenAddress } from '../shared/utils.js'
// ============================================================================
// TEAL Programs - loaded from shared artifacts
// ============================================================================
// A stateful app that supports global state, local state, and box storage
const APPROVAL_PROGRAM = loadTealSource('approval-box-storage.teal')
// Clear state program (must always approve)
const CLEAR_STATE_PROGRAM = loadTealSource('clear-state-approve.teal')
// A TEAL template with replaceable parameters
const TEAL_TEMPLATE = loadTealSource('teal-template-basic.teal')
// A TEAL template with AlgoKit deploy-time control parameters
const ALGOKIT_TEMPLATE = loadTealSource('teal-template-deploy-control.teal')
async function main() {
printHeader('App Manager Example')
// Initialize client and verify LocalNet is running
const algorand = AlgorandClient.defaultLocalNet()
try {
await algorand.client.algod.status()
printSuccess('Connected to LocalNet')
} catch (error) {
printError(`Failed to connect to LocalNet: ${error instanceof Error ? error.message : String(error)}`)
printInfo('Make sure LocalNet is running (e.g., algokit localnet start)')
return
}
// Step 1: Create and fund test accounts
printStep(1, 'Create and fund test accounts')
printInfo('Creating accounts for app manager demonstrations')
const creator = algorand.account.random()
const user = algorand.account.random()
printInfo(`\nCreated accounts:`)
printInfo(` Creator: ${shortenAddress(creator.addr.toString())}`)
printInfo(` User: ${shortenAddress(user.addr.toString())}`)
// Fund accounts
await algorand.account.ensureFundedFromEnvironment(creator.addr, algo(20))
await algorand.account.ensureFundedFromEnvironment(user.addr, algo(10))
printSuccess('Created and funded test accounts')
// Step 2: Deploy a test application with state and boxes
printStep(2, 'Deploy a test application with state and boxes')
printInfo('Creating an app with global state, local state schema, and box storage')
const createResult = await algorand.send.appCreate({
sender: creator.addr,
approvalProgram: APPROVAL_PROGRAM,
clearStateProgram: CLEAR_STATE_PROGRAM,
schema: {
globalInts: 1, // counter
globalByteSlices: 2, // message, creator
localInts: 2, // user_score, opted_in_round
localByteSlices: 0,
},
})
const appId = createResult.appId
const appAddress = createResult.appAddress
printInfo(`\nApplication created:`)
printInfo(` App ID: ${appId}`)
printInfo(` App Address: ${shortenAddress(appAddress.toString())}`)
printInfo(` Transaction ID: ${createResult.txIds[0]}`)
printSuccess('Test application deployed')
// Step 3: Demonstrate algorand.app.getById()
printStep(3, 'Demonstrate algorand.app.getById() to fetch application information')
printInfo('Fetching complete application information by ID')
const appInfo = await algorand.app.getById(appId)
printInfo(`\nApplication information:`)
printInfo(` App ID: ${appInfo.appId}`)
printInfo(` App Address: ${shortenAddress(appInfo.appAddress.toString())}`)
printInfo(` Creator: ${shortenAddress(appInfo.creator.toString())}`)
printInfo(` Global State Schema:`)
printInfo(` globalInts: ${appInfo.globalInts}`)
printInfo(` globalByteSlices: ${appInfo.globalByteSlices}`)
printInfo(` Local State Schema:`)
printInfo(` localInts: ${appInfo.localInts}`)
printInfo(` localByteSlices: ${appInfo.localByteSlices}`)
printInfo(` Extra Program Pages: ${appInfo.extraProgramPages ?? 0}`)
printInfo(` Approval Program: ${appInfo.approvalProgram.length} bytes`)
printInfo(` Clear State Program: ${appInfo.clearStateProgram.length} bytes`)
printSuccess('Application information retrieved')
// Step 4: Demonstrate algorand.app.getGlobalState()
printStep(4, 'Demonstrate algorand.app.getGlobalState() to read global state')
printInfo('Reading all global state key-value pairs')
const globalState = await algorand.app.getGlobalState(appId)
printInfo(`\nGlobal state entries:`)
for (const [key, stateValue] of Object.entries(globalState)) {
if ('valueRaw' in stateValue) {
// Byte slice value
printInfo(` "${key}": "${stateValue.value}" (bytes)`)
} else {
// Integer value
printInfo(` "${key}": ${stateValue.value} (uint64)`)
}
}
// Increment counter a few times
printInfo(`\nIncrementing counter...`)
for (let i = 0; i < 3; i++) {
await algorand.send.appCall({
sender: creator.addr,
appId: appId,
args: [new TextEncoder().encode('increment')],
note: `increment-${i}`, // Unique note to differentiate transactions
})
}
const updatedGlobalState = await algorand.app.getGlobalState(appId)
printInfo(` Counter after 3 increments: ${updatedGlobalState['counter']?.value ?? 0}`)
printSuccess('Global state read successfully')
// Step 5: Demonstrate algorand.app.getLocalState()
printStep(5, 'Demonstrate algorand.app.getLocalState() to read local state')
printInfo('User must opt in first to have local state')
// Opt user into the app
await algorand.send.appCall({
sender: user.addr,
appId: appId,
onComplete: OnApplicationComplete.OptIn,
})
printInfo('User opted in to the application')
const localState = await algorand.app.getLocalState(appId, user.addr)
printInfo(`\nUser's local state:`)
for (const [key, stateValue] of Object.entries(localState)) {
if ('valueRaw' in stateValue) {
printInfo(` "${key}": "${stateValue.value}" (bytes)`)
} else {
printInfo(` "${key}": ${stateValue.value} (uint64)`)
}
}
printInfo(`\nLocal state was initialized on opt-in with:`)
printInfo(` user_score: ${localState['user_score']?.value ?? 0} (initial value)`)
printInfo(` opted_in_round: ${localState['opted_in_round']?.value ?? 0} (round when opted in)`)
printSuccess('Local state read successfully')
// Step 6: Create boxes and demonstrate box operations
printStep(6, 'Demonstrate box storage operations')
printInfo('Creating boxes to store application data')
// Fund the app account for box storage (boxes require MBR)
await algorand.send.payment({
sender: creator.addr,
receiver: appAddress,
amount: algo(1), // Fund for box storage MBR
})
// Create multiple boxes with different content
const boxData = [
{ name: 'user_data', value: 'Alice:100:premium' },
{ name: 'config', value: '{"version":1,"enabled":true}' },
{ name: 'scores', value: 'high:9999,low:1' },
]
for (const box of boxData) {
await algorand.send.appCall({
sender: creator.addr,
appId: appId,
args: [
new TextEncoder().encode('set_box'),
new TextEncoder().encode(box.name),
new TextEncoder().encode(box.value),
],
boxReferences: [{ appId: 0n, name: new TextEncoder().encode(box.name) }],
})
printInfo(` Created box "${box.name}" with ${box.value.length} bytes`)
}
printSuccess('Boxes created')
// Step 7: Demonstrate algorand.app.getBoxNames()
printStep(7, 'Demonstrate algorand.app.getBoxNames() to list all boxes')
printInfo('Retrieving all box names for the application')
const boxNames = await algorand.app.getBoxNames(appId)
printInfo(`\nApplication has ${boxNames.length} boxes:`)
for (const boxName of boxNames) {
printInfo(` Name: "${boxName.name}"`)
printInfo(` Raw bytes: ${boxName.nameRaw.length} bytes`)
printInfo(` Base64: ${boxName.nameBase64}`)
}
printSuccess('Box names retrieved')
// Step 8: Demonstrate algorand.app.getBoxValue()
printStep(8, 'Demonstrate algorand.app.getBoxValue() to read a single box')
printInfo('Reading the value of a specific box by name')
const boxValue = await algorand.app.getBoxValue(appId, 'user_data')
printInfo(`\nBox "user_data" value:`)
printInfo(` Raw bytes: ${boxValue.length} bytes`)
printInfo(` As string: "${new TextDecoder().decode(boxValue)}"`)
// Read another box
const configValue = await algorand.app.getBoxValue(appId, 'config')
printInfo(`\nBox "config" value:`)
printInfo(` As string: "${new TextDecoder().decode(configValue)}"`)
printSuccess('Box value retrieved')
// Step 9: Demonstrate algorand.app.getBoxValues()
printStep(9, 'Demonstrate algorand.app.getBoxValues() to read multiple boxes at once')
printInfo('Reading multiple box values in a single call')
const allBoxValues = await algorand.app.getBoxValues(appId, ['user_data', 'config', 'scores'])
printInfo(`\nRetrieved ${allBoxValues.length} box values:`)
const boxNamesArray = ['user_data', 'config', 'scores']
for (let i = 0; i < allBoxValues.length; i++) {
printInfo(` "${boxNamesArray[i]}": "${new TextDecoder().decode(allBoxValues[i])}"`)
}
printSuccess('Multiple box values retrieved')
// Step 10: Demonstrate algorand.app.getBoxValuesFromABIType()
printStep(10, 'Demonstrate algorand.app.getBoxValuesFromABIType() for typed box decoding')
printInfo('Creating boxes with ABI-encoded values and decoding them')
// Create a box with ABI-encoded uint64 value
const abiType = ABIType.from('uint64')
const encodedValue = abiType.encode(BigInt(42))
await algorand.send.appCall({
sender: creator.addr,
appId: appId,
args: [
new TextEncoder().encode('set_box'),
new TextEncoder().encode('abi_number'),
encodedValue,
],
boxReferences: [{ appId: 0n, name: new TextEncoder().encode('abi_number') }],
})
printInfo('Created box "abi_number" with ABI-encoded uint64 value')
// Read and decode the ABI value
const decodedValues = await algorand.app.getBoxValuesFromABIType({
appId: appId,
boxNames: ['abi_number'],
type: abiType,
})
printInfo(`\nDecoded ABI values:`)
printInfo(` "abi_number" (uint64): ${decodedValues[0]}`)
// Create boxes with ABI-encoded string values
const stringType = ABIType.from('string')
const encodedString = stringType.encode('Hello, ABI!')
await algorand.send.appCall({
sender: creator.addr,
appId: appId,
args: [
new TextEncoder().encode('set_box'),
new TextEncoder().encode('abi_string'),
encodedString,
],
boxReferences: [{ appId: 0n, name: new TextEncoder().encode('abi_string') }],
})
const decodedStrings = await algorand.app.getBoxValuesFromABIType({
appId: appId,
boxNames: ['abi_string'],
type: stringType,
})
printInfo(` "abi_string" (string): "${decodedStrings[0]}"`)
printSuccess('ABI-typed box values decoded')
// Step 11: Demonstrate algorand.app.compileTeal()
printStep(11, 'Demonstrate algorand.app.compileTeal() to compile TEAL source')
printInfo('Compiling TEAL code and examining the result')
const simpleTeal = loadTealSource('simple-approve.teal')
const compiled = await algorand.app.compileTeal(simpleTeal)
printInfo(`\nCompilation result:`)
printInfo(` Original TEAL: ${compiled.teal.split('\n').length} lines`)
printInfo(` Compiled (base64): ${compiled.compiled.slice(0, 30)}...`)
printInfo(` Compiled hash: ${compiled.compiledHash}`)
printInfo(` Compiled bytes: ${compiled.compiledBase64ToBytes.length} bytes`)
printInfo(` Source map available: ${compiled.sourceMap !== undefined}`)
// Compile the approval program
const approvalCompiled = await algorand.app.compileTeal(APPROVAL_PROGRAM)
printInfo(`\nApproval program compilation:`)
printInfo(` Original: ${APPROVAL_PROGRAM.split('\n').length} lines`)
printInfo(` Compiled: ${approvalCompiled.compiledBase64ToBytes.length} bytes`)
printSuccess('TEAL compilation successful')
// Step 12: Demonstrate algorand.app.compileTealTemplate()
printStep(12, 'Demonstrate algorand.app.compileTealTemplate() with template variables')
printInfo('Compiling TEAL templates with parameter substitution')
// Compile template with custom parameters
const compiledTemplate = await algorand.app.compileTealTemplate(TEAL_TEMPLATE, {
TMPL_INT_VALUE: 42,
TMPL_BYTES_VALUE: 'hello',
})
printInfo(`\nTemplate compilation with custom parameters:`)
printInfo(` TMPL_INT_VALUE: 42`)
printInfo(` TMPL_BYTES_VALUE: "hello"`)
printInfo(` Compiled bytes: ${compiledTemplate.compiledBase64ToBytes.length} bytes`)
// Compile AlgoKit template with deploy-time control parameters
const compiledUpdatable = await algorand.app.compileTealTemplate(
ALGOKIT_TEMPLATE,
undefined,
{ updatable: true, deletable: false },
)
printInfo(`\nAlgoKit template with deploy-time controls:`)
printInfo(` updatable: true`)
printInfo(` deletable: false`)
printInfo(` Compiled bytes: ${compiledUpdatable.compiledBase64ToBytes.length} bytes`)
// Compile with different control values
const compiledImmutable = await algorand.app.compileTealTemplate(
ALGOKIT_TEMPLATE,
undefined,
{ updatable: false, deletable: false },
)
printInfo(`\nImmutable version (updatable: false, deletable: false):`)
printInfo(` Compiled bytes: ${compiledImmutable.compiledBase64ToBytes.length} bytes`)
printInfo(` Note: Different control values produce different bytecode`)
printSuccess('TEAL template compilation successful')
// Step 13: Summary
printStep(13, 'Summary - App Manager API')
printInfo('The AppManager provides comprehensive application query and compile capabilities:')
printInfo('')
printInfo('algorand.app.getById(appId):')
printInfo(' - Fetches complete application information')
printInfo(' - Returns: AppInformation with id, address, creator, programs, schemas')
printInfo('')
printInfo('algorand.app.getGlobalState(appId):')
printInfo(' - Reads all global state key-value pairs')
printInfo(' - Returns: AppState object keyed by UTF-8 strings')
printInfo(' - Values include both raw bytes and decoded forms')
printInfo('')
printInfo('algorand.app.getLocalState(appId, address):')
printInfo(' - Reads an account\'s local state for an app')
printInfo(' - Account must be opted in to the application')
printInfo(' - Returns: AppState object with local key-value pairs')
printInfo('')
printInfo('algorand.app.getBoxNames(appId):')
printInfo(' - Lists all box names for an application')
printInfo(' - Returns: BoxName[] with name, nameRaw, nameBase64')
printInfo('')
printInfo('algorand.app.getBoxValue(appId, boxName):')
printInfo(' - Reads a single box value by name')
printInfo(' - Returns: Uint8Array of raw box contents')
printInfo('')
printInfo('algorand.app.getBoxValues(appId, boxNames):')
printInfo(' - Reads multiple box values in one call')
printInfo(' - Returns: Uint8Array[] in same order as input names')
printInfo('')
printInfo('algorand.app.getBoxValuesFromABIType({ appId, boxNames, type }):')
printInfo(' - Reads and decodes box values using ABI types')
printInfo(' - Supports all ABI types: uint64, string, address, arrays, tuples')
printInfo(' - Returns: ABIValue[] decoded according to specified type')
printInfo('')
printInfo('algorand.app.compileTeal(tealCode):')
printInfo(' - Compiles TEAL source code')
printInfo(' - Returns: CompiledTeal with compiled bytes, hash, source map')
printInfo(' - Results are cached to avoid recompilation')
printInfo('')
printInfo('algorand.app.compileTealTemplate(template, params?, deployMetadata?):')
printInfo(' - Compiles TEAL with template parameter substitution')
printInfo(' - Supports custom TMPL_* parameters')
printInfo(' - Supports AlgoKit deploy-time controls (TMPL_UPDATABLE, TMPL_DELETABLE)')
// Clean up - close out user and delete app
await algorand.send.appCall({
sender: user.addr,
appId: appId,
onComplete: OnApplicationComplete.CloseOut,
})
await algorand.send.appDelete({
sender: creator.addr,
appId: appId,
})
printSuccess('App Manager example completed!')
}
main().catch((error) => {
printError(`Unhandled error: ${error instanceof Error ? error.message : String(error)}`)
process.exit(1)
})