How Data Is Serialized in the Amadeus Protocol: A Complete Guide to Binary Encoding
The Amadeus Protocol uses a custom binary serialization scheme built in AssemblyScript, centered on a canonical Serializer class that produces deterministic, sorted-key binary payloads for on-chain storage and cross-environment data exchange.
Data serialization in the Amadeus Protocol is implemented through a purpose-built binary format designed for deterministic encoding, compact storage, and seamless interoperability. The system lives in the amadeusprotocol/node repository, with core logic implemented in AssemblyScript smart contract samples. This guide examines the complete serialization pipeline from low-level encoding primitives to high-level domain object persistence.
Core Serialization Architecture
The Amadeus Protocol's serialization system is built around two complementary abstractions: low-level encoding helpers for primitive types and a high-level Serializer class that orchestrates field ordering and final output. Both are defined in contract_samples/assemblyscript/sdk_vecpak.ts.
Low-Level Encoding Primitives
At the foundation, the protocol provides type-tagged encoding functions that write primitive values into a growing Array<u8>. Each function prefixes the value with a type tag and uses varint encoding for length-prefixed data:
encodeVarint– Writes variable-length integersencodeU16/encodeI16– Fixed-width 16-bit integersencodeString– Length-prefixed UTF-8 stringsencodeBytes– Length-prefixed raw byte arrays
These helpers use explicit type constants such as TYPE_INT, TYPE_BYTES, and TYPE_MAP to ensure unambiguous decoding. The varint approach minimizes byte overhead for small values while accommodating larger numbers when needed.
The Serializer Class
The Serializer class provides the primary interface for building serialized payloads. It accumulates fields as key-value pairs, then produces a canonical binary representation on demand.
Field addition follows a consistent pattern:
import { Serializer } from "./sdk_vecpak";
const s = new Serializer();
s.addString("name", "Excalibur");
s.addU16("damage", 42);
s.addI16("modifier", -3);
Each add* method performs three operations:
- Encodes the value using the appropriate low-level helper
- Creates a
Fieldstruct containing the key (as bytes) and value bytes - Stores the field in an internal collection
When finish() is called, the serializer sorts all fields by their canonical binary key representation, writes a TYPE_MAP header, and concatenates the sorted key-value pairs into a finalized Uint8Array. This sorting guarantees deterministic encoding—critical for blockchain applications where byte-exact equality affects state validation.
Deserialization with the Deserializer Class
The counterpart to Serializer is the Deserializer class, also defined in sdk_vecpak.ts. It reconstructs objects from binary streams through a cursor-based API:
import { Deserializer } from "./sdk_vecpak";
const d = new Deserializer(data);
while (d.hasNext()) {
const key = d.nextKey();
// Branch on key to read appropriate type
}
Key methods include:
hasNext()– Checks if additional map entries remainnextKey()– Advances to and returns the next field's keyreadU16(),readI16(),readString(),readBytes()– Type-specific value extractionskip()– Advances past unknown fields for forward compatibility
The DecodeRef pointer tracks position through the byte array, enabling safe sequential access without exposing raw offsets to callers.
Domain Object Serialization Pattern
Real-world Amadeus contracts implement serialization through a consistent interface: instance method serialize(): Uint8Array and static deserialize(data: Uint8Array). The RPG sample demonstrates this pattern in contract_samples/assemblyscript/5_rpg/model.ts.
Example: Weapon Class Implementation
import { Serializer, Deserializer } from "./sdk_vecpak";
export class Weapon {
name: string = "";
damage: u16 = 0;
serialize(): Uint8Array {
const s = new Serializer();
s.addString("name", this.name);
s.addU16("damage", this.damage);
return s.finish();
}
static deserialize(data: Uint8Array): Weapon {
const d = new Deserializer(data);
const w = new Weapon();
while (d.hasNext()) {
const key = d.nextKey();
if (key == "name") {
w.name = d.readString();
} else if (key == "damage") {
w.damage = d.readU16();
} else {
d.skip(); // Forward compatibility for new fields
}
}
return w;
}
}
The skip() call in the else branch enables forward compatibility—contracts can read data written by newer code versions without failing on unrecognized fields.
On-Chain Persistence
Serialized data moves between memory and persistent storage through the Amadeus SDK's key-value API. The sample entry point contract_samples/assemblyscript/5_rpg/main.ts demonstrates this workflow:
import { sdk } from "amadeus-sdk";
import { Weapon } from "./weapon";
export function storeWeapon(key: string, w: Weapon): void {
sdk.kv_put(key, w.serialize());
}
export function loadWeapon(key: string): Weapon | null {
const bytes = sdk.kv_get(key);
if (bytes) {
return Weapon.deserialize(bytes);
}
return null;
}
The sdk.kv_put and sdk.kv_get functions handle the boundary between contract logic and chain state. Binary serialization minimizes storage costs compared to text formats like JSON, while the canonical sorting ensures that identical logical objects produce identical bytes—preventing state divergence across validator implementations.
Design Rationale and Trade-offs
The Amadeus Protocol's custom serialization prioritizes three properties:
- Determinism – Sorted field keys and explicit type tags eliminate encoding ambiguity
- Compactness – Varint encoding and binary format reduce on-chain storage costs
- Simplicity – Single-file implementation in
sdk_vecpak.tsenables auditability and cross-language porting
These choices reflect blockchain-specific constraints: storage is expensive, consensus requires byte-exact agreement, and contracts must run in resource-constrained environments like WebAssembly.
Summary
- The
Serializerclass insdk_vecpak.tsproduces canonical binary maps with sorted keys - Low-level helpers (
encodeU16,encodeString, etc.) handle type-tagged varint encoding - The
Deserializerclass reconstructs objects through sequential cursor-based access - Domain objects implement
serialize()and staticdeserialize()for type-safe persistence - On-chain storage uses
sdk.kv_put()andsdk.kv_get()with binary payloads - Forward compatibility is supported via the
skip()method for unknown fields
Frequently Asked Questions
What programming language does the Amadeus Protocol use for serialization?
The Amadeus Protocol implements its serialization system in AssemblyScript, a TypeScript-like language that compiles to WebAssembly. This choice enables deterministic execution in blockchain environments while maintaining familiar syntax for developers. The core implementation resides in contract_samples/assemblyscript/sdk_vecpak.ts.
Why does the Amadeus Protocol use a custom binary format instead of JSON or Protocol Buffers?
The protocol requires deterministic encoding where identical logical values always produce identical byte sequences. JSON lacks guaranteed field ordering and number encoding precision. Protocol Buffers, while binary, don't guarantee deterministic serialization across implementations. The custom format in sdk_vecpak.ts ensures sorted keys, explicit type tags, and consistent varint encoding—essential for blockchain consensus.
How does the Amadeus Protocol handle schema evolution and backward compatibility?
The Deserializer class provides the skip() method to advance past unrecognized fields. When deserializing, contracts can iterate through map entries with hasNext() and nextKey(), processing known fields while calling skip() for unknown keys. This allows older contract code to read data written by newer versions without breaking.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →