Sails Header defines a deterministic, userspace envelope for Sails-based Gear/Vara asynchronous messages. Every Sails message begins with a 16-byte base header (extensions optional) that encodes a magic prefix, version, payload offset, and three routing identifiers: a 64-bit interface_id, a 16-bit entry_id, and an 8-bit route_idx. The header lives entirely within the message payload, requiring no runtime or consensus changes, and enables off-chain tooling and cross-program interoperability by exposing canonical interface metadata.
- Interoperability. Provide a uniform wire format so any program or tool can interpret interface- and entry-level information from the header alone.
- Off-chain decodability. Allow explorers, debuggers, and SDKs to decode Sails payloads without executing WASM by relying on the header’s identifiers.
- Deterministic identifiers. Tie
interface_idandentry_idto the canonical interface definition so identical interfaces across languages receive identical IDs. - Compile-time embedding. Make the identifiers available at compile time (e.g., via macros) so a program can embed routing data directly in the binary.
- Backward compatibility. Maintain the existing Gear message format; Sails Header occupies payload bytes only.
- Altering node runtime, consensus, or native message layout.
- Resurrecting deprecated metadata formats.
| Term | Meaning |
|---|---|
| Program | A deployed Sails program (Gear/Vara WASM module) with one or more interfaces. |
| Service / Interface | A logical API defined in IDL, consisting of functions and events. |
| Route | A named service instance. Programs may expose multiple instances per interface. |
interface_id |
64-bit identifier derived from the interface hashing spec (service name excluded). |
entry_id |
16-bit identifier for an interface entry (function/event), assigned deterministically. |
route_idx |
One-byte route selector. 0x00 requests route inference (allowed only when exactly one interface instance matches). Non-zero indices are mapped via a per-program manifest. |
Byte offset Field Size (bytes) Description
0–1 Magic 2 ASCII "GM" (0x47 0x4D)
2 Version 1 Header version (0x01)
3 Header length 1 Total header size in bytes; base header = 0x10
4–11 Interface ID 8 64-bit ID from Interface hash
12–13 Entry ID 2 Little-endian 16-bit entry identifier
14 Route Index 1 Unsigned byte; 0 = infer route if unambiguous
15 Reserved 1 Must be set to 0x00 in v1 (no error flags). Future specs may repurpose this byte.
>15 Extensions variable Present only if `header length` > 0x10
- Magic. Indicates presence of Sails Header. Readers should verify
0x47 0x4Dbefore interpreting the remaining bytes. - Version. Drives compatibility. Version 1 defines the format in this document; future versions may extend the header.
- Header length. Allows optional extensions to follow the base fields. Payload data starts at offset
header length. - Interface ID. Deterministic identifier computed via the hashing spec (see below). The ID never depends on the textual service name.
- Entry ID. Deterministically assigned
u16per interface by sorting entry names lexicographically (ties resolved by canonical signature). Implementations may freeze the assignment in lockfiles to preserve wire compatibility. - Route Index. Identifies which service instance is targeted; mapping from integer to route name is program-specific. A value of
0x00means "infer the route for thisinterface_id" and is valid only if exactly one instance of that interface exists in the target program. - Reserved byte. Always zero in v1 (no error flag semantics). Future revisions may reinterpret this byte. Receivers MUST treat non-zero values as invalid for v1 headers.
- Extensions. Optional, structured as a TLV stream (see below).
header lengthMUST cover the base header plus all extension bytes.
Commands and queries
- Collect all commands and queries defined in the interface.
- Sort by name (lexicographically).
- Assign
entry_idsequentially starting at zero. Implementations MAY pin these assignments externally (e.g., lockfile) if backwards compatibility is critical.
Events
- Collect all events defined in the interface.
- Sort by name (lexicographically).
- Assign
entry_idsequentially starting at zero. Implementations MAY pin these assignments externally (e.g., lockfile) if backwards compatibility is critical.
route_idx values are assigned by the program author. Non-zero values identify specific interface instances and are mapped via a manifest or registry that downstream tooling can inspect. 0x00 is reserved as an inference sentinel: the receiver may resolve it only when exactly one instance with the given interface_id exists; otherwise the message is invalid/ambiguous.
Extensions appear immediately after the base header (offset 16) and continue until header length bytes have been consumed. Each extension record uses a Tag-Length-Value format:
struct Extension {
type_id: u8; // 0 reserved
flags: u8; // extension-specific flags/version (0 if unused)
length: u16; // little-endian payload length in bytes
data: [u8; length];
}
Parsing rules:
- For v1,
header lengthMUST equal0x10. Extension-sized headers are reserved for future versions. - Each extension MUST fit entirely within
header length; otherwise the header is invalid. - Unknown
type_ids MUST be skipped using the declaredlength, ensuring forward compatibility. type_id = 0is reserved and MUST NOT appear on the wire.- Implementations MAY standardize specific
type_ids (e.g., correlation IDs) and document their payload structure separately.
- Programs SHOULD compute
interface_idandentry_idat compile time (e.g., using macros or IDL generators) and embed them as constants. This ensures routing does not depend on runtime hashing. - When sending a message:
- Fill the 16-byte base header.
- Append any extensions (optional).
- Append the SCALE-encoded payload immediately after
header length.
- Receivers MUST examine the magic + version to interpret the header. They MAY reject messages with unknown versions or with a v1 header length different from
0x10. - Off-chain tools (explorers, RPC gateways) can read the same header to classify messages without executing WASM.
The Sails header identifies the interface, entry, and route of the payload on both calls and replies.
It does not link a reply to its originating request. Gear's message graph, including MessageId and reply metadata, carries request/reply correlation.
Indexers should use the runtime message relationship for correlation and the Sails header for payload classification.
Implementations MUST ensure:
- Service names never influence
interface_id. - Ordering of extends/functions/events/types is stable (sort rules above).
entry_idassignment methodology is documented; ideally frozen via manifest for upgraded interfaces.
A receiver that detects a potential header SHOULD apply at least the following checks:
- Magic: The first two bytes are
0x47 0x4D; otherwise treat the payload as legacy/unheadered. - Version:
version == 0x01. Unknown versions may be rejected or parsed according to future specs. - Header length: For v1,
hlen == 0x10. Reject smaller, larger, or payload-extending values. - Reserved byte: For v1 the byte at offset 15 MUST be zero. Non-zero values indicate incompatible behavior unless a future version redefines it.
- Extensions: If
hlen > 0x10, ensure each TLV record fits within the declared header length; malformed TLVs invalidate the header. - Route inference (
route_idx == 0x00): Resolve only if exactly one matchinginterface_idinstance exists. If none or many exist, reject as invalid/ambiguous.
Once the header passes validation, proceed to decode routing identifiers and payload.
- Headers with invalid magic, unsupported versions, non-zero reserved byte (in v1), or header lengths that extend beyond the payload MUST be rejected before processing.
- Extension parsing must respect
lengthfields strictly; implementations SHOULD cap accepted header lengths to prevent resource exhaustion. - Tooling should treat
interface_id,entry_id, androute_idxas untrusted inputs - only use them after successful validation and cross-checking against known manifests. Forroute_idx = 0x00, perform ambiguity checks before resolving a concrete route.
Assume interface_id = 0xA1B2C3D4E5F60718 and entry_id = 0x0200 (Foo::Bar), route inference requested (route_idx = 0x00), base header only. The receiver may accept this only if exactly one instance of that interface exists.
47 4D 01 10 18 07 F6 E5 D4 C3 B2 A1 02 00 00 00
^magic ^ver ^hlen ^interface_id LE ^entry_id ^route ^reserved
Payload bytes follow immediately after byte offset 11.
interface_id = 0x0123456789ABCDEF, entry_id = 0x0500, route_idx = 0x02, reserved byte remains zero.
47 4D 01 10 EF CD AB 89 67 45 23 01 05 00 02 00
Q: How do I map route_idx back to route names?
Provide a manifest that associates each route name with a non-zero 1-byte index. Tooling can distribute this manifest alongside the program binary or IDL. A route_idx of 0x00 means the sender requests route inference, so tooling should verify the target program exposes exactly one matching interface_id instance before resolving it.
Q: Can I include additional metadata in the header?
The v1 header is fixed to 16 bytes and does not permit additional metadata fields. Header extensions are reserved for future versions of the specification.
Q: How do off-chain tools verify the header?
Check the magic/version, read interface_id and entry_id, and consult a registry or IDL manifest to interpret the payload.
- RFC 8785 - JavaScript Object Notation (JSON) Canonicalization Scheme.
- BLAKE3 cryptographic hash function specification.
- RFC 7914-style domain separation guidelines (domain-specific hashing).
- SCALE Codec Specification – defines the serialization format used for the payload that follows the header.
- Gear Protocol Documentation – describes the underlying asynchronous messaging context in which Sails Header operates.
This document is intentionally implementation-neutral. Any language or framework can adopt the Sails Header provided it implements the canonical hashing rules and the binary layout defined above.