Skip to content

ARC-56 Storage Helpers

← Back to ABI Encoding

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.
  • LocalNet running (via algokit localnet start)

From the repository root:

Terminal window
cd examples
npm run example abi/15-arc56-storage.ts

View source on GitHub

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 mode
declare 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)
})