Обработка bounce-сообщений

Обработаешь bounce в Tact: rolled-back состояние, bounced<T> и лимит 224 бит полезной нагрузки.

Bounce

Если внутреннее сообщение с bounce: true падает у получателя (нет кода, throw, нехватка газа), сеть возвращает отправителю bounce-сообщение. Без обработчика bounced ты потеряешь и эффект у получателя, и согласованность своего storage.


Откат баланса при bounce

tact (fallback highlight)
// @tact-lang/compiler 1.6.13

message Credit {
    queryId: Int as uint64;
    amount: Int as coins;
}

message CreditNotification {
    queryId: Int as uint64;
    amount: Int as coins;
    customer: Address;
}

contract Ledger(
    balances: map<Address, Int>,
) {
    receive() {
        cashback(sender());
    }

    receive(msg: Credit) {
        let current: Int = self.balances.get(sender()) ?? 0;
        self.balances.set(sender(), current + msg.amount);

        message(MessageParameters{
            to: sender(),
            value: 0,
            mode: SendRemainingValue,
            bounce: true,
            body: CreditNotification{
                queryId: msg.queryId,
                amount: msg.amount,
                customer: sender(),
            }.toCell(),
        });
    }

    // Вернулись только поля, влезшие в 224 бита тела bounce
    bounced(msg: bounced<CreditNotification>) {
        let current: Int = self.balances.get(msg.customer) ?? 0;
        self.balances.set(msg.customer, current - msg.amount);
    }

    get fun balanceOf(user: Address): Int {
        return self.balances.get(user) ?? 0;
    }
}

Как это работает

  • Оптимистичное обновление: сначала меняешь своё состояние, потом шлёшь сообщение; при bounce — компенсируешь.
  • bounced<CreditNotification> — специальный тип: доступны только префикс полей в пределах ~224 бит тела.
  • bounce: true обязателен, если хочешь узнать об ошибке у контрагента.
  • Fallback bounced(raw: Slice) ловит типы без отдельного typed-handler.
  • Пустой receive() по-прежнему нужен для деплоя; bounce на него не заменяет.

Частые ошибки

Положил важное поле после «длинного» префикса в message → в bounced<T> его не будет; проектируй порядок полей под bounce.

Шлёшь с bounce: false «чтобы дешевле» → ошибка у получателя съедает value без компенсации у отправителя.

В bounce снова шлёшь наружу без лимита → риск цепочек и повторных расходов; компенсация должна быть локальной и простой.


Что дальше

Материалы gramdocs.tech носят образовательный характер и не являются финансовой, юридической или инвестиционной рекомендацией. Работа с блокчейном TON и токеном Gram связана с рисками потери средств. Правовая информация