Skip to content

AVM Type Encoding

← Back to ABI Encoding

This example demonstrates how to work with AVM-specific types:

  • AVMBytes: Raw byte arrays (no length prefix, unlike ABI string/bytes)
  • AVMString: UTF-8 strings (no length prefix, unlike ABI string)
  • AVMUint64: 64-bit unsigned integers (8 bytes, big-endian) AVM types represent how data is stored natively on the AVM stack, while ABI types follow the ARC-4 encoding specification with length prefixes. Key functions:
  • getABIEncodedValue(avmType, value): Encode a value (works with both AVM and ABI types)
  • decodeAVMValue(avmType, bytes): Decode AVM bytes back to a value
  • isAVMType(type): Check if a type string is an AVM type
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example abi/12-avm-types.ts

View source on GitHub

12-avm-types.ts
/**
* Example: AVM Type Encoding
*
* This example demonstrates how to work with AVM-specific types:
* - AVMBytes: Raw byte arrays (no length prefix, unlike ABI string/bytes)
* - AVMString: UTF-8 strings (no length prefix, unlike ABI string)
* - AVMUint64: 64-bit unsigned integers (8 bytes, big-endian)
*
* AVM types represent how data is stored natively on the AVM stack,
* while ABI types follow the ARC-4 encoding specification with length prefixes.
*
* Key functions:
* - getABIEncodedValue(avmType, value): Encode a value (works with both AVM and ABI types)
* - decodeAVMValue(avmType, bytes): Decode AVM bytes back to a value
* - isAVMType(type): Check if a type string is an AVM type
*
* Prerequisites:
* - No LocalNet required
*/
import type { AVMBytes, AVMString, AVMType, AVMUint64 } from '@algorandfoundation/algokit-utils/abi'
import { ABIType, decodeAVMValue, getABIEncodedValue, isAVMType } from '@algorandfoundation/algokit-utils/abi'
import { formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
/**
* Encode a value to raw AVM representation (as conceptualized in ARC-56).
*
* ARC-56 defines AVM types as having NO length prefixes:
* - AVMString: Raw UTF-8 bytes (no 2-byte length prefix)
* - AVMBytes: Raw bytes as-is
* - AVMUint64: 8-byte big-endian encoding
*
* Note: The library's getABIEncodedValue() uses ABI-style encoding for AVMString
* (with length prefix). This helper demonstrates the conceptual raw encoding.
*/
function encodeAVMValueRaw(avmType: AVMType, value: string | Uint8Array | bigint | number): Uint8Array {
switch (avmType) {
case 'AVMString':
// Raw UTF-8 bytes - no length prefix
return new TextEncoder().encode(value as string)
case 'AVMBytes':
// Raw bytes as-is
if (typeof value === 'string') return new TextEncoder().encode(value)
return value as Uint8Array
case 'AVMUint64':
// 8-byte big-endian encoding
return ABIType.from('uint64').encode(value as bigint | number)
}
}
function main() {
printHeader('AVM Type Encoding Example')
// Step 1: Introduction to AVM Types
printStep(1, 'Introduction to AVM Types')
printInfo('AVM types represent native Algorand Virtual Machine stack values.')
printInfo('')
printInfo('Three AVM types:')
printInfo(' AVMBytes - Raw byte array (no length prefix)')
printInfo(' AVMString - UTF-8 string (no length prefix)')
printInfo(' AVMUint64 - 64-bit unsigned integer (8 bytes, big-endian)')
printInfo('')
printInfo('Key difference from ABI types:')
printInfo(' ABI string/bytes: 2-byte length prefix + data')
printInfo(' AVM string/bytes: Raw data only (no prefix)')
// Step 2: AVMBytes type
printStep(2, 'AVMBytes - Raw Byte Arrays')
const avmBytesType: AVMBytes = 'AVMBytes'
printInfo(`Type: ${avmBytesType}`)
printInfo(`isAVMType("AVMBytes"): ${isAVMType('AVMBytes')}`)
printInfo('')
// Encode raw bytes
const rawBytes = new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]) // "Hello" in ASCII
const avmBytesEncoded = encodeAVMValueRaw('AVMBytes', rawBytes)
printInfo('Encoding raw bytes [0x48, 0x65, 0x6c, 0x6c, 0x6f] ("Hello"):')
printInfo(` Input bytes: ${formatHex(rawBytes)}`)
printInfo(` Encoded: ${formatHex(avmBytesEncoded)}`)
printInfo(` Length: ${avmBytesEncoded.length} bytes`)
printInfo('')
// Decode back
const avmBytesDecoded = decodeAVMValue('AVMBytes', avmBytesEncoded)
printInfo('Decoding AVMBytes:')
printInfo(` Result type: ${avmBytesDecoded instanceof Uint8Array ? 'Uint8Array' : typeof avmBytesDecoded}`)
printInfo(` Result: ${formatHex(avmBytesDecoded as Uint8Array)}`)
// Step 3: AVMString type
printStep(3, 'AVMString - UTF-8 Strings (No Length Prefix)')
const avmStringType: AVMString = 'AVMString'
printInfo(`Type: ${avmStringType}`)
printInfo(`isAVMType("AVMString"): ${isAVMType('AVMString')}`)
printInfo('')
// Encode various strings
const testStrings = ['Hello', 'World!', 'Algorand']
for (const str of testStrings) {
const encoded = encodeAVMValueRaw('AVMString', str)
printInfo(`"${str}":`)
printInfo(` Encoded: ${formatHex(encoded)}`)
printInfo(` Length: ${encoded.length} bytes`)
}
printInfo('')
// Decode back
const encodedHello = encodeAVMValueRaw('AVMString', 'Hello')
const decodedHello = decodeAVMValue('AVMString', encodedHello)
printInfo('Decoding AVMString:')
printInfo(` Input: ${formatHex(encodedHello)}`)
printInfo(` Result: "${decodedHello}"`)
printInfo(` Result type: ${typeof decodedHello}`)
// Step 4: AVMUint64 type
printStep(4, 'AVMUint64 - 64-bit Unsigned Integers')
const avmUint64Type: AVMUint64 = 'AVMUint64'
printInfo(`Type: ${avmUint64Type}`)
printInfo(`isAVMType("AVMUint64"): ${isAVMType('AVMUint64')}`)
printInfo('')
// Encode various uint64 values
const testNumbers: bigint[] = [0n, 1n, 255n, 1000n, 1000000n, 2n ** 32n - 1n, 2n ** 64n - 1n]
printInfo('Encoding uint64 values (8-byte big-endian):')
for (const num of testNumbers) {
const encoded = encodeAVMValueRaw('AVMUint64', num)
printInfo(` ${num.toString().padStart(20)}: ${formatHex(encoded)}`)
}
printInfo('')
// Decode back
const encoded1000 = encodeAVMValueRaw('AVMUint64', 1000n)
const decoded1000 = decodeAVMValue('AVMUint64', encoded1000)
printInfo('Decoding AVMUint64:')
printInfo(` Input: ${formatHex(encoded1000)}`)
printInfo(` Result: ${decoded1000}`)
printInfo(` Result type: ${typeof decoded1000}`)
// Step 5: Compare AVM encoding vs ABI encoding
printStep(5, 'AVM Encoding vs ABI Encoding Comparison')
printInfo('Comparing how the same values encode differently:')
printInfo('')
// String comparison
const testString = 'Hello'
const avmStringEncoded = encodeAVMValueRaw('AVMString', testString)
const abiStringEncoded = ABIType.from('string').encode(testString)
printInfo(`String: "${testString}"`)
printInfo(` AVM encoding: ${formatHex(avmStringEncoded)}`)
printInfo(` Length: ${avmStringEncoded.length} bytes (raw UTF-8 only)`)
printInfo(` ABI encoding: ${formatHex(abiStringEncoded)}`)
printInfo(` Length: ${abiStringEncoded.length} bytes (2-byte prefix + UTF-8)`)
printInfo(` First 2 bytes: ${formatHex(abiStringEncoded.slice(0, 2))} = ${(abiStringEncoded[0] << 8) | abiStringEncoded[1]} (length)`)
printInfo('')
// Bytes comparison
const testBytesArray = new Uint8Array([0xde, 0xad, 0xbe, 0xef])
const avmBytesEncodedCmp = encodeAVMValueRaw('AVMBytes', testBytesArray)
// ABI has no "bytes" type - closest is byte[] or string; we'll use static byte[4]
const abiByte4Encoded = ABIType.from('byte[4]').encode(Array.from(testBytesArray))
printInfo(`Bytes: [0xde, 0xad, 0xbe, 0xef]`)
printInfo(` AVMBytes encoding: ${formatHex(avmBytesEncodedCmp)}`)
printInfo(` Length: ${avmBytesEncodedCmp.length} bytes (raw bytes only)`)
printInfo(` ABI byte[4] encoding: ${formatHex(abiByte4Encoded)}`)
printInfo(` Length: ${abiByte4Encoded.length} bytes (static array, no prefix)`)
printInfo('')
// Uint64 comparison
const testUint64 = 1000n
const avmUint64Encoded = encodeAVMValueRaw('AVMUint64', testUint64)
const abiUint64Encoded = ABIType.from('uint64').encode(testUint64)
printInfo(`Uint64: ${testUint64}`)
printInfo(` AVM encoding: ${formatHex(avmUint64Encoded)}`)
printInfo(` Length: ${avmUint64Encoded.length} bytes`)
printInfo(` ABI encoding: ${formatHex(abiUint64Encoded)}`)
printInfo(` Length: ${abiUint64Encoded.length} bytes`)
printInfo(` Match: ${Buffer.from(avmUint64Encoded).toString('hex') === Buffer.from(abiUint64Encoded).toString('hex')}`)
printInfo(' (uint64 encoding is identical - both are 8 bytes big-endian)')
// Step 6: isAVMType helper function
printStep(6, 'isAVMType Helper Function')
printInfo('Use isAVMType() to check if a type string is an AVM type:')
printInfo('')
const typeChecks = [
'AVMBytes',
'AVMString',
'AVMUint64',
'uint64',
'string',
'address',
'byte[]',
'(uint64,bool)',
]
for (const type of typeChecks) {
const isAvm = isAVMType(type)
printInfo(` isAVMType("${type}"): ${isAvm}`)
}
// Step 6b: getABIEncodedValue - unified encoding function
printStep(7, 'getABIEncodedValue - Unified Encoding Function')
printInfo('getABIEncodedValue() handles both AVM types and ABI types:')
printInfo('')
// Demonstrate with AVM types
const avmStringBytes = getABIEncodedValue('AVMString', 'Test')
printInfo(`getABIEncodedValue('AVMString', 'Test'): ${formatHex(avmStringBytes)}`)
const avmUint64Bytes = getABIEncodedValue('AVMUint64', 100n)
printInfo(`getABIEncodedValue('AVMUint64', 100n): ${formatHex(avmUint64Bytes)}`)
// Demonstrate with ABI types
const abiUint64Type = ABIType.from('uint64')
const abiUint64Bytes = getABIEncodedValue(abiUint64Type, 100n)
printInfo(`getABIEncodedValue(ABIType.from('uint64'), 100n): ${formatHex(abiUint64Bytes)}`)
printInfo('')
printInfo('This function is useful when you have a mixed type that could be either.')
printInfo('Internally it uses isAVMType() to determine the encoding strategy.')
// Step 8: When to use AVM types vs ABI types
printStep(8, 'When to Use AVM Types vs ABI Types')
printInfo('Use AVM types when:')
printInfo(' - Working with raw AVM stack values (global/local state, box storage)')
printInfo(' - Reading/writing app state where values have no length prefix')
printInfo(' - The ARC-56 spec specifies AVMBytes, AVMString, or AVMUint64')
printInfo('')
printInfo('Use ABI types when:')
printInfo(' - Encoding method arguments for ARC-4 ABI method calls')
printInfo(' - Encoding method return values following ARC-4 specification')
printInfo(' - The type is an ARC-4 type like "uint64", "string", "address"')
printInfo('')
printInfo('Example scenarios:')
printInfo(' - App global state value stored as uint64 -> AVMUint64')
printInfo(' - App global state key stored as string -> AVMString')
printInfo(' - Box content stored as raw bytes -> AVMBytes')
printInfo(' - ABI method arg of type "string" -> ABIStringType (with length prefix)')
printInfo(' - ABI method return of type "uint64" -> ABIUintType (same encoding)')
// Step 9: Practical example - Encoding for app state
printStep(9, 'Practical Example - Encoding for App State')
printInfo('Simulating app state encoding (as seen in ARC-56 contracts):')
printInfo('')
// Simulate a key-value pair in global state
const stateKey = 'counter'
const stateValue = 42n
// Keys are typically AVMString (no length prefix in state key)
const encodedKey = encodeAVMValueRaw('AVMString', stateKey)
// Values can be AVMUint64 for integer values
const encodedValue = encodeAVMValueRaw('AVMUint64', stateValue)
printInfo('Global state entry:')
printInfo(` Key: "${stateKey}"`)
printInfo(` Key encoded (AVMString): ${formatHex(encodedKey)}`)
printInfo(` Value: ${stateValue}`)
printInfo(` Value encoded (AVMUint64): ${formatHex(encodedValue)}`)
printInfo('')
// Decode back
const decodedKey = decodeAVMValue('AVMString', encodedKey)
const decodedValue = decodeAVMValue('AVMUint64', encodedValue)
printInfo('Decoding back:')
printInfo(` Key: "${decodedKey}"`)
printInfo(` Value: ${decodedValue}`)
// Step 10: Round-trip verification
printStep(10, 'Round-Trip Verification')
printInfo('Verifying encode/decode round-trips preserve values:')
printInfo('')
// AVMString round-trip
const originalString = 'AlgorandFoundation'
const encString = encodeAVMValueRaw('AVMString', originalString)
const decString = decodeAVMValue('AVMString', encString)
const matchString = originalString === decString
printInfo(`AVMString "${originalString}": ${matchString ? 'PASS' : 'FAIL'}`)
// AVMBytes round-trip
const originalBytes = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8])
const encBytes = encodeAVMValueRaw('AVMBytes', originalBytes)
const decBytes = decodeAVMValue('AVMBytes', encBytes) as Uint8Array
const matchBytes = originalBytes.length === decBytes.length && originalBytes.every((b, i) => b === decBytes[i])
printInfo(`AVMBytes [1,2,3,4,5,6,7,8]: ${matchBytes ? 'PASS' : 'FAIL'}`)
// AVMUint64 round-trip
const originalUint64 = 9007199254740991n // Max safe integer
const encUint64 = encodeAVMValueRaw('AVMUint64', originalUint64)
const decUint64 = decodeAVMValue('AVMUint64', encUint64) as bigint
const matchUint64 = originalUint64 === decUint64
printInfo(`AVMUint64 ${originalUint64}: ${matchUint64 ? 'PASS' : 'FAIL'}`)
// Step 11: Summary
printStep(11, 'Summary')
printInfo('AVM Type Summary:')
printInfo('')
printInfo('Types:')
printInfo(' AVMBytes - Raw bytes, no length prefix')
printInfo(' AVMString - UTF-8 string, no length prefix')
printInfo(' AVMUint64 - 8-byte big-endian unsigned integer')
printInfo('')
printInfo('Functions:')
printInfo(' decodeAVMValue(type, bytes) - Decode AVM bytes to value')
printInfo(' getABIEncodedValue(type, value) - Encode (works with AVM and ABI types)')
printInfo(' isAVMType(type) - Check if type is an AVM type')
printInfo('')
printInfo('Key Differences from ABI:')
printInfo(' - AVMString has no 2-byte length prefix (ABI string does)')
printInfo(' - AVMBytes is raw (ABI uses length-prefixed byte arrays)')
printInfo(' - AVMUint64 encoding is identical to ABI uint64')
printInfo('')
printInfo('Use Cases:')
printInfo(' - AVM types: App state, box storage, raw stack values')
printInfo(' - ABI types: Method calls, ARC-4 encoded arguments/returns')
printSuccess('AVM Type Encoding example completed successfully!')
}
main()