Skip to content
Algorand Developer Portal

ABI Struct Type

← Back to ABI Encoding

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

From the repository root:

Terminal window
cd examples
npm run example abi/08-struct-type.ts

View source on GitHub

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