Skip to main content
A consolidated summary of how each Tolk type is serialized into TL-B–compatible binary data.
Low-level detailsThis page assumes prior knowledge of TL-B and TVM. It is intended as a concise low‑level reference page.

int

Not serializable; use intN or other numeric types instead.

intN

  • fixed N-bit signed integer
  • TL-B intN
  • stored via {N} STI
  • loaded via {N} LDI

uintN

  • fixed N-bit unsigned integer
  • TL-B uintN
  • stored via {N} STU
  • loaded via {N} LDU

coins

  • alias to varuint16
  • TL-B VarUInteger 16
  • stored via STGRAMS
  • loaded via LDGRAMS

varintN for N = 16 or N = 32

  • variadic signed integer: 4/5 bits for len + 8*len bit number
  • TL-B VarInteger {N}
  • stored via STVARINT{N}
  • loaded via LDVARINT{N}

varuintN for N = 16 or N = 32

  • variadic unsigned integer: 4/5 bits for len + 8*len bit number
  • TL-B VarUInteger {N}
  • stored via STVARUINT{N}
  • loaded via LDVARUINT{N}

bool

  • one bit: ‘0’ or ‘1’
  • TL-B Bool
  • stored via 1 STI
  • loaded via 1 LDI resulting in 0 or -1

address

  • standard (internal) address; 267 bits: ‘100’ + workchain + hash
  • TL-B addr_std
  • stored via STSTDADDR
  • loaded via LDSTDADDR

address? (nullable)

  • internal or none address; 2 or 267 bits: null -> ‘00’, otherwise -> address
  • TL-B addr_none or addr_std
  • stored via STOPTSTDADDR
  • loaded via LDOPTSTDADDR

any_address

  • any valid TL-B address, from 2 to 523 bits
  • TL-B MsgAddress
  • stored via STSLICE
  • loaded via LDMSGADDR

cell and Cell<T>

  • a reference
  • TL-B ^Cell / ^T
  • stored via STREF
  • loaded via LDREF

cell? and Cell<T>? (nullable)

  • maybe reference (‘0’ or ‘1’+ref)
  • TL-B Maybe ^Cell / Maybe ^T
  • stored via STOPTREF
  • loaded via LDOPTREF

bitsN

  • just N bits
  • TL-B bitsN
  • stored via STSLICE, preceded by a runtime check that the slice contains exactly N bits and zero references; this check can be disabled using skipBitsNValidation = false.
  • loaded via LDSLICE / LDSLICEX (for N > 256)

RemainingBitsAndRefs

  • the remainder of a slice when reading, and a raw slice when writing
  • TL-B Cell
  • stored via STSLICE
  • loaded by copying current slice and assigning current to an empty one

builder and slice

Can be used for writing, not for reading. Not recommended, because they do not reveal internal structure and have unpredictable size. Auto-generated TypeScript wrappers are not able to parse them.

Structures

If a struct has a prefix, it’s written first. Then its fields are serialized sequentially.
struct (0x12345678) A {
    a: int8
    b: cell?
}

fun demo() {
    val a: A = {
        a: 123,
        b: createEmptyCell(),
    };
    // 41 bits and 1 ref: opcode + int8 + '1' + empty ref
    a.toCell()
}

32-bit prefixes (opcodes)

By convention, all messages (incoming and outgoing) use 32-bit prefixes:
struct (0x7362d09c) TransferNotification {
    queryId: uint64
    // ...
}

Not only 32-bit prefixes

Declaring messages with opcodes does not differ from declaring any other structs. Prefixes can be of any width:
  • 0x000F — 16-bit prefix
  • 0x0F — 8-bit prefix
  • 0b010 — 3-bit prefix
  • 0b00001111 — 8-bit prefix
Example. Let’s express the following TL-B scheme:
asset_simple$001 workchain:int8 ptr:bits32 = Asset;
asset_booking$1000 order_id:uint64 = Asset;
// ...
In Tolk, use structures and union types:
struct (0b001) AssetSimple {
    workchain: int8
    ptr: bits32
}

struct (0b1000) AssetBooking {
    orderId: uint64
}

type Asset = AssetSimple | AssetBooking // | ...
When deserializing, Asset will follow manually provided prefixes, see “union types” below. If a structure has a prefix, it is used consistently in all contexts (both standalone and within unions):
AssetBooking.fromSlice(s)   // expecting '1000...' (binary)
AssetBooking{...}.toCell()  // '1000...'

Type aliases

A type alias is identical to its underlying type unless a custom serializer is defined. Example. Need to implement a “variadic string” encoded as “len + data”:
len: (## 8)        // 8 bits of len
data: (bits len)   // 0..255 bits of data
To express this, create a type and define a custom serializer:
type ShortString = slice

fun ShortString.packToBuilder(self, mutate b: builder) {
    val nBits = self.remainingBitsCount();
    b.storeUint(nBits, 8);
    b.storeSlice(self);
}

fun ShortString.unpackFromSlice(mutate s: slice) {
    val nBits = s.loadUint(8);
    return s.loadBits(nBits);
}
And just use ShortString as a regular type — everywhere:
tokenName: ShortString
fullDomain: Cell<ShortString>
Method names packToBuilder and unpackFromSlice are reserved for this purpose, their signatures must match exactly as shown.

Enums

The serialization type can be specified manually:
// `Role` will be (un)packed as `int8`
enum Role: int8 {
    Admin,
    User,
    Guest,
}

struct ChangeRoleMsg {
    ownerAddress: address
    newRole: Role    // int8: -128 <= V <= 127
}
Otherwise, it is calculated automatically. For Role above, uint2 is sufficient to fit values 0, 1, 2:
// `Role` will (un)packed as `uint2`
enum Role {
	  Admin,
	  User,
	  Guest,
}
Input values are validated during deserialization. For enum Role: int8 any (input<0 || input>2) triggers exception 5 (integer out of range). Non-range values are also validated:
enum OwnerHashes: uint256 {
    id1 = 0x1234,
    id2 = 0x2345,
    ...
}

// on serialization, just "store uint256"
// on deserialization, "load uint256" + throw 5 if v not in [0x1234, 0x2345, ...]

Nullable types T? (except address?)

  • often called “Maybe”; ‘0’ or ‘1’+T
  • TL-B (Maybe T)
  • asm 1 STI + IF …
  • asm 1 LDI + IF …
The exception: address? is serialized as “internal or none” (2/267 bits): null -> ‘00’, otherwise -> address.

Union types T1 | T2 | ...

Rules for union type serialization:
  • T | null is TL/B Maybe T (‘0’ or ‘1’+T)
  • if all T_i have prefixes struct (0x1234) A, they are used
  • otherwise, a compiler auto-generates a prefix tree

Manual serialization prefixes

If all T_i have manual prefixes, they are used:
struct (0b001)  AssetSimple   { /* body1 */ }
struct (0b1000) AssetBooking  { /* body2 */ }
struct (0b01)   AssetNothing  {}

struct Demo {
    // '001'+body1 OR '1000'+body2
    e: AssetSimple | AssetBooking
    // '001'+body1 OR '1000'+body2 OR '01'
    f: AssetSimple | AssetBooking | AssetNothing
}
If a prefix exists for A but not for B, the union A | B cannot be serialized: it seems like a bug in code.

Auto-generated prefix tree

If T_i don’t have manual prefixes, the compiler generates a prefix tree. A two-component union T1 | T2 is TL/B Either (prefixes 0/1). For example, int32 | int64 becomes (‘0’+int32 or ‘1’+int64). Multi-component unions have longer prefixes. For example int32 | int64 | int128 | int256 forms a tree 00/01/10/11. General rules:
  • if null exists, it’s 0, all others are 1+tree (“maybe others”)
    • example: A|B|C|D|null => 0 | 100+A | 101+B | 110+C | 111+D
  • if no null, just distributed sequentially
    • example: A|B|C => 00+A | 01+B | 10+C
struct WithUnion {
    f: int8 | int16 | int32
}
This field will be packed as: ‘00’+int8 OR ‘01’+int16 OR ‘10’+int32. On deserialization, the same format is expected (prefix ‘11’ will throw an exception). Same for structs without a manual prefix:
struct A { ... }    // 0x... prefixes not specified
struct B { ... }
struct C { ... }

struct WithUnion {
    // auto-generated prefix tree: 00/01/10
    f: A | B | C
    // with null, like Maybe<A|B>: 0/10/11
    g: A | B | null
    // even this works; when '11', a ref exists
    h: A | int32 | C | cell
}

Tensors (T1, T2, ...)

Tensor components are serialized sequentially, in the same manner as structure fields.

tuple and typed tuples

Tuples cannot be serialized; serialization is not implemented for tuples. But tuples can be returned from get methods, since contract getters work via the stack, not serialization.

map<K, V>

  • maybe reference: ‘0’ (empty) or ‘1’+ref (dict contents)
  • TL-B HashmapE n X (follow hashmaps in TL-B)
  • stored via STDICT
  • loaded via LDDICT

Callables (...ArgsT) -> ResultT

Callables cannot be serialized. Lambdas may be used within contract logic but cannot be serialized for off‑chain responses.
See also: