AVM Type Encoding
Description
Section titled “Description”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
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example abi/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();