ARC-56 Storage Helpers
Description
Section titled “Description”This example demonstrates how to use ARC-56 storage helpers to inspect contract state key definitions and maps from an ARC-56 contract specification: Storage Key Functions:
- getGlobalABIStorageKeys(): Get all global state key definitions
- getLocalABIStorageKeys(): Get all local state key definitions
- getBoxABIStorageKeys(): Get all box key definitions Storage Map Functions:
- getBoxABIStorageMaps(): Get box map definitions ABIStorageKey properties:
- key: Base64-encoded key bytes
- keyType: The type of the key (ABIType or AVMType)
- valueType: The type of the value (ABIType or AVMType)
- desc?: Optional description ABIStorageMap properties:
- keyType: The type of keys in the map
- valueType: The type of values in the map
- desc?: Optional description
- prefix?: Base64-encoded prefix for map keys The example also deploys a contract to LocalNet and reads actual state values.
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 abi/15-arc56-storage.ts/** * Example: ARC-56 Storage Helpers * * This example demonstrates how to use ARC-56 storage helpers to inspect * contract state key definitions and maps from an ARC-56 contract specification: * * Storage Key Functions: * - getGlobalABIStorageKeys(): Get all global state key definitions * - getLocalABIStorageKeys(): Get all local state key definitions * - getBoxABIStorageKeys(): Get all box key definitions * * Storage Map Functions: * - getBoxABIStorageMaps(): Get box map definitions * * ABIStorageKey properties: * - key: Base64-encoded key bytes * - keyType: The type of the key (ABIType or AVMType) * - valueType: The type of the value (ABIType or AVMType) * - desc?: Optional description * * ABIStorageMap properties: * - keyType: The type of keys in the map * - valueType: The type of values in the map * - desc?: Optional description * - prefix?: Base64-encoded prefix for map keys * * The example also deploys a contract to LocalNet and reads actual state values. * * Prerequisites: * - LocalNet running (via `algokit localnet start`) */
import { AlgorandClient, algo } from '@algorandfoundation/algokit-utils'import type { ABIStorageKey, ABIStorageMap, Arc56Contract } from '@algorandfoundation/algokit-utils/abi'import { ABIType, decodeAVMValue, getBoxABIStorageKeys, getBoxABIStorageMaps, getGlobalABIStorageKeys, getLocalABIStorageKeys, isAVMType,} from '@algorandfoundation/algokit-utils/abi'import { readFileSync } from 'fs'import { join } from 'path'import { formatBytes, formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
// tsx provides __dirname in CJS-compatibility modedeclare const __dirname: string
/** * Formats a storage key type for display (either ABI type or AVM type) */function formatKeyOrValueType(type: ABIType | string): string { if (typeof type === 'string') { return type // AVM type (e.g., "AVMUint64", "AVMString", "AVMBytes") } return type.toString() // ABI type}
/** * Displays ABIStorageKey properties */function displayStorageKey(name: string, storageKey: ABIStorageKey): void { printInfo(` ${name}:`) printInfo(` key (base64): ${storageKey.key}`) printInfo(` key (decoded): "${Buffer.from(storageKey.key, 'base64').toString('utf-8')}"`) printInfo(` keyType: ${formatKeyOrValueType(storageKey.keyType)}`) printInfo(` valueType: ${formatKeyOrValueType(storageKey.valueType)}`) if (storageKey.desc) { printInfo(` desc: ${storageKey.desc}`) }}
/** * Displays ABIStorageMap properties */function displayStorageMap(name: string, storageMap: ABIStorageMap): void { printInfo(` ${name}:`) printInfo(` keyType: ${formatKeyOrValueType(storageMap.keyType)}`) printInfo(` valueType: ${formatKeyOrValueType(storageMap.valueType)}`) if (storageMap.desc) { printInfo(` desc: ${storageMap.desc}`) } if (storageMap.prefix !== undefined) { printInfo(` prefix (base64): "${storageMap.prefix}"`) if (storageMap.prefix) { printInfo(` prefix (decoded): "${Buffer.from(storageMap.prefix, 'base64').toString('utf-8')}"`) } }}
/** * Decodes a raw state value using the storage key's valueType */function decodeStateValue(storageKey: ABIStorageKey, rawBytes: Uint8Array | undefined): string { // Handle undefined or empty bytes if (!rawBytes || rawBytes.length === 0) { return '(empty)' }
const valueType = storageKey.valueType try { if (typeof valueType === 'string' && isAVMType(valueType)) { // AVM type const decoded = decodeAVMValue(valueType, rawBytes) if (decoded instanceof Uint8Array) { return formatHex(decoded) } return String(decoded) } else if (valueType instanceof ABIType) { // ABI type const decoded = valueType.decode(rawBytes) if (decoded instanceof Uint8Array) { return formatHex(decoded) } return JSON.stringify(decoded) } } catch { // If decoding fails, fall through to raw bytes display } return formatBytes(rawBytes)}
async function main() { printHeader('ARC-56 Storage Helpers Example')
// Step 1: Load ARC-56 contract specification printStep(1, 'Load ARC-56 Contract Specification')
// Load the application.json (ARC-56 format with TEAL source) from tests/example-contracts/state const arc56Path = join(__dirname, '..', '..', '..', 'tests', 'example-contracts', 'state', 'application.json') const arc56Content = readFileSync(arc56Path, 'utf-8') const appSpec: Arc56Contract = JSON.parse(arc56Content)
printInfo(`Loaded contract: ${appSpec.name}`) printInfo(`ARC standards supported: ${appSpec.arcs.join(', ')}`) printInfo('') printInfo('State schema:') printInfo(` Global: ${appSpec.state.schema.global.ints} ints, ${appSpec.state.schema.global.bytes} bytes`) printInfo(` Local: ${appSpec.state.schema.local.ints} ints, ${appSpec.state.schema.local.bytes} bytes`)
// Step 2: Demonstrate getGlobalABIStorageKeys() printStep(2, 'Get Global State Key Definitions')
printInfo('Using getGlobalABIStorageKeys() to get all global state keys:') printInfo('')
const globalKeys = getGlobalABIStorageKeys(appSpec) const globalKeyNames = Object.keys(globalKeys)
printInfo(`Found ${globalKeyNames.length} global state keys:`) printInfo('')
for (const name of globalKeyNames) { displayStorageKey(name, globalKeys[name]) printInfo('') }
// Step 3: Demonstrate getLocalABIStorageKeys() printStep(3, 'Get Local State Key Definitions')
printInfo('Using getLocalABIStorageKeys() to get all local state keys:') printInfo('')
const localKeys = getLocalABIStorageKeys(appSpec) const localKeyNames = Object.keys(localKeys)
printInfo(`Found ${localKeyNames.length} local state keys:`) printInfo('')
for (const name of localKeyNames) { displayStorageKey(name, localKeys[name]) printInfo('') }
// Step 4: Demonstrate getBoxABIStorageKeys() printStep(4, 'Get Box Key Definitions')
printInfo('Using getBoxABIStorageKeys() to get all box storage keys:') printInfo('')
const boxKeys = getBoxABIStorageKeys(appSpec) const boxKeyNames = Object.keys(boxKeys)
if (boxKeyNames.length === 0) { printInfo('No box storage keys defined in this contract.') } else { printInfo(`Found ${boxKeyNames.length} box storage keys:`) printInfo('') for (const name of boxKeyNames) { displayStorageKey(name, boxKeys[name]) printInfo('') } }
// Step 5: Demonstrate getBoxABIStorageMaps() printStep(5, 'Get Box Map Definitions')
printInfo('Using getBoxABIStorageMaps() to get box map definitions:') printInfo('')
const boxMaps = getBoxABIStorageMaps(appSpec) const boxMapNames = Object.keys(boxMaps)
if (boxMapNames.length === 0) { printInfo('No box maps defined in this contract.') } else { printInfo(`Found ${boxMapNames.length} box maps:`) printInfo('') for (const name of boxMapNames) { displayStorageMap(name, boxMaps[name]) printInfo('') } }
// Step 6: Deploy contract and read actual state values printStep(6, 'Deploy Contract and Read Actual State Values')
printInfo('Connecting to LocalNet and deploying State contract...') printInfo('')
const algorand = AlgorandClient.defaultLocalNet() const deployer = await algorand.account.kmd.getLocalNetDispenserAccount()
// Register the deployer's signer with AlgorandClient algorand.setSignerFromAccount(deployer)
// Create app factory from spec (use deployer as default sender with its signer) const factory = algorand.client.getAppFactory({ appSpec, defaultSender: deployer, })
// Deploy the app with template parameters const { result, appClient } = await factory.deploy({ deployTimeParams: { VALUE: 1 }, }) const appId = result.appId printInfo(`Contract deployed with App ID: ${appId}`) printInfo('')
// Set some global state values using the set_global method // Method signature: set_global(uint64,uint64,string,byte[4])void printInfo('Setting global state values...') await appClient.send.call({ method: 'set_global', args: [100n, 200n, 'hello', new Uint8Array([0x41, 0x42, 0x43, 0x44])], // int1, int2, bytes1, bytes2 }) printInfo('Global state values set successfully.') printInfo('')
// Read global state using app client printInfo('Reading global state and decoding using storage key types:') printInfo('')
const globalState = await appClient.getGlobalState()
for (const name of globalKeyNames) { const storageKey = globalKeys[name] const stateEntry = Object.values(globalState).find((entry) => entry.keyBase64 === storageKey.key)
if (stateEntry) { // State can be either uint64 (value is bigint) or bytes (value is string with valueRaw) const hasRawValue = 'valueRaw' in stateEntry printInfo(` ${name}:`) if (hasRawValue) { const rawValue = (stateEntry as { valueRaw: Uint8Array }).valueRaw printInfo(` Raw value: ${rawValue ? formatBytes(rawValue) : '(empty)'}`) printInfo(` Decoded (${formatKeyOrValueType(storageKey.valueType)}): ${decodeStateValue(storageKey, rawValue)}`) } else { // For uint64 values, the state stores value directly as bigint const value = (stateEntry as { value: bigint }).value printInfo(` Value (uint64): ${value}`) } } else { printInfo(` ${name}: (not set)`) } }
// Step 7: Opt-in and set local state printStep(7, 'Opt-in and Set Local State')
printInfo('Opting in to the contract...') try { await appClient.send.optIn({ method: 'opt_in' }) printInfo('Opted in successfully.') } catch (e) { if (String(e).includes('already opted in')) { printInfo('Already opted in - skipping.') } else { throw e } } printInfo('')
// Method signature: set_local(uint64,uint64,string,byte[4])void printInfo('Setting local state values...') await appClient.send.call({ method: 'set_local', args: [10n, 20n, 'local', new Uint8Array([0x4c, 0x4f, 0x43, 0x41])], // int1, int2, bytes1, bytes2 }) printInfo('Local state values set successfully.') printInfo('')
// Read local state printInfo('Reading local state and decoding using storage key types:') printInfo('')
const localState = await appClient.getLocalState(deployer.addr)
for (const name of localKeyNames) { const storageKey = localKeys[name] const stateEntry = Object.values(localState).find((entry) => entry.keyBase64 === storageKey.key)
if (stateEntry) { // State can be either uint64 (value is bigint) or bytes (value is string with valueRaw) const hasRawValue = 'valueRaw' in stateEntry printInfo(` ${name}:`) if (hasRawValue) { const rawValue = (stateEntry as { valueRaw: Uint8Array }).valueRaw printInfo(` Raw value: ${rawValue ? formatBytes(rawValue) : '(empty)'}`) printInfo(` Decoded (${formatKeyOrValueType(storageKey.valueType)}): ${decodeStateValue(storageKey, rawValue)}`) } else { // For uint64 values, the state stores value directly as bigint const value = (stateEntry as { value: bigint }).value printInfo(` Value (uint64): ${value}`) } } else { printInfo(` ${name}: (not set)`) } }
// Step 8: Set and read box storage map printStep(8, 'Set and Read Box Storage Map')
if (boxMapNames.length > 0) { try { printInfo('Setting a box map entry...')
// Fund the app for minimum balance requirement for box storage await algorand.send.payment({ sender: deployer, receiver: appClient.appAddress, amount: algo(1), // 1 ALGO for box storage minimum balance })
// Set a box using the set_box method // Method signature: set_box(byte[4],string)void await appClient.send.call({ method: 'set_box', args: [new Uint8Array([0x62, 0x6f, 0x78, 0x31]), 'Box content here!'], // name, value boxReferences: [{ appId: 0n, name: new Uint8Array([0x62, 0x6f, 0x78, 0x31]) }], }) printInfo('Box value set successfully.') printInfo('')
// Read the box value const boxMap = boxMaps[boxMapNames[0]] printInfo(`Reading box map '${boxMapNames[0]}':`) printInfo(` Key type: ${formatKeyOrValueType(boxMap.keyType)}`) printInfo(` Value type: ${formatKeyOrValueType(boxMap.valueType)}`)
const boxValue = await appClient.getBoxValue(new Uint8Array([0x62, 0x6f, 0x78, 0x31])) printInfo(` Raw box value: ${formatBytes(boxValue)}`)
// Decode the box value using the valueType from the storage map const valueType = boxMap.valueType if (typeof valueType !== 'string') { const decoded = valueType.decode(boxValue) printInfo(` Decoded value: "${decoded}"`) } } catch (e) { printInfo(`Box storage demo skipped due to error: ${e instanceof Error ? e.message : String(e)}`) printInfo('(This can happen if LocalNet state is stale - try running "algokit localnet reset")') } } else { printInfo('No box maps to demonstrate.') }
// Step 9: Summary printStep(9, 'Summary')
printInfo('ARC-56 Storage Helper Functions:') printInfo('') printInfo('Storage Key Functions:') printInfo(' getGlobalABIStorageKeys(contract) - Get all global state keys') printInfo(' getLocalABIStorageKeys(contract) - Get all local state keys') printInfo(' getBoxABIStorageKeys(contract) - Get all box storage keys') printInfo('') printInfo('Storage Map Functions:') printInfo(' getBoxABIStorageMaps(contract) - Get all box maps') printInfo('') printInfo('ABIStorageKey Properties:') printInfo(' key - Base64-encoded key bytes') printInfo(' keyType - Type of the key (ABIType or AVMType)') printInfo(' valueType - Type of the value (ABIType or AVMType)') printInfo(' desc - Optional description') printInfo('') printInfo('ABIStorageMap Properties:') printInfo(' keyType - Type of keys in the map') printInfo(' valueType - Type of values in the map') printInfo(' desc - Optional description') printInfo(' prefix - Base64-encoded prefix for map keys') printInfo('') printInfo('Use Cases:') printInfo(' - Inspect contract state schema from ARC-56 spec') printInfo(' - Decode raw state bytes using typed definitions') printInfo(' - Build generic contract explorers/tools') printInfo(' - Validate state key/value types at runtime')
printSuccess('ARC-56 Storage Helpers example completed successfully!')}
main().catch((error) => { console.error('Error:', error) process.exit(1)})