Key features of createMessage
- Supports extra currencies
- Supports
stateInit(code and data) with automatic address computation - Supports workchains
- Supports sharding (formerly
splitDepth) - Integrated with auto-serialization of
body - Automatically detects body ref or not
- More efficient than handwritten code
The concept is based on union types
There is a variety of interacting between contracts. When you explore FunC implementations, you notice that:- sometimes, you “send to an address (slice)”
- … but sometimes, you “build the address (builder) manually”
- sometimes, you compose
StateInitfrom code+data - … but sometimes, you already have
StateInitas a ready cell - sometimes, you send a message to basechain
- … but sometimes, you have the
MY_workchainconstant and use it everywhere - sometimes, you just attach tons (msg value)
- … but sometimes, you also need extra currencies
- etc.
Extra currencies: union
When you don’t need them, you just attach msg value as tons:value is a union:
Destination: union
The same idea of union types spreads onto destination of a message.StateInit and workchains
Let’s start from an example. From a jetton minter, you are deploying a jetton wallet. You know wallet’s code and initial data:walletInitialState because, in TON, the address of a contract is — by definition — a hash of its initial state:
AutoDeployAddress. Here is how it’s declared in stdlib:
Sharding: deploying “close to” another contract
ThecreateMessage interface also supports initializing contracts in specific shards. Say you’re writing sharded jettons, and you want every jetton wallet to be in the same shard as the owner’s wallet.
In other words, your intention is:
- a jetton wallet must be close to the owner’s wallet
- this closeness is determined by a shard depth (syn. fixed prefix length, syn. split depth)
shard depth = 8:
| Title | Addr hash (256 bits) | Comment |
|---|---|---|
| closeTo (owner addr) | 01010101...xxx | owner’s wallet |
| shardPrefix | 01010101 | first 8 bits of closeTo |
| stateInitHash | yyyyyyyy...yyy | calculated by code+data |
| result (JW addr) | 01010101...yyy | jetton wallet in same shard as owner |
StateInit (besides code+data) — for correct initialization inside the blockchain. The compiler automatically embeds it.
But semantically, shard depth alone makes no sense. That’s why shard depth + closeTo is a single entity:
Body ref or not: compile-time calculation
In TON Blockchain, according to the specification, a message is a cell (flags, dest address, stateInit, etc.), and its body can be either inlined into the same cell or can be placed into its own cell (and be a ref). In FunC, you had to manually calculate whether it’s safe to embed body (you did it on paper or dynamically). In Tolk, you just passbody, and the compiler does all calculations for you:
- if
bodyis small, it’s embedded directly into a message cell - if
bodyis large or unpredictable, it’s wrapped into a ref
createMessage is declared:
body: RequestedInfo {...}, then TBody = RequestedInfo, and the compiler estimates its size:
- it’s small if its maximum size is less than 500 bits and 2 refs — then no ref
- it’s large if >= 500 bits or >= 2 refs — then “ref”
- it’s unpredictable if contains
builderorsliceinside — then ref
body is already a cell, it will be left as a ref, without any surprise:
body: someObj.toCell(), pass just body: someObj, let the compiler take care of everything.
Body is not restricted to structures
In practice, you usecreateMessage to send a message (sic!) to another contract — in the exact format as the receiver expects.
You declare a struct with 32-bit opcode and some data in it.
createMessage<(int32, uint64)>(...) and encoded correctly.
The example above just illustrates the power of the type system, no more.
Body can be empty
If you don’t need anybody at all, just leave it out:
body here? How is it expressed in the type system?” The answer is: never.
Actually, CreateMessageOptions is declared like this:
body in createMessage, it leads to the default TBody = never. And by convention, fields having never type are not required in a literal. It’s not that obvious, but it is definitely beautiful.
Don’t confuse StateInit and code+data, they are different
It’s incorrect to say that StateInit is code+data, because in TON, a full StateInit cell contents is richer (considerblock.tlb):
- it also contains fixed_prefix_length (formerly split_depth),
- it also contains ticktock info
- it also contains a library cell
- code and data are actually optional
toShard.fixedPrefixLength).
That’s why a structure code+data is named ContractState, NOT StateInit:
stateInit: ContractState | cell is named stateInit, emphasizing that StateInit can be initialized automatically from ContractState (or can be a well-formed rich cell).
Why not send, but createMessage?
You might ask: “why do we follow the patternval msg = createMessage(...); msg.send(mode) instead of just send(... + mode) ?”
Typically, yes — you send a message immediately after composing it. But there are also advanced use cases:
- not just send, but send and estimate fees,
- or estimate fees without sending,
- or get a message hash,
- or save a message cell to storage for later sending,
- or even push it to an action phase.
send(...) function, you have to dig into what body is actually being sent to understand what’s going on.
Why not provide a separate deploy function?
You might also ask: why do we joinstateInit as a destination for other use cases? Why not make a deploy() function that accepts code+data and drops stateInit from a regular createMessage?
The answer lies in terminology. Yes, attaching stateInit is often referred to as deployment, but it’s an inaccurate term. TON Blockchain doesn’t have a dedicated deployment mechanism. You send a message to some void — and if this void doesn’t exist, but you’ve attached a way to initialize it (code+data) — it’s initialized immediately and accepts your message.
If you wish to emphasize the deployment, give it a name:
Universal createExternalLogMessage
The philosophy is similar tocreateMessage. But external outs don’t have bounce; you don’t attach tons, etc. So, options for creating are different.
Currently, external messages are used only for emitting logs (for viewing them in indexers). But theoretically, they can be extended to send messages to the offchain.
Example:
dest and body, actually:
createMessage — just pass someObject, do NOT call toCell(), let the compiler decide whether it fits into the same cell or not. UnsafeBodyNoRef is also applicable.
Emitting external logs, example 1:
ExtOutLogBucket is a variant of a custom external address for emitting logs to the outer world. It includes some topic (arbitrary number), that determines the format of the message body. In the example above, you emit deposit event (reserving topic deposit = 123) — and external indexers will index your emitted logs by destination address without parsing body.