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:- For each incoming message, declare a
structwith a unique 32-bit prefix (opcode). - Declare a union type “all available messages”.
- Parse this union from
in.bodyandmatchit over structures.
Brief explanation of the example
structdeclares 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 routingin.body(binary data).AllowedMessageis a type alias for a union type, similar to TypeScript (and in some way, to Rust’s enums). See union types.in: InMessageprovides access to message properties:in.body,in.senderAddress, and so on.T.fromSliceparses binary data toT. See auto-serialization. Combined withlazy, it’s done on demand. See lazy loading.matchroutes a union type. Inside each branch, type ofmsgis narrowed (called “smart cast”). See pattern matching.throw 0xFFFFis 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 whythrowis wrapped byiforassert. See conditions and loops.
onInternalMessage.
Read onBouncedMessage below.
How to define and modify contract’s storage
A storage is also a regular structure. It’s convenient to addload and store methods accessing blockchain’s persistent data:
match cases, invoke those methods:
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:
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.valueCoinsmsgFull=> usein.senderAddressetc., no need for manual parsingmsgBody=>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 specifybounce behavior:
BounceMode is an enum with these options available:
BounceMode.NoBounceBounceMode.Only256BitsOfBody—in.bouncedBodywill be “0xFFFFFFFF” + first 256 bits (cheapest, and often sufficient)BounceMode.RichBounce— allows to access the entireoriginalBody; also,gasUsed,exitCode, and some other properties of a failed request are available (most expensive)BounceMode.RichBounceOnlyRootCell— the same, butoriginalBodywill 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:
RichBounce, that’s the way:
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
lazyinonBouncedMessage
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.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 occurfun onSplitPrepareandfun onSplitInstall— prepared for split/install transactions, currently unavailable in the blockchainfun mainis often used for short snippets and demos