Skip to content
Algorand Developer Portal

ABI Static Array Type

← Back to ABI Encoding

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
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example abi/05-static-array.ts

View source on GitHub

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();