ABI Static Array Type
Description
Section titled “Description”This example demonstrates how to encode and decode fixed-length arrays using ABIArrayStaticType:
- byte[32]: Fixed 32 bytes, common for hashes and cryptographic data
- uint64[3]: Fixed array of 3 unsigned 64-bit integers
- address[2]: Fixed array of 2 Algorand addresses Key characteristics of static arrays:
- Fixed length known at compile time
- No length prefix in encoding (unlike dynamic arrays)
- Elements are encoded consecutively
- Encoded length = elementSize * arrayLength
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example abi/05-static-array.ts/** * Example: ABI Static Array Type * * This example demonstrates how to encode and decode fixed-length arrays using ABIArrayStaticType: * - byte[32]: Fixed 32 bytes, common for hashes and cryptographic data * - uint64[3]: Fixed array of 3 unsigned 64-bit integers * - address[2]: Fixed array of 2 Algorand addresses * * Key characteristics of static arrays: * - Fixed length known at compile time * - No length prefix in encoding (unlike dynamic arrays) * - Elements are encoded consecutively * - Encoded length = elementSize * arrayLength * * Prerequisites: * - No LocalNet required */
import { Address } from '@algorandfoundation/algokit-utils'import { ABIAddressType, ABIArrayStaticType, ABIByteType, ABIType, ABIUintType } from '@algorandfoundation/algokit-utils/abi'import { formatBytes, formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
function main() { printHeader('ABI Static Array Type Example')
// Step 1: Create ABIArrayStaticType and inspect properties printStep(1, 'ABIArrayStaticType Properties')
const byte32Type = ABIType.from('byte[32]') as ABIArrayStaticType const uint64x3Type = ABIType.from('uint64[3]') as ABIArrayStaticType const address2Type = ABIType.from('address[2]') as ABIArrayStaticType
printInfo('byte[32]:') printInfo(` toString(): ${byte32Type.toString()}`) printInfo(` childType: ${byte32Type.childType.toString()}`) printInfo(` length: ${byte32Type.length}`) printInfo(` byteLen(): ${byte32Type.byteLen()}`) printInfo(` isDynamic(): ${byte32Type.isDynamic()}`)
printInfo('\nuint64[3]:') printInfo(` toString(): ${uint64x3Type.toString()}`) printInfo(` childType: ${uint64x3Type.childType.toString()}`) printInfo(` length: ${uint64x3Type.length}`) printInfo(` byteLen(): ${uint64x3Type.byteLen()}`) printInfo(` isDynamic(): ${uint64x3Type.isDynamic()}`)
printInfo('\naddress[2]:') printInfo(` toString(): ${address2Type.toString()}`) printInfo(` childType: ${address2Type.childType.toString()}`) printInfo(` length: ${address2Type.length}`) printInfo(` byteLen(): ${address2Type.byteLen()}`) printInfo(` isDynamic(): ${address2Type.isDynamic()}`)
// Step 2: byte[32] encoding - common for hashes printStep(2, 'byte[32] Encoding - Common for Hashes')
// Simulate a SHA-256 hash (32 bytes) const hashBytes = new Uint8Array(32) for (let i = 0; i < 32; i++) { hashBytes[i] = i * 8 // 0x00, 0x08, 0x10, 0x18, ... }
// Encode as array of individual byte values const hashValues: number[] = Array.from(hashBytes)
const hashEncoded = byte32Type.encode(hashValues) printInfo(`Input: array of 32 byte values`) printInfo(` Values: [${hashValues.slice(0, 8).join(', ')}, ...]`) printInfo(`Encoded: ${formatHex(hashEncoded)}`) printInfo(`Encoded length: ${hashEncoded.length} bytes`)
// Verify encoded length = elementSize * arrayLength const byteSize = new ABIByteType().byteLen() const expectedByte32Len = byteSize * 32 printInfo(`Expected length (1 byte * 32): ${expectedByte32Len} bytes`) printInfo(`Length matches: ${hashEncoded.length === expectedByte32Len}`)
// Decode back const hashDecoded = byte32Type.decode(hashEncoded) as number[] printInfo(`Decoded: [${hashDecoded.slice(0, 8).join(', ')}, ...]`) printInfo(`Round-trip verified: ${hashDecoded.every((v, i) => v === hashValues[i])}`)
// Step 3: uint64[3] encoding - fixed array of integers printStep(3, 'uint64[3] Encoding - Fixed Array of Integers')
const uint64Values = [1000n, 2000n, 3000n]
const uint64x3Encoded = uint64x3Type.encode(uint64Values) printInfo(`Input: [${uint64Values.join(', ')}]`) printInfo(`Encoded: ${formatHex(uint64x3Encoded)}`) printInfo(`Encoded length: ${uint64x3Encoded.length} bytes`)
// Verify encoded length = elementSize * arrayLength const uint64Size = new ABIUintType(64).byteLen() const expectedUint64x3Len = uint64Size * 3 printInfo(`Expected length (8 bytes * 3): ${expectedUint64x3Len} bytes`) printInfo(`Length matches: ${uint64x3Encoded.length === expectedUint64x3Len}`)
// Decode back const uint64Decoded = uint64x3Type.decode(uint64x3Encoded) as bigint[] printInfo(`Decoded: [${uint64Decoded.join(', ')}]`) printInfo(`Round-trip verified: ${uint64Decoded.every((v, i) => v === uint64Values[i])}`)
// Step 4: address[2] encoding - fixed array of addresses printStep(4, 'address[2] Encoding - Fixed Array of Addresses')
// Create two sample addresses from public keys const pubKey1 = new Uint8Array(32).fill(0xaa) const pubKey2 = new Uint8Array(32).fill(0xbb)
const addr1 = new Address(pubKey1) const addr2 = new Address(pubKey2)
const addressValues = [addr1.toString(), addr2.toString()]
const address2Encoded = address2Type.encode(addressValues) printInfo(`Input addresses:`) printInfo(` [0]: ${addressValues[0]}`) printInfo(` [1]: ${addressValues[1]}`) printInfo(`Encoded: ${formatBytes(address2Encoded, 16)}`) printInfo(`Encoded length: ${address2Encoded.length} bytes`)
// Verify encoded length = elementSize * arrayLength const addressSize = new ABIAddressType().byteLen() const expectedAddress2Len = addressSize * 2 printInfo(`Expected length (32 bytes * 2): ${expectedAddress2Len} bytes`) printInfo(`Length matches: ${address2Encoded.length === expectedAddress2Len}`)
// Decode back const addressDecoded = address2Type.decode(address2Encoded) as string[] printInfo(`Decoded:`) printInfo(` [0]: ${addressDecoded[0]}`) printInfo(` [1]: ${addressDecoded[1]}`) printInfo(`Round-trip verified: ${addressDecoded.every((v, i) => v === addressValues[i])}`)
// Step 5: Demonstrate no length prefix printStep(5, 'Static Arrays Have No Length Prefix')
printInfo('Static arrays encode directly WITHOUT a length prefix:') printInfo(' - The length is known from the type definition') printInfo(' - All bytes are element data, none for length')
// Show contrast with what a dynamic array would look like const singleUint64 = new ABIUintType(64) const value1000 = singleUint64.encode(1000n) const value2000 = singleUint64.encode(2000n) const value3000 = singleUint64.encode(3000n)
printInfo('\nCompare single uint64 encodings:') printInfo(` 1000n: ${formatHex(value1000)} (8 bytes)`) printInfo(` 2000n: ${formatHex(value2000)} (8 bytes)`) printInfo(` 3000n: ${formatHex(value3000)} (8 bytes)`)
printInfo('\nuint64[3] encoding is just these concatenated (no prefix):') printInfo(` ${formatHex(uint64x3Encoded)} (24 bytes)`)
// Verify the encoding is just concatenated elements const concatenated = new Uint8Array([...value1000, ...value2000, ...value3000]) const matchesConcatenated = uint64x3Encoded.every((v, i) => v === concatenated[i]) printInfo(`Matches concatenation: ${matchesConcatenated}`)
// Step 6: Elements encoded consecutively printStep(6, 'Elements Are Encoded Consecutively')
printInfo('Each element occupies a fixed position:') printInfo(` Element 0: bytes 0-7 (${formatHex(uint64x3Encoded.slice(0, 8))})`) printInfo(` Element 1: bytes 8-15 (${formatHex(uint64x3Encoded.slice(8, 16))})`) printInfo(` Element 2: bytes 16-23 (${formatHex(uint64x3Encoded.slice(16, 24))})`)
printInfo('\nFor address[2]:') printInfo(` Element 0: bytes 0-31 (first 32 bytes = address 1)`) printInfo(` Element 1: bytes 32-63 (next 32 bytes = address 2)`)
// Extract individual elements from the encoded address array const extractedAddr1 = new ABIAddressType().decode(address2Encoded.slice(0, 32)) const extractedAddr2 = new ABIAddressType().decode(address2Encoded.slice(32, 64))
printInfo(`\nExtracted from encoded bytes:`) printInfo(` bytes[0:32] decoded: ${extractedAddr1}`) printInfo(` bytes[32:64] decoded: ${extractedAddr2}`) printInfo(` Matches originals: ${extractedAddr1 === addressValues[0] && extractedAddr2 === addressValues[1]}`)
// Step 7: Verify encoded length formula printStep(7, 'Encoded Length Formula: elementSize * arrayLength')
// Note: bool arrays have special packing - 8 bools fit in 1 byte // So we exclude bool from the simple formula test const testCases = [ { type: 'byte[16]', elementSize: 1, arrayLength: 16 }, { type: 'byte[32]', elementSize: 1, arrayLength: 32 }, { type: 'byte[64]', elementSize: 1, arrayLength: 64 }, { type: 'uint8[10]', elementSize: 1, arrayLength: 10 }, { type: 'uint16[5]', elementSize: 2, arrayLength: 5 }, { type: 'uint32[4]', elementSize: 4, arrayLength: 4 }, { type: 'uint64[3]', elementSize: 8, arrayLength: 3 }, { type: 'uint128[2]', elementSize: 16, arrayLength: 2 }, { type: 'uint256[2]', elementSize: 32, arrayLength: 2 }, { type: 'address[3]', elementSize: 32, arrayLength: 3 }, ]
printInfo('Type | Element Size | Array Length | Expected | Actual') printInfo('--------------|--------------|--------------|----------|-------')
for (const tc of testCases) { const parsedType = ABIType.from(tc.type) as ABIArrayStaticType const expectedLen = tc.elementSize * tc.arrayLength const actualLen = parsedType.byteLen() const match = expectedLen === actualLen ? 'OK' : 'MISMATCH'
printInfo( `${tc.type.padEnd(13)} | ${String(tc.elementSize).padEnd(12)} | ${String(tc.arrayLength).padEnd(12)} | ${String(expectedLen).padEnd(8)} | ${actualLen} ${match}` ) }
// Step 8: Creating ABIArrayStaticType programmatically printStep(8, 'Creating ABIArrayStaticType Programmatically')
// You can also create static array types directly with new const customArrayType = new ABIArrayStaticType(new ABIUintType(32), 5)
printInfo('Created with: new ABIArrayStaticType(new ABIUintType(32), 5)') printInfo(` toString(): ${customArrayType.toString()}`) printInfo(` childType: ${customArrayType.childType.toString()}`) printInfo(` length: ${customArrayType.length}`) printInfo(` byteLen(): ${customArrayType.byteLen()}`)
// Encode and decode with custom type // Note: encode accepts numbers or bigints, decode returns bigints for uint types const customValues = [100, 200, 300, 400, 500] const customEncoded = customArrayType.encode(customValues) const customDecoded = customArrayType.decode(customEncoded) as bigint[]
printInfo(`\nEncode [${customValues.join(', ')}]:`) printInfo(` Encoded: ${formatHex(customEncoded)}`) printInfo(` Decoded: [${customDecoded.join(', ')}]`) printInfo(` Round-trip verified: ${customDecoded.every((v, i) => BigInt(v) === BigInt(customValues[i]))}`)
// Step 9: Summary printStep(9, 'Summary')
printInfo('ABIArrayStaticType key properties:') printInfo(' - childType: The type of each element') printInfo(' - length: Fixed number of elements') printInfo(' - byteLen(): Returns elementSize * length') printInfo(' - isDynamic(): Always returns false')
printInfo('\nStatic array encoding characteristics:') printInfo(' - No length prefix (length is in the type)') printInfo(' - Elements encoded consecutively') printInfo(' - Fixed encoded size = elementSize * arrayLength') printInfo(' - Common uses: byte[32] for hashes, address[N] for multi-sig')
printInfo('\nCreating static array types:') printInfo(' - ABIType.from("byte[32]") - parse from string') printInfo(' - new ABIArrayStaticType(childType, length) - programmatic')
printSuccess('ABI Static Array Type example completed successfully!')}
main()