Message-driven execution
This section covers how messages trigger transactions in TON. You'll learn how accounts communicate through messages, how message types differ, and how to construct and send messages to initiate on-chain execution.
Messages
In the TON blockchain, a message is the fundamental unit of interaction between accounts (smart contracts). All actions, state changes, and logic execution within accounts are triggered by messages.
Transaction and message real-life examples
Let’s explore how transactions and messages work in TON through an analogy. Imagine the TON blockchain as a unique city in the global world — the Internet. In this city, the rules are strict: residents never meet in person, and the only way to communicate is via a postal service. People receive messages asking them to perform a task, complete it privately in their homes, and then send the results back to others.
Not every message makes it to the mailbox — if it fails to arrive due to insufficient postage, it’s discarded. But when a message does arrive, the resident picks up exactly one incoming message, locks themselves inside, and doesn’t come out until the task is completed. While they’re working, new letters may arrive — but they ignore them completely.
If they realize mid-task that some necessary information is missing, they cannot pause to ask for clarification. In that case, they must declare the task as failed and, at best, return leftover resources along with scraps of the original instructions.
If the task is successful, the person, as specified in the original message:
- send new messages to other residents, or
- store the resulting items safely in their home.
After finishing, they return to their mailbox and retrieve the next incoming message.
Now imagine that for each work session, the person keeps a detailed journal. To keep their work organized and separate, each journal entry is called a transaction. Each transaction contains:
- the full content of the incoming message (the task),
- a note on what was produced and where it was stored,
- and, optionally, information about any messages sent to others.
Messages delivered via the postal system between residents are called internal messages. Messages from the outside world also reach the city. These have a specified recipient, but their sender is unknown — they are known as external incoming or external-in messages.
While external-in and internal messages fulfil a similar role from the actor’s perspective, each actor may handle them differently. Some may process them normally, while others might completely ignore messages from the outside world — like someone who refuses to open letters from strangers.
Message structure: TL-B
Now, let’s look at the data contained in the messages. TON distinguishes between external and internal messages, both of which are capable of carrying data.
In TON, all data is represented using cells. To serialize data into a cell, TON relies on a well-established standard called TL-B (Type Language – Binary).
Native message structure
In TON, all messages adhere to a unified schema that defines their structure and serialization.
message$_ {X:Type} info:CommonMsgInfo
init:(Maybe (Either StateInit ^StateInit))
body:(Either X ^X) = Message X;
info: CommonMsgInfo
— contains metadata about the message.init: (Maybe (Either StateInit ^StateInit))
— optional field used to initialize a new account or update the existing one.body: (Either X ^X)
— the main payload of the message; can be embedded directly or stored as a reference.
Message types
There are three types of messages in TON:
- External incoming: sent from outside the blockchain → received by a smart contract
- Internal: sent from a smart contract → received by a smart contract
- External outgoing: sent from a smart contract → received outside the blockchain (unknown actor)
External incoming messages
Functional role
External incoming or external-in messages serve as the primary entry point for the outside world to interact with the TON blockchain. Any user can send arbitrary data to any smart contract, and it’s up to the contract to decide how to handle it. Technically, any account can receive external messages. However, how a specific contract processes an external-in message depends entirely on its internal logic. The most common type of contract that handles such messages is the wallet contract.
Typical sources of external-in messages include:
- users of wallet applications
- validators
- DApp services
Although external-in messages are rarely used in the core logic of smart contracts, they are essential for integrating external actions into the blockchain. Any interaction that originates outside TON — such as sending TON or interacting with a DEX — begins with an external-in message. The most common example of this is a wallet contract, which receives the user’s instruction and then relays it by sending internal messages to other contracts.
Sending an external-in message
The term message is closely related to a transaction, but they serve different purposes and should not be confused:
- A message is a data packet exchanged between smart contracts that contains instructions for an action.
- A transaction is the result of executing a smart contract in response to an incoming message. During execution, the contract may update its state and produce one or more outgoing messages.
An external-in message is a message whose CommonMsgInfo
header uses the ext_in_msg_info$10
structure.
message$_ {X:Type} info:CommonMsgInfo
init:(Maybe (Either StateInit ^StateInit))
body:(Either X ^X) = Message X;
TL-B:
//external incoming message
ext_in_msg_info$10 src:MsgAddressExt dest:MsgAddressInt
import_fee:Grams = CommonMsgInfo;
Since an external-in message is sent into the blockchain from the outside, the initiator is an external actor. This can be either a wallet contract or custom code interacting with blockchain API services.
To send an external-in message to a smart contract, you need to:
- Construct a data structure that conforms to the TL-B schema
- Submit this structure to the blockchain using an API service
For example, in Blueprint, there is a built-in helper that dynamically assembles this structure for the developer.
//@ton/blueprint 0.36.1
import { Address, beginCell } from '@ton/ton';
import { NetworkProvider } from '@ton/blueprint';
export async function run(provider: NetworkProvider, args: string[]) {
//Mainnet address : 'EQAyVZ2rDnEDliuaQJ3PJFKiqAS-9fOm9s7DG1y5Ta16zwU2'
const address = Address.parse('EQAyVZ2rDnEDliuaQJ3PJFKiqAS-9fOm9s7DG1y5Ta16zwU2');
//Switch address for the Testnet :
//const address = Address.parse('kQAyVZ2rDnEDliuaQJ3PJFKiqAS-9fOm9s7DG1y5Ta16z768');
// Get current seqno using blueprint's provider
const contractProvider = provider.provider(address);
const result = await contractProvider.get('seqno', []);
const currentSeqno = result.stack.readNumber();
// Send external message with current seqno
return contractProvider.external(beginCell().storeUint(currentSeqno, 32).endCell());
}
- Prepare a wallet application (e.g., Tonkeeper) that you’ll use to send the external message
- Install the Blueprint project locally:
npm create ton@latest
- Add the provided script to your Blueprint project under the
scripts
directory, for example:blueprintproject/scripts/yourscript.ts
- Run the script with the command:
npx blueprint run yourscript
Internal messages
Functional role
An internal message is a message sent from one smart contract to another within the TON blockchain. When a contract receives an internal message, it can reliably determine:
- how many TON coins are attached
- which contracts are the sender and the recipient
The blockchain ensures the integrity of this information, allowing the contract to be trusted and used safely. TL-B:
A contract always initiates internal messages — they are the result of a transaction’s execution. In other words, as the name implies, the sender of an internal message is always a smart contract.
The most common way to send an internal message is by first sending an external message to a contract that contains the logic to forward the internal message to another contract.
//internal message
int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool
src:MsgAddressInt dest:MsgAddressInt
value:CurrencyCollection ihr_fee:Grams fwd_fee:Grams
created_lt:uint64 created_at:uint32 = CommonMsgInfo;
Structure | Description |
---|---|
int_msg_info$0 | Indicates an internal message. The $0 tag means that CommonMsgInfo starts with a 0 bit. |
ihr_disabled | Flag indicating whether Hypercube Routing (IHR) is disabled. |
bounce | If set to 1, the message will be bounced in case of a processing error. |
bounced | Indicates that the message is a result of a bounce. |
src | Address of the smart contract that sent the message. |
dest | Address of the destination smart contract. |
value | Structure describing the amount and type of value transferred in the message. |
ihr_fee | Fee for delivery via Hypercube Routing. |
fwd_fee | Fee for forwarding the message. |
created_lt | Logical time at which the message was created, used for ordering contract actions. |
created_at | Unix timestamp of when the message was created. |
The first thing a developer needs to specify is the amount of Toncoin attached to the internal message value
and the destination address dest
.
In this example, an external message is sent to the wallet contract with additional instructions for sending an internal message. As a result of processing the transaction, the wallet contract sends an outgoing internal message with the specified value
and recipient address
.
//@ton/blueprint 0.36.1
import { Address } from '@ton/ton';
import { NetworkProvider } from '@ton/blueprint';
export async function run(provider: NetworkProvider, args: string[]) {
const address = Address.parse('kQBUCuxgGsF6znHM_yNmnV_EwtlmdvmDzqTxiWHJip2ux6Wn');
const contractProvider = provider.provider(address);
return contractProvider.internal(provider.sender(), {
value: '0.01',
});
}