ABI Struct Type
Description
Section titled “Description”This example demonstrates how to encode and decode named structs using ABIStructType:
- Creating ABIStructType with named fields
- Encoding struct values as objects with named keys
- Comparing struct encoding to equivalent tuple encoding
- Accessing struct field names and types
- Decoding back to struct values with named fields Key characteristics of struct encoding:
- Structs are named tuples - the encoding is identical to the equivalent tuple
- Field names provide semantic meaning but don’t affect the binary encoding
- Decoded values are objects with named properties (not arrays like tuples) ARC-4 specification: Structs are tuples with named fields for improved readability.
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example abi/08-struct-type.ts/** * Example: ABI Struct Type * * This example demonstrates how to encode and decode named structs using ABIStructType: * - Creating ABIStructType with named fields * - Encoding struct values as objects with named keys * - Comparing struct encoding to equivalent tuple encoding * - Accessing struct field names and types * - Decoding back to struct values with named fields * * Key characteristics of struct encoding: * - Structs are named tuples - the encoding is identical to the equivalent tuple * - Field names provide semantic meaning but don't affect the binary encoding * - Decoded values are objects with named properties (not arrays like tuples) * * ARC-4 specification: Structs are tuples with named fields for improved readability. * * Prerequisites: * - No LocalNet required */
import { ABIBoolType, ABIStructType, ABITupleType, ABIType, ABIUintType,} from '@algorandfoundation/algokit-utils/abi'import { formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
function main() { printHeader('ABI Struct Type Example')
// Step 1: Creating ABIStructType with named fields printStep(1, 'Creating ABIStructType with Named Fields')
// Create a struct: { name: string, age: uint64, active: bool } const userStruct = ABIStructType.fromStruct('User', { User: [ { name: 'name', type: 'string' }, { name: 'age', type: 'uint64' }, { name: 'active', type: 'bool' }, ], })
printInfo('Created struct using ABIStructType.fromStruct():') printInfo(` Struct name: ${userStruct.structName}`) printInfo(` Display name: ${userStruct.displayName}`) printInfo(` ABI type name: ${userStruct.name}`) printInfo(` Number of fields: ${userStruct.structFields.length}`) printInfo(` isDynamic(): ${userStruct.isDynamic()} (because string is dynamic)`)
printInfo('\nStruct fields:') userStruct.structFields.forEach((field, i) => { const fieldType = field.type as ABIType printInfo(` [${i}] ${field.name}: ${fieldType.toString()}`) })
// Step 2: Encoding struct values as objects with named keys printStep(2, 'Encoding Struct Values as Objects')
const userValue = { name: 'Alice', age: 30n, active: true, }
const userEncoded = userStruct.encode(userValue)
printInfo(`Input object: { name: "${userValue.name}", age: ${userValue.age}, active: ${userValue.active} }`) printInfo(`Encoded: ${formatHex(userEncoded)}`) printInfo(`Total bytes: ${userEncoded.length}`)
// Break down the encoding printInfo('\nByte layout (head/tail encoding because string is dynamic):') printInfo('HEAD SECTION:')
// string is dynamic - 2-byte offset const nameOffset = (userEncoded[0] << 8) | userEncoded[1] printInfo(` [0-1] name offset: ${formatHex(userEncoded.slice(0, 2))} = ${nameOffset} (points to tail)`)
// uint64 is static - 8 bytes printInfo(` [2-9] age (uint64): ${formatHex(userEncoded.slice(2, 10))} = ${userValue.age}`)
// bool is static - 1 byte printInfo(` [10] active (bool): ${formatHex(userEncoded.slice(10, 11))} = ${userValue.active}`)
printInfo('\nTAIL SECTION:') const nameLenBytes = userEncoded.slice(nameOffset, nameOffset + 2) const nameLen = (nameLenBytes[0] << 8) | nameLenBytes[1] const nameContent = userEncoded.slice(nameOffset + 2, nameOffset + 2 + nameLen) printInfo(` [${nameOffset}-${nameOffset + 1}] string length: ${formatHex(nameLenBytes)} = ${nameLen} bytes`) printInfo(` [${nameOffset + 2}-${nameOffset + 1 + nameLen}] string content: ${formatHex(nameContent)} = "${userValue.name}"`)
// Step 3: Struct encoding is identical to equivalent tuple encoding printStep(3, 'Struct Encoding vs Tuple Encoding')
// Create equivalent tuple type const equivalentTuple = ABIType.from('(string,uint64,bool)') as ABITupleType const tupleValue: [string, bigint, boolean] = ['Alice', 30n, true] const tupleEncoded = equivalentTuple.encode(tupleValue)
printInfo(`Struct type: ${userStruct.name}`) printInfo(`Tuple type: ${equivalentTuple.name}`)
printInfo('\nStruct encoded:') printInfo(` ${formatHex(userEncoded)}`)
printInfo('\nTuple encoded (same values):') printInfo(` ${formatHex(tupleEncoded)}`)
// Compare byte by byte const encodingsMatch = userEncoded.length === tupleEncoded.length && userEncoded.every((byte, i) => byte === tupleEncoded[i])
printInfo(`\nEncodings are identical: ${encodingsMatch}`) printInfo('This confirms structs are just named tuples with the same binary encoding.')
// Step 4: Accessing struct field names and types via the type object printStep(4, 'Accessing Struct Field Names and Types')
printInfo('Field information from structFields property:') userStruct.structFields.forEach((field, i) => { const fieldType = field.type as ABIType printInfo(`\n Field ${i}:`) printInfo(` Name: ${field.name}`) printInfo(` Type: ${fieldType.toString()}`) printInfo(` isDynamic: ${fieldType.isDynamic()}`) if (!fieldType.isDynamic()) { printInfo(` byteLen: ${fieldType.byteLen()}`) } })
printInfo('\nConverting struct to tuple type:') const tupleFromStruct = userStruct.toABITupleType() printInfo(` toABITupleType(): ${tupleFromStruct.toString()}`) printInfo(` childTypes.length: ${tupleFromStruct.childTypes.length}`)
// Step 5: Decoding back to struct value with named fields printStep(5, 'Decoding to Struct with Named Fields')
const userDecoded = userStruct.decode(userEncoded)
printInfo('Decoded struct value (object with named keys):') printInfo(` typeof decoded: ${typeof userDecoded}`) printInfo(` decoded.name: "${(userDecoded as { name: string }).name}"`) printInfo(` decoded.age: ${(userDecoded as { age: bigint }).age}`) printInfo(` decoded.active: ${(userDecoded as { active: boolean }).active}`)
// Compare with tuple decoding const tupleDecoded = equivalentTuple.decode(userEncoded) printInfo('\nCompare with tuple decoding (array with index access):') printInfo(` typeof decoded: ${typeof tupleDecoded}`) printInfo(` Array.isArray: ${Array.isArray(tupleDecoded)}`) printInfo(` decoded[0]: "${tupleDecoded[0]}"`) printInfo(` decoded[1]: ${tupleDecoded[1]}`) printInfo(` decoded[2]: ${tupleDecoded[2]}`)
printInfo('\nKey difference:') printInfo(' Struct decode() returns an OBJECT with named properties') printInfo(' Tuple decode() returns an ARRAY with indexed elements')
// Step 6: Static struct example printStep(6, 'Static Struct Example')
// Create a struct with all static fields const pointStruct = ABIStructType.fromStruct('Point', { Point: [ { name: 'x', type: 'uint32' }, { name: 'y', type: 'uint32' }, ], })
printInfo('Static struct (all fields are static types):') printInfo(` Struct name: ${pointStruct.structName}`) printInfo(` ABI type: ${pointStruct.name}`) printInfo(` isDynamic(): ${pointStruct.isDynamic()}`) printInfo(` byteLen(): ${pointStruct.byteLen()} (4 + 4 = 8 bytes)`)
const pointValue = { x: 100, y: 200 } const pointEncoded = pointStruct.encode(pointValue) const pointDecoded = pointStruct.decode(pointEncoded) as { x: number; y: number }
printInfo(`\nEncode { x: ${pointValue.x}, y: ${pointValue.y} }:`) printInfo(` Encoded: ${formatHex(pointEncoded)}`) printInfo(` Total bytes: ${pointEncoded.length}`)
printInfo('\nByte layout (all static, no offsets):') printInfo(` [0-3] x (uint32): ${formatHex(pointEncoded.slice(0, 4))} = ${pointValue.x}`) printInfo(` [4-7] y (uint32): ${formatHex(pointEncoded.slice(4, 8))} = ${pointValue.y}`)
printInfo(`\nDecoded: { x: ${pointDecoded.x}, y: ${pointDecoded.y} }`)
// Step 7: Encoding struct as array (tuple-style) printStep(7, 'Encoding Struct as Array (Tuple-style)')
printInfo('ABIStructType.encode() accepts both objects and arrays:')
// Encode as object const objEncoded = userStruct.encode({ name: 'Bob', age: 25n, active: false })
// Encode as array (tuple-style) const arrEncoded = userStruct.encode(['Bob', 25n, false])
printInfo('\nEncoded as object { name: "Bob", age: 25n, active: false }:') printInfo(` ${formatHex(objEncoded)}`)
printInfo('\nEncoded as array ["Bob", 25n, false]:') printInfo(` ${formatHex(arrEncoded)}`)
const arrayObjMatch = objEncoded.length === arrEncoded.length && objEncoded.every((byte, i) => byte === arrEncoded[i])
printInfo(`\nEncodings are identical: ${arrayObjMatch}`) printInfo('Both input formats produce the same encoded bytes.')
// Step 8: Nested struct example printStep(8, 'Nested Struct Example')
// Person struct containing Address struct const personStruct = ABIStructType.fromStruct('Person', { Person: [ { name: 'name', type: 'string' }, { name: 'age', type: 'uint8' }, { name: 'address', type: 'Address' }, ], Address: [ { name: 'street', type: 'string' }, { name: 'city', type: 'string' }, ], })
printInfo('Nested struct Person containing Address:') printInfo(` Person ABI type: ${personStruct.name}`)
const personValue = { name: 'Charlie', age: 28, address: { street: '123 Main St', city: 'Boston', }, }
const personEncoded = personStruct.encode(personValue) const personDecoded = personStruct.decode(personEncoded) as { name: string age: number address: { street: string; city: string } }
printInfo(`\nInput: { name: "${personValue.name}", age: ${personValue.age}, address: {...} }`) printInfo(`Encoded: ${formatHex(personEncoded)}`) printInfo(`Total bytes: ${personEncoded.length}`)
printInfo('\nDecoded nested struct:') printInfo(` decoded.name: "${personDecoded.name}"`) printInfo(` decoded.age: ${personDecoded.age}`) printInfo(` decoded.address.street: "${personDecoded.address.street}"`) printInfo(` decoded.address.city: "${personDecoded.address.city}"`)
// Step 9: Creating ABIStructType programmatically printStep(9, 'Creating ABIStructType Programmatically')
// Create struct type using constructor directly const customStruct = new ABIStructType('Score', [ { name: 'playerId', type: new ABIUintType(64) }, { name: 'score', type: new ABIUintType(32) }, { name: 'isHighScore', type: new ABIBoolType() }, ])
printInfo('Created with: new ABIStructType("Score", [...])') printInfo(` Struct name: ${customStruct.structName}`) printInfo(` ABI type: ${customStruct.name}`) printInfo(` isDynamic(): ${customStruct.isDynamic()}`) printInfo(` byteLen(): ${customStruct.byteLen()} (8 + 4 + 1 = 13 bytes)`)
const scoreValue = { playerId: 12345n, score: 9999, isHighScore: true } const scoreEncoded = customStruct.encode(scoreValue) const scoreDecoded = customStruct.decode(scoreEncoded) as { playerId: bigint score: number isHighScore: boolean }
printInfo(`\nEncode: { playerId: ${scoreValue.playerId}, score: ${scoreValue.score}, isHighScore: ${scoreValue.isHighScore} }`) printInfo(` Encoded: ${formatHex(scoreEncoded)}`) printInfo(` Decoded: { playerId: ${scoreDecoded.playerId}, score: ${scoreDecoded.score}, isHighScore: ${scoreDecoded.isHighScore} }`)
// Step 10: Summary printStep(10, 'Summary')
printInfo('ABIStructType key properties:') printInfo(' - structName: The name of the struct') printInfo(' - structFields: Array of { name, type } field definitions') printInfo(' - toABITupleType(): Converts to equivalent tuple type') printInfo(' - isDynamic(): true if ANY field is dynamic') printInfo(' - byteLen(): only valid for static structs')
printInfo('\nStruct vs Tuple:') printInfo(' - Structs are named tuples with identical binary encoding') printInfo(' - Field names provide semantic meaning, not encoding differences') printInfo(' - encode() accepts objects OR arrays') printInfo(' - decode() returns objects with named properties (not arrays)')
printInfo('\nCreating struct types:') printInfo(' - ABIStructType.fromStruct(name, structs) - from struct definitions') printInfo(' - new ABIStructType(name, fields) - programmatic with ABIType instances')
printInfo('\nNested structs:') printInfo(' - Structs can contain other structs') printInfo(' - Pass all struct definitions in the structs record') printInfo(' - Nested struct values are objects within objects')
printSuccess('ABI Struct Type example completed successfully!')}
main()