Skip to content
Algorand Developer Portal

ABI String Type

← Back to ABI Encoding

This example demonstrates how to encode and decode dynamic strings using ABIStringType:

  • ABIStringType encodes strings with a 2-byte length prefix followed by UTF-8 content
  • Shows encoding of empty strings, ASCII text, and Unicode characters
  • Demonstrates that strings are dynamic types (variable length)
  • Displays byte breakdown: length prefix vs content bytes
  • No LocalNet required

From the repository root:

Terminal window
cd examples
npm run example abi/04-string-type.ts

View source on GitHub

04-string-type.ts
/**
* Example: ABI String Type
*
* This example demonstrates how to encode and decode dynamic strings using ABIStringType:
* - ABIStringType encodes strings with a 2-byte length prefix followed by UTF-8 content
* - Shows encoding of empty strings, ASCII text, and Unicode characters
* - Demonstrates that strings are dynamic types (variable length)
* - Displays byte breakdown: length prefix vs content bytes
*
* Prerequisites:
* - No LocalNet required
*/
import { ABIStringType } from '@algorandfoundation/algokit-utils/abi';
import { formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js';
function main() {
printHeader('ABI String Type Example');
// Step 1: ABIStringType basics
printStep(1, 'ABIStringType - Basic Properties');
const stringType = new ABIStringType();
printInfo(`type: ${stringType.toString()}`);
printInfo(`isDynamic: ${stringType.isDynamic()}`);
printInfo('Note: Strings are dynamic types - their encoded length varies with content');
// Step 2: Encoding an empty string
printStep(2, 'Encoding Empty String');
const emptyString = '';
const emptyEncoded = stringType.encode(emptyString);
const emptyDecoded = stringType.decode(emptyEncoded);
printInfo(`string value: "" (empty)`);
printInfo(` encoded: ${formatHex(emptyEncoded)}`);
printInfo(` total bytes: ${emptyEncoded.length}`);
printInfo(
` length prefix (2 bytes): ${formatHex(emptyEncoded.slice(0, 2))} = ${(emptyEncoded[0] << 8) | emptyEncoded[1]}`,
);
printInfo(` content bytes: ${emptyEncoded.length - 2}`);
printInfo(` decoded: "${emptyDecoded}"`);
printInfo(` round-trip verified: ${emptyDecoded === emptyString}`);
// Step 3: Encoding a short ASCII string
printStep(3, 'Encoding Short ASCII String');
const helloString = 'Hello';
const helloEncoded = stringType.encode(helloString);
const helloDecoded = stringType.decode(helloEncoded);
printInfo(`string value: "${helloString}"`);
printInfo(` encoded: ${formatHex(helloEncoded)}`);
printInfo(` total bytes: ${helloEncoded.length}`);
printInfo(
` length prefix (2 bytes): ${formatHex(helloEncoded.slice(0, 2))} = ${(helloEncoded[0] << 8) | helloEncoded[1]}`,
);
printInfo(` content bytes: ${helloEncoded.length - 2}`);
// Show individual character encoding
printInfo('\n Byte breakdown:');
printInfo(
` [0-1] Length prefix: ${formatHex(helloEncoded.slice(0, 2))} (${helloString.length})`,
);
for (let i = 0; i < helloString.length; i++) {
const charByte = helloEncoded[i + 2];
printInfo(
` [${i + 2}] '${helloString[i]}' -> 0x${charByte.toString(16).padStart(2, '0')} (${charByte})`,
);
}
printInfo(`\n decoded: "${helloDecoded}"`);
printInfo(` round-trip verified: ${helloDecoded === helloString}`);
// Step 4: Encoding a longer ASCII string
printStep(4, 'Encoding Longer ASCII String');
const loremString = 'The quick brown fox jumps over the lazy dog.';
const loremEncoded = stringType.encode(loremString);
const loremDecoded = stringType.decode(loremEncoded);
printInfo(`string value: "${loremString}"`);
printInfo(` encoded: ${formatHex(loremEncoded)}`);
printInfo(` total bytes: ${loremEncoded.length}`);
printInfo(
` length prefix (2 bytes): ${formatHex(loremEncoded.slice(0, 2))} = ${(loremEncoded[0] << 8) | loremEncoded[1]}`,
);
printInfo(` content bytes: ${loremEncoded.length - 2}`);
printInfo(` decoded: "${loremDecoded}"`);
printInfo(` round-trip verified: ${loremDecoded === loremString}`);
// Step 5: Encoding Unicode characters
printStep(5, 'Encoding Unicode Characters');
const unicodeString = 'Hello, δΈ–η•Œ! 🌍';
const unicodeEncoded = stringType.encode(unicodeString);
const unicodeDecoded = stringType.decode(unicodeEncoded);
printInfo(`string value: "${unicodeString}"`);
printInfo(` encoded: ${formatHex(unicodeEncoded)}`);
printInfo(` total bytes: ${unicodeEncoded.length}`);
printInfo(
` length prefix (2 bytes): ${formatHex(unicodeEncoded.slice(0, 2))} = ${(unicodeEncoded[0] << 8) | unicodeEncoded[1]}`,
);
printInfo(` content bytes: ${unicodeEncoded.length - 2}`);
printInfo(` JS string length: ${unicodeString.length} (characters)`);
printInfo(` UTF-8 byte length: ${unicodeEncoded.length - 2} (bytes)`);
printInfo(' Note: Unicode characters may use multiple bytes in UTF-8 encoding');
printInfo(` decoded: "${unicodeDecoded}"`);
printInfo(` round-trip verified: ${unicodeDecoded === unicodeString}`);
// Step 6: Encoding emoji-only string
printStep(6, 'Encoding Emoji String');
const emojiString = 'πŸš€πŸŽ‰πŸ’»';
const emojiEncoded = stringType.encode(emojiString);
const emojiDecoded = stringType.decode(emojiEncoded);
printInfo(`string value: "${emojiString}"`);
printInfo(` encoded: ${formatHex(emojiEncoded)}`);
printInfo(` total bytes: ${emojiEncoded.length}`);
printInfo(
` length prefix (2 bytes): ${formatHex(emojiEncoded.slice(0, 2))} = ${(emojiEncoded[0] << 8) | emojiEncoded[1]}`,
);
printInfo(` content bytes: ${emojiEncoded.length - 2}`);
printInfo(` JS string length: ${emojiString.length} (UTF-16 code units, emojis use 2 each)`);
printInfo(` UTF-8 byte length: ${emojiEncoded.length - 2} (bytes, emojis use 4 bytes each)`);
printInfo(` decoded: "${emojiDecoded}"`);
printInfo(` round-trip verified: ${emojiDecoded === emojiString}`);
// Step 7: Maximum length demonstration
printStep(7, 'String Length Limits');
printInfo('String encoding uses a 2-byte (uint16) length prefix:');
printInfo(' Maximum string length: 65535 bytes (2^16 - 1)');
printInfo(' Length prefix is big-endian encoded');
// Demonstrate a string that would have a length > 255 (requiring both bytes)
const longString = 'A'.repeat(300);
const longEncoded = stringType.encode(longString);
printInfo(`\nExample with 300-character string:`);
printInfo(` length prefix: ${formatHex(longEncoded.slice(0, 2))}`);
printInfo(` high byte: 0x${longEncoded[0].toString(16).padStart(2, '0')} = ${longEncoded[0]}`);
printInfo(` low byte: 0x${longEncoded[1].toString(16).padStart(2, '0')} = ${longEncoded[1]}`);
printInfo(
` decoded length: (${longEncoded[0]} << 8) | ${longEncoded[1]} = ${(longEncoded[0] << 8) | longEncoded[1]}`,
);
// Step 8: Summary
printStep(8, 'Summary - Dynamic Type Behavior');
printInfo('Key points about ABIStringType:');
printInfo(' - Strings are dynamic types (isDynamic() returns true)');
printInfo(' - Encoding format: 2-byte length prefix + UTF-8 content');
printInfo(' - Length prefix is big-endian (most significant byte first)');
printInfo(' - UTF-8 encoding means characters may use 1-4 bytes');
printInfo(' - Maximum string length: 65535 bytes');
printSuccess('ABI String Type example completed successfully!');
}
main();