Skip to content

Bounced messages

When a contract sends a message with the flag bounce set to true, and if the message isn’t processed properly, it will bounce back to the sender. This is useful when you want to ensure that the message has been processed properly and, if not, revert the changes.

Caveats

Currently, bounced messages in TON can have at most 256 bits of payload and no references, but the amount of useful data bits when handling messages in bounced receivers is at most 224, since the first 32 bits are occupied by the message opcode. This means that you can’t recover much of the data from the bounced message, for instance you cannot fit an address into a bounced message, since regular internal addresses need 267 bits to be represented.

Tact helps you to check if your message fits the limit, and if it doesn’t, suggests using a special type constructor bounced<M> for the bounced message receiver, which constructs a partial representation of the message that fits within the required limits.

Note that the inner message struct of the bounced<M> type constructor cannot be optional.

contract Bounce() {
// COMPILATION ERROR! Only named type can be bounced<>
bounced(msg: bounced<BB?>) {}
// ~~~~~~~~~~~~
}
message BB {}

Only the first message fields that fit into the 224 bits will be available to use in bounced message receivers. Sometimes you need to rearrange the order of the fields in your message so the relevant ones would fit into the limit.

contract Bounce() {
bounced(msg: bounced<TwoFields>) {
// COMPILATION ERROR! Maximum size of the bounced message is 224 bits...
msg.f2;
// ~~
}
}
message TwoFields {
f1: Int as uint224; // only this field will fit in a bounced message body
f2: Int;
}

For gas optimization reasons, unrecognized bounced messages are ignored by Tact contracts and do not cause erroneous, non-zero exit codes. That is, if there is no relevant bounced() message receiver or no fallback bounced receiver, messages sent from the contract, rejected by their intended recipient and bounced back to the original contract will not be processed, apart from collecting their value and paying any related fees.

This behavior is unlike the non-bounced messages such as regular internal or external messages, where if the contract does not handle the message, an error with exit code 130 is thrown: Invalid incoming message. Not throwing such errors for bounced messages is a common pattern on TON Blockchain, to which Tact adheres.

Bounced message receiver

To receive a bounced message, define a bounced() receiver function in your contract or a trait:

contract MyContract {
bounced(msg: bounced<MyMessage>) {
// ...
}
}

Fallback bounced message receiver

To process bounced messages manually, you can use a fallback catch-all definition that handles a raw Slice directly. Note that such a receiver will get all the bounced messages produced by your contract:

contract MyContract {
bounced(rawMsg: Slice) {
// ...
}
}

On TON, bounced message bodies have a 32 bits prefix, where all bits are set, i.e., with 0xFFFFFFFF. However, since that prefix is always the same for all bounced messages, Tact cuts it off for your convenience, keeping only the remaining 256 bits of the payload, which usually starts with a 32-bit message opcode.

Contract storage handling

Bounced message receivers handle contract storage just as internal message receivers do. In addition, the empty return statement and the throw(0) patterns work the same.