Primitive Codecs
Description
Section titled “Description”This example demonstrates how to use primitive codec types for encoding/decoding basic values in wire format (JSON or MessagePack). Topics covered:
- Codec interface: encode(), decode(), defaultValue() methods
- numberCodec for encoding/decoding numbers
- bigIntCodec for encoding/decoding BigInt values
- booleanCodec for encoding/decoding boolean values
- stringCodec for encoding/decoding UTF-8 strings
- bytesCodec for encoding/decoding raw bytes
- bytesBase64Codec for base64 encoding
- addressCodec for encoding/decoding Algorand addresses
- Round-trip verification: decode(encode(value)) equals original
Prerequisites
Section titled “Prerequisites”- No LocalNet required
Run This Example
Section titled “Run This Example”From the repository root:
cd examplesnpm run example common/09-primitive-codecs.ts/** * Example: Primitive Codecs * * This example demonstrates how to use primitive codec types for encoding/decoding * basic values in wire format (JSON or MessagePack). * * Topics covered: * - Codec interface: encode(), decode(), defaultValue() methods * - numberCodec for encoding/decoding numbers * - bigIntCodec for encoding/decoding BigInt values * - booleanCodec for encoding/decoding boolean values * - stringCodec for encoding/decoding UTF-8 strings * - bytesCodec for encoding/decoding raw bytes * - bytesBase64Codec for base64 encoding * - addressCodec for encoding/decoding Algorand addresses * - Round-trip verification: decode(encode(value)) equals original * * Prerequisites: * - No LocalNet required */
import type { EncodingFormat } from '@algorandfoundation/algokit-utils/common'import { Address, addressCodec, arrayEqual, bigIntCodec, booleanCodec, bytesBase64Codec, bytesCodec, numberCodec, stringCodec,} from '@algorandfoundation/algokit-utils/common'import { formatHex, printHeader, printInfo, printStep, printSuccess } from '../shared/utils.js'
// ============================================================================// Main Example// ============================================================================
printHeader('Primitive Codecs Example')
// ============================================================================// Step 1: The Codec Interface// ============================================================================printStep(1, 'The Codec Interface')
printInfo('Codecs provide bidirectional transformation between app types and wire formats.')printInfo('Each codec has these core methods:')printInfo('')printInfo(' encode(value, format) - Transform app value to wire format')printInfo(' decode(value, format) - Transform wire value to app value')printInfo(' defaultValue() - Get the default value for this type')printInfo('')printInfo('Format can be "json" or "msgpack" - some codecs behave differently per format.')printInfo('')
// Show default values for each codecprintInfo('Default values for each primitive codec:')printInfo(` numberCodec.defaultValue() = ${numberCodec.defaultValue()}`)printInfo(` bigIntCodec.defaultValue() = ${bigIntCodec.defaultValue()}n`)printInfo(` booleanCodec.defaultValue() = ${booleanCodec.defaultValue()}`)printInfo(` stringCodec.defaultValue() = "${stringCodec.defaultValue()}" (empty string)`)printInfo(` bytesCodec.defaultValue() = Uint8Array(${bytesCodec.defaultValue().length}) (empty)`)printInfo(` addressCodec.defaultValue() = ${addressCodec.defaultValue().toString().slice(0, 10)}... (zero address)`)printInfo('')printSuccess('Codec interface provides consistent encode/decode methods')
// ============================================================================// Step 2: numberCodec - Encoding/Decoding Numbers// ============================================================================printStep(2, 'numberCodec - Encoding/Decoding Numbers')
printInfo('numberCodec handles JavaScript number values.')printInfo('Numbers pass through unchanged in both JSON and msgpack formats.')printInfo('')
const testNumbers = [0, 42, -100, 3.14159, Number.MAX_SAFE_INTEGER]
for (const num of testNumbers) { const encodedJson = numberCodec.encode(num, 'json') const encodedMsgpack = numberCodec.encode(num, 'msgpack') const decodedJson = numberCodec.decode(encodedJson, 'json') const decodedMsgpack = numberCodec.decode(encodedMsgpack, 'msgpack')
printInfo(` ${num}:`) printInfo(` JSON: encode=${encodedJson}, decode=${decodedJson}, matches=${num === decodedJson}`) printInfo(` msgpack: encode=${encodedMsgpack}, decode=${decodedMsgpack}, matches=${num === decodedMsgpack}`)}printInfo('')
// Round-trip verificationconst roundTripNum = 12345const rtEncoded = numberCodec.encode(roundTripNum, 'json')const rtDecoded = numberCodec.decode(rtEncoded, 'json')if (rtDecoded === roundTripNum) { printSuccess(`Round-trip: decode(encode(${roundTripNum})) === ${rtDecoded}`)}
// ============================================================================// Step 3: bigIntCodec - Encoding/Decoding BigInt Values// ============================================================================printStep(3, 'bigIntCodec - Encoding/Decoding BigInt Values')
printInfo('bigIntCodec handles JavaScript BigInt values (uint64 on Algorand).')printInfo('Essential for values exceeding Number.MAX_SAFE_INTEGER (9007199254740991).')printInfo('')
const testBigInts: bigint[] = [0n, 100n, 9007199254740993n, 18446744073709551615n] // 0, 100, MAX_SAFE+2, max uint64
for (const big of testBigInts) { const encodedJson = bigIntCodec.encode(big, 'json') const encodedMsgpack = bigIntCodec.encode(big, 'msgpack') const decodedJson = bigIntCodec.decode(encodedJson, 'json') const decodedMsgpack = bigIntCodec.decode(encodedMsgpack, 'msgpack')
printInfo(` ${big}n:`) printInfo(` JSON: encode=${encodedJson}${typeof encodedJson === 'bigint' ? 'n' : ''}, decode=${decodedJson}n, matches=${big === decodedJson}`) printInfo(` msgpack: encode=${encodedMsgpack}${typeof encodedMsgpack === 'bigint' ? 'n' : ''}, decode=${decodedMsgpack}n, matches=${big === decodedMsgpack}`)}printInfo('')
// Round-trip verification with large valueconst roundTripBig = 18446744073709551615n // max uint64const rtBigEncoded = bigIntCodec.encode(roundTripBig, 'msgpack')const rtBigDecoded = bigIntCodec.decode(rtBigEncoded, 'msgpack')if (rtBigDecoded === roundTripBig) { printSuccess(`Round-trip: decode(encode(${roundTripBig}n)) === ${rtBigDecoded}n`)}
// ============================================================================// Step 4: booleanCodec - Encoding/Decoding Boolean Values// ============================================================================printStep(4, 'booleanCodec - Encoding/Decoding Boolean Values')
printInfo('booleanCodec handles true/false values.')printInfo('Default value is false.')printInfo('')
const testBools = [true, false]
for (const bool of testBools) { const encodedJson = booleanCodec.encode(bool, 'json') const encodedMsgpack = booleanCodec.encode(bool, 'msgpack') const decodedJson = booleanCodec.decode(encodedJson, 'json') const decodedMsgpack = booleanCodec.decode(encodedMsgpack, 'msgpack')
printInfo(` ${bool}:`) printInfo(` JSON: encode=${encodedJson}, decode=${decodedJson}, matches=${bool === decodedJson}`) printInfo(` msgpack: encode=${encodedMsgpack}, decode=${decodedMsgpack}, matches=${bool === decodedMsgpack}`)}printInfo('')
// Round-trip verificationconst roundTripBool = trueconst rtBoolEncoded = booleanCodec.encode(roundTripBool, 'json')const rtBoolDecoded = booleanCodec.decode(rtBoolEncoded, 'json')if (rtBoolDecoded === roundTripBool) { printSuccess(`Round-trip: decode(encode(${roundTripBool})) === ${rtBoolDecoded}`)}
// ============================================================================// Step 5: stringCodec - Encoding/Decoding UTF-8 Strings// ============================================================================printStep(5, 'stringCodec - Encoding/Decoding UTF-8 Strings')
printInfo('stringCodec handles UTF-8 string values.')printInfo('In msgpack, strings may be received as Uint8Array (normalized on decode).')printInfo('')
const testStrings = ['', 'Hello', 'Hello, Algorand!', 'Unicode: \u00e9\u00e8\u00ea']
for (const str of testStrings) { const encodedJson = stringCodec.encode(str, 'json') const encodedMsgpack = stringCodec.encode(str, 'msgpack') const decodedJson = stringCodec.decode(encodedJson, 'json') const decodedMsgpack = stringCodec.decode(encodedMsgpack, 'msgpack')
const displayStr = str === '' ? '(empty)' : `"${str}"` printInfo(` ${displayStr}:`) printInfo(` JSON: encode="${encodedJson}", decode="${decodedJson}", matches=${str === decodedJson}`) printInfo(` msgpack: encode="${encodedMsgpack}", decode="${decodedMsgpack}", matches=${str === decodedMsgpack}`)}printInfo('')
// Round-trip verificationconst roundTripStr = 'Algorand SDK'const rtStrEncoded = stringCodec.encode(roundTripStr, 'json')const rtStrDecoded = stringCodec.decode(rtStrEncoded, 'json')if (rtStrDecoded === roundTripStr) { printSuccess(`Round-trip: decode(encode("${roundTripStr}")) === "${rtStrDecoded}"`)}
// ============================================================================// Step 6: bytesCodec - Encoding/Decoding Raw Bytes// ============================================================================printStep(6, 'bytesCodec - Encoding/Decoding Raw Bytes')
printInfo('bytesCodec handles Uint8Array values (raw binary data).')printInfo('JSON format: encodes to base64 string')printInfo('msgpack format: encodes as raw bytes')printInfo('')
const testBytes = [ new Uint8Array([]), new Uint8Array([0x01, 0x02, 0x03]), new Uint8Array([0xde, 0xad, 0xbe, 0xef]), new Uint8Array(32).fill(0xab), // 32-byte key simulation]
for (const bytes of testBytes) { const encodedJson = bytesCodec.encode(bytes, 'json') const encodedMsgpack = bytesCodec.encode(bytes, 'msgpack') const decodedJson = bytesCodec.decode(encodedJson, 'json') const decodedMsgpack = bytesCodec.decode(encodedMsgpack, 'msgpack')
const displayBytes = bytes.length === 0 ? '(empty)' : formatHex(bytes.slice(0, 8)) + (bytes.length > 8 ? '...' : '') printInfo(` ${displayBytes} (${bytes.length} bytes):`) printInfo(` JSON: encode="${encodedJson}" (base64)`) printInfo(` decode=${formatHex(decodedJson.slice(0, 8))}${decodedJson.length > 8 ? '...' : ''}, matches=${arrayEqual(bytes, decodedJson)}`) printInfo(` msgpack: encode=${formatHex((encodedMsgpack as Uint8Array).slice(0, 8))}${(encodedMsgpack as Uint8Array).length > 8 ? '...' : ''}`) printInfo(` decode=${formatHex(decodedMsgpack.slice(0, 8))}${decodedMsgpack.length > 8 ? '...' : ''}, matches=${arrayEqual(bytes, decodedMsgpack)}`)}printInfo('')
// Round-trip verificationconst roundTripBytes = new Uint8Array([0x01, 0x02, 0x03, 0x04, 0x05])const rtBytesEncodedJson = bytesCodec.encode(roundTripBytes, 'json')const rtBytesDecodedJson = bytesCodec.decode(rtBytesEncodedJson, 'json')if (arrayEqual(rtBytesDecodedJson, roundTripBytes)) { printSuccess(`Round-trip (JSON): decode(encode(${formatHex(roundTripBytes)})) matches original`)}
const rtBytesEncodedMsgpack = bytesCodec.encode(roundTripBytes, 'msgpack')const rtBytesDecodedMsgpack = bytesCodec.decode(rtBytesEncodedMsgpack, 'msgpack')if (arrayEqual(rtBytesDecodedMsgpack, roundTripBytes)) { printSuccess(`Round-trip (msgpack): decode(encode(${formatHex(roundTripBytes)})) matches original`)}
// ============================================================================// Step 7: bytesBase64Codec - Base64 Encoding for Bytes// ============================================================================printStep(7, 'bytesBase64Codec - Base64 Encoding for Bytes')
printInfo('bytesBase64Codec always encodes to base64 (both JSON and msgpack).')printInfo('Used for fields that are always base64 in wire format.')printInfo('')
const testBase64Bytes = [ new Uint8Array([0x48, 0x65, 0x6c, 0x6c, 0x6f]), // "Hello" in ASCII new Uint8Array([0xff, 0xfe, 0xfd]),]
for (const bytes of testBase64Bytes) { const encodedJson = bytesBase64Codec.encode(bytes, 'json') const encodedMsgpack = bytesBase64Codec.encode(bytes, 'msgpack') const decodedJson = bytesBase64Codec.decode(encodedJson, 'json') const decodedMsgpack = bytesBase64Codec.decode(encodedMsgpack, 'msgpack')
printInfo(` ${formatHex(bytes)}:`) printInfo(` JSON: encode="${encodedJson}" (base64)`) printInfo(` decode=${formatHex(decodedJson)}, matches=${arrayEqual(bytes, decodedJson)}`) printInfo(` msgpack: encode="${encodedMsgpack}" (base64)`) printInfo(` decode=${formatHex(decodedMsgpack)}, matches=${arrayEqual(bytes, decodedMsgpack)}`)}printInfo('')
// Round-trip verificationconst rtB64Bytes = new Uint8Array([0x01, 0x02, 0x03])const rtB64Encoded = bytesBase64Codec.encode(rtB64Bytes, 'json')const rtB64Decoded = bytesBase64Codec.decode(rtB64Encoded, 'json')if (arrayEqual(rtB64Decoded, rtB64Bytes)) { printSuccess(`Round-trip: decode(encode(${formatHex(rtB64Bytes)})) via base64 "${rtB64Encoded}" matches original`)}
// ============================================================================// Step 8: addressCodec - Encoding/Decoding Algorand Addresses// ============================================================================printStep(8, 'addressCodec - Encoding/Decoding Algorand Addresses')
printInfo('addressCodec handles Algorand Address objects.')printInfo('JSON format: encodes to 58-character base32 string')printInfo('msgpack format: encodes to 32-byte public key')printInfo('')
// Create test addressesconst zeroAddr = Address.zeroAddress()const testAddrBytes = new Uint8Array(32)for (let i = 0; i < 32; i++) testAddrBytes[i] = iconst testAddr = new Address(testAddrBytes)
const testAddresses = [zeroAddr, testAddr]
for (const addr of testAddresses) { const encodedJson = addressCodec.encode(addr, 'json') const encodedMsgpack = addressCodec.encode(addr, 'msgpack') const decodedJson = addressCodec.decode(encodedJson, 'json') const decodedMsgpack = addressCodec.decode(encodedMsgpack, 'msgpack')
const displayAddr = `${addr.toString().slice(0, 12) }...` printInfo(` ${displayAddr}:`) printInfo(` JSON: encode="${(encodedJson as string).slice(0, 12)}..." (base32 string)`) printInfo(` decode=${decodedJson.toString().slice(0, 12)}..., matches=${addr.equals(decodedJson)}`) printInfo(` msgpack: encode=${formatHex((encodedMsgpack as Uint8Array).slice(0, 8))}... (32-byte pubkey)`) printInfo(` decode=${decodedMsgpack.toString().slice(0, 12)}..., matches=${addr.equals(decodedMsgpack)}`)}printInfo('')
// Round-trip verificationconst roundTripAddr = testAddrconst rtAddrEncodedJson = addressCodec.encode(roundTripAddr, 'json')const rtAddrDecodedJson = addressCodec.decode(rtAddrEncodedJson, 'json')if (rtAddrDecodedJson.equals(roundTripAddr)) { printSuccess(`Round-trip (JSON): decode(encode(${roundTripAddr.toString().slice(0, 12)}...)) matches original`)}
const rtAddrEncodedMsgpack = addressCodec.encode(roundTripAddr, 'msgpack')const rtAddrDecodedMsgpack = addressCodec.decode(rtAddrEncodedMsgpack, 'msgpack')if (rtAddrDecodedMsgpack.equals(roundTripAddr)) { printSuccess(`Round-trip (msgpack): decode(encode(${roundTripAddr.toString().slice(0, 12)}...)) matches original`)}
// ============================================================================// Step 9: Format Differences Summary// ============================================================================printStep(9, 'Format Differences Summary')
printInfo('Codec behavior differs by format (json vs msgpack):')printInfo('')printInfo(' Codec | JSON Encoding | msgpack Encoding')printInfo(' -------------------|----------------------|-------------------')printInfo(' numberCodec | number | number')printInfo(' bigIntCodec | bigint | bigint')printInfo(' booleanCodec | boolean | boolean')printInfo(' stringCodec | string | string (or Uint8Array)')printInfo(' bytesCodec | base64 string | Uint8Array')printInfo(' bytesBase64Codec | base64 string | base64 string')printInfo(' addressCodec | 58-char base32 string| 32-byte Uint8Array')printInfo('')printSuccess('Codecs handle format-specific transformations automatically')
// ============================================================================// Step 10: Round-Trip Verification Summary// ============================================================================printStep(10, 'Round-Trip Verification Summary')
printInfo('All primitive codecs support perfect round-trip encoding:')printInfo('')
// Aggregate all round-trip resultsconst roundTrips: Array<{ name: string; value: string; format: EncodingFormat; success: boolean }> = []
// Numberconst numVal = 42roundTrips.push({ name: 'numberCodec', value: String(numVal), format: 'json', success: numberCodec.decode(numberCodec.encode(numVal, 'json'), 'json') === numVal,})
// BigIntconst bigVal = 9007199254740993nroundTrips.push({ name: 'bigIntCodec', value: `${bigVal}n`, format: 'msgpack', success: bigIntCodec.decode(bigIntCodec.encode(bigVal, 'msgpack'), 'msgpack') === bigVal,})
// Booleanconst boolVal = trueroundTrips.push({ name: 'booleanCodec', value: String(boolVal), format: 'json', success: booleanCodec.decode(booleanCodec.encode(boolVal, 'json'), 'json') === boolVal,})
// Stringconst strVal = 'Algorand'roundTrips.push({ name: 'stringCodec', value: `"${strVal}"`, format: 'json', success: stringCodec.decode(stringCodec.encode(strVal, 'json'), 'json') === strVal,})
// Bytesconst bytesVal = new Uint8Array([1, 2, 3, 4, 5])roundTrips.push({ name: 'bytesCodec', value: formatHex(bytesVal), format: 'json', success: arrayEqual(bytesCodec.decode(bytesCodec.encode(bytesVal, 'json'), 'json'), bytesVal),})roundTrips.push({ name: 'bytesCodec', value: formatHex(bytesVal), format: 'msgpack', success: arrayEqual(bytesCodec.decode(bytesCodec.encode(bytesVal, 'msgpack'), 'msgpack'), bytesVal),})
// BytesBase64const b64Val = new Uint8Array([0xff, 0xfe, 0xfd])roundTrips.push({ name: 'bytesBase64Codec', value: formatHex(b64Val), format: 'json', success: arrayEqual(bytesBase64Codec.decode(bytesBase64Codec.encode(b64Val, 'json'), 'json'), b64Val),})
// Addressconst addrVal = testAddrroundTrips.push({ name: 'addressCodec', value: `${addrVal.toString().slice(0, 12) }...`, format: 'json', success: addressCodec.decode(addressCodec.encode(addrVal, 'json'), 'json').equals(addrVal),})roundTrips.push({ name: 'addressCodec', value: `${addrVal.toString().slice(0, 12) }...`, format: 'msgpack', success: addressCodec.decode(addressCodec.encode(addrVal, 'msgpack'), 'msgpack').equals(addrVal),})
for (const rt of roundTrips) { const status = rt.success ? 'PASS' : 'FAIL' printInfo(` [${status}] ${rt.name} (${rt.format}): ${rt.value}`)}
const allPassed = roundTrips.every((rt) => rt.success)if (allPassed) { printInfo('') printSuccess('All round-trip verifications passed!')}
// ============================================================================// Summary// ============================================================================printStep(11, 'Summary')
printInfo('Primitive codecs for Algorand wire format encoding:')printInfo('')printInfo(' Codec Interface:')printInfo(' - encode(value, format) - Transform to wire format')printInfo(' - decode(value, format) - Transform from wire format')printInfo(' - defaultValue() - Get type default (0, false, "", etc.)')printInfo('')printInfo(' Available Primitive Codecs:')printInfo(' - numberCodec - JavaScript numbers')printInfo(' - bigIntCodec - BigInt for uint64 values')printInfo(' - booleanCodec - true/false values')printInfo(' - stringCodec - UTF-8 strings')printInfo(' - bytesCodec - Raw binary data (Uint8Array)')printInfo(' - bytesBase64Codec - Always base64 encoded bytes')printInfo(' - addressCodec - Algorand Address objects')printInfo('')printInfo(' Format Support:')printInfo(' - "json" - JSON wire format (strings for bytes/addresses)')printInfo(' - "msgpack" - MessagePack format (binary for bytes/addresses)')printInfo('')printSuccess('Primitive Codecs Example completed!')