Skip to main content
Each Tolk contract has specials entrypoints — reserved functions to handle various types of messages. From the language perspective, handling an incoming message is just ordinary code.

onInternalMessage

In 99% of cases, a contract handles internal messages. An end user does not interact with a contract directly; instead, interaction occurs through the user’s wallet, which sends an internal message to the contract. The entrypoint is declared this way:
A basic guideline is the following:
  • For each incoming message, declare a struct with a unique 32-bit prefix (opcode).
  • Declare a union type “all available messages”.
  • Parse this union from in.body and match it over structures.

Brief explanation of the example

  • struct declares any business data (particularly, messages and storage). It’s like a TypeScript class. See structures.
  • (0x12345678) is called a “message opcode” (32 bit). Unique prefixes help routing in.body (binary data).
  • AllowedMessage is a type alias for a union type, similar to TypeScript (and in some way, to Rust’s enums). See union types.
  • in: InMessage provides access to message properties: in.body, in.senderAddress, and so on.
  • T.fromSlice parses binary data to T. See auto-serialization. Combined with lazy, it’s done on demand. See lazy loading.
  • match routes a union type. Inside each branch, type of msg is narrowed (called “smart cast”). See pattern matching.
  • throw 0xFFFF is a standard reaction on “unrecognized message”. But typically, a contract should ignore empty messages: it’s “just top-up balance” (send some Toncoin, body is empty). That’s why throw is wrapped by if or assert. See conditions and loops.
Bounced messages do not enter onInternalMessage. Read onBouncedMessage below.

How to define and modify contract’s storage

A storage is also a regular structure. It’s convenient to add load and store methods accessing blockchain’s persistent data:
Then, in match cases, invoke those methods:
Alternatively, load the storage above match instead of doing it in every branch. For further reading, consider contract’s storage in details.

Old-fashioned onInternalMessage

In old times, a handler was declared this way in FunC language:
Tolk also allows to use old-style declarations:
For instance, after using convert-func-to-tolk, the result is exactly as above. Prefer using a modern solution with InMessage: it’s not only easier, but also cheaper in gas terms. Transitioning after auto-conversion is trivial:
  • myBalance => contract.getOriginalBalance() (it’s not a property of a message, it’s a state of a contract)
  • msgValue => in.valueCoins
  • msgFull => use in.senderAddress etc., no need for manual parsing
  • msgBody => in.body

onBouncedMessage

A special entrypoint for handling bounced messages also exists. When a contract sends a message to another, but the another fails to handle it, a message is bounced back to the sender.
InMessageBounced is very similar to InMessage. The only difference is that in.bouncedBody has a special shape depending on how a message was originally sent.

BounceMode in createMessage

When sending a message, it’s required to specify bounce behavior:
BounceMode is an enum with these options available:
  • BounceMode.NoBounce
  • BounceMode.Only256BitsOfBodyin.bouncedBody will be “0xFFFFFFFF” + first 256 bits (cheapest, and often sufficient)
  • BounceMode.RichBounce — allows to access the entire originalBody; also, gasUsed, exitCode, and some other properties of a failed request are available (most expensive)
  • BounceMode.RichBounceOnlyRootCell — the same, but originalBody will contain only a root cell

How to handle in.bouncedBody

Depending on BounceMode, in.bouncedBody will look differently. If all bounceable messages are sent with a cheap Only256BitsOfBody:
If you use RichBounce, that’s the way:
Mixing different modes (sending some messages as cheap and others as rich) complicates handling and is discouraged. So, the binary body of an outgoing message (TransferMessage above) is either in.bouncedBody (256 bits) or rich.originalBody (the entire slice). To handle this correctly,
  • create a union “all messages theoretically bounceable”
  • handle it with lazy in onBouncedMessage

onExternalMessage

Besides internal messages, a contract may handle external messages that arrive from off-chain. For example, a wallet contract handles external messages, performing signature validation via a public key.
When a contract accepts an external message, it has a very limited gas amount for execution. Once a request is validated, remember to call acceptExternalMessage() to increase this limit. Also, commitContractDataAndActions() might be useful. Both are standard functions with detailed comments, an IDE provides them.

Other reserved entrypoints

Besides the functions above, several predefined prototypes also exist:
  • fun onTickTock — triggers when tick and tock transactions occur
  • fun onSplitPrepare and fun onSplitInstall — prepared for split/install transactions, currently unavailable in the blockchain
  • fun main is often used for short snippets and demos
This program is correct:
It compiles, runs, and pushes 123 onto the stack. Its TVM method_id is 0.