Book
Sending messages

Sending messages

TON blockchain is message-based — to communicate with other contracts and to deploy new ones you need to send messages.

Messages in Tact are commonly composed using a built-in Struct SendParameters, which consists of:

FieldTypeDescription
bounceBoolWhen set to true (default) message bounces back to the sender if the receiver contract doesn't exist or wasn't able to process the message.
toAddressReceiver internal Address in TON blockchain.
valueIntThe amount of nanoToncoins you want to send with the message. This value is usually used to cover forward fees (opens in a new tab), unless the optional flag SendPayGasSeparately is used.
modeIntAn 8-bit value that configures how to send a message, defaults to 00. See: Message mode.
bodyCell?Optional message body as a Cell
codeCell?Optional initial code of the contract (the compiled bytecode)
dataCell?Optional initial data of the contract (arguments of init() function of the contract)

Fields code and data are what's called an init package, which is used in deployments of new contracts.

Send simple reply

The simplest message is a reply to the incoming message returning all excess value of a message:

receive() {
    self.reply("Hello, World!".asComment()); // asComment converts a String to a Cell with a comment
}

Send message

If you need more advanced logic you can use the send() function and SendParameters Struct directly.

In fact, the previous example with .reply() can be made using the following call to send() function:

receive() {
    send(SendParameters{
        // bounce is set to true by default
        to: sender(), // sending message back to the sender
        value: 0, // don't add Toncoins to the message...
        mode: SendRemainingValue | SendIgnoreErrors, // ...except for ones received from the sender due to SendRemainingValue
        body: "Hello, World".asComment(), // asComment converts a String to a Cell with a comment
    });
}

Another example sends a message to the specified Address with a value of 11 TON and the body of a comment with a String "Hello, World!":

let recipient: Address = ...;
let value: Int = ton("1");
send(SendParameters{
    // bounce is set to true by default
    to: recipient,
    value: value,
    mode: SendIgnoreErrors, // will send the message despite any errors
    body: "Hello, World!".asComment(),
});

The optional flag SendIgnoreErrors means that even when an error occurs during message sending next messages would be sent anyway. No error during the sending phase would revert a transaction.

Send typed message

To send a binary typed message you can use the following code:

let recipient: Address = ...;
let value: Int = ton("1");
send(SendParameters{
    // bounce is set to true by default
    to: recipient,
    value: value,
    mode: SendIgnoreErrors, // don't stop in case of errors
    body: SomeMessage{arg1: 123, arg2: 1234}.toCell(),
});

Deploy contract

To deploy a contract you need to calculate its address and initial state with initOf, then send them in the initialization message:

let init: StateInit = initOf SecondContract(arg1, arg2);
let address: Address = contractAddress(init);
let value: Int = ton("1");
send(SendParameters{
    // bounce is set to true by default
    to: address,
    value: value,
    mode: SendIgnoreErrors, // don't stop in case of errors
    code: init.code,
    data: init.data,
    body: "Hello, World!".asComment(), // not necessary, can be omitted
});

Outbound message processing

Each transaction on TON Blockchain consists of multiple phases (opens in a new tab). Outbound messages are evaluated in compute phase (opens in a new tab), but are not sent in that phase. Instead, they're queued in order of appearance for the action phase (opens in a new tab), where all actions listed in compute phase (opens in a new tab), like outbound messages or reserve requests, are executed.

As all the values are computed in compute phase (opens in a new tab), all the fees computed by the end of it, and exceptions do not revert the transaction during action phase (opens in a new tab), outbound message sends can fail without bounce due to unsufficient action fees (opens in a new tab) or forward fees (opens in a new tab).

Consider the following example:

// This contract initially has 0 nanoToncoins on the balance
contract FailureIsNothingButAnotherStep {
    // And all the funds it gets are obtained from inbound internal messages
    receive() {
        // 1st outbound message evaluated and queued (but not sent yet)
        send(SendParameters{
            to: sender(),
            value: ton("0.042"), // plus forward fee due to SendPayGasSeparately
            mode: SendIgnoreErrors | SendPayGasSeparately,
        });
 
        // 2nd outbound message evaluated and queued (but not sent yet, and never will be!)
        send(SendParameters{
            to: sender(),
            value: 0,
            mode: SendRemainingValue | SendIgnoreErrors,
        });
    }
}

There, the second message won't actually be sent:

  • After finishing the compute phase (opens in a new tab), the remaining value R\mathrm{R} of the contract is computed.

  • During the outbound message processing and assuming that there was enough value provided in the inbound message, the first message leaves R(0.042+forwardfees)\mathrm{R} - (0.042 + \mathrm{forward-fees}) nanoToncoins on the balance.

  • When the second message is processed, contract tries to send R\mathrm{R} nanoToncoins, but fails to do so because there is already a smaller amount left.

💡

Read more about all message sending functions in the Reference: