Таймауты и отложенные сообщения

Реализуешь time-lock на now() и двухфазное действие с timeout — без иллюзии «сообщение выполнится само через N секунд».

Таймауты на TON

TVM не планирует будущие транзакции сам по себе. «Отложить действие» = записать unlockAt в storage и принять второе сообщение после now() >= unlockAt. Внешний бот, кошелёк или другой контракт должен пнуть контракт, когда время пришло.


Vesting-claim с задержкой

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

message Schedule {
    amount: Int as coins;
    unlockAt: Int as uint32; // unix seconds
}

message Claim {}

contract Timelock(
    owner: Address,
    beneficiary: Address,
    amount: Int as coins,
    unlockAt: Int as uint32,
    scheduled: Bool,
) {
    receive() {
        cashback(sender());
    }

    receive(msg: Schedule) {
        require(sender() == self.owner, "not owner");
        require(!self.scheduled, "already scheduled");
        require(msg.unlockAt > now(), "unlock must be in the future");
        require(msg.amount > 0, "amount");

        self.amount = msg.amount;
        self.unlockAt = msg.unlockAt;
        self.scheduled = true;
        cashback(sender());
    }

    receive(_: Claim) {
        require(sender() == self.beneficiary, "not beneficiary");
        require(self.scheduled, "nothing scheduled");
        require(now() >= self.unlockAt, "too early");

        let payout: Int = self.amount;
        self.amount = 0;
        self.scheduled = false;

        message(MessageParameters{
            to: self.beneficiary,
            value: payout,
            mode: SendPayFwdFeesSeparately,
            bounce: false,
            body: null,
        });
    }

    get fun unlockAt(): Int {
        return self.unlockAt;
    }

    get fun secondsLeft(): Int {
        let t: Int = now();
        return t >= self.unlockAt ? 0 : self.unlockAt - t;
    }
}

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

  • now() — unix-время валидатора в секундах; для storage сериализуй как uint32, если хватает диапазона.
  • Schedule только записывает намерение; деньги для выплаты должны уже быть на балансе контракта (или прийти отдельным пополнением).
  • Claim — триггер после дедлайна; без него средства так и останутся на контракте.
  • Тот же паттерн используют delayed-upgrade: Upgrade → жди timeout → Confirm.
  • Не путай с created_at в заголовке сообщения: это метаданные сети, не таймер исполнения.

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

Ждёшь, что контракт «сам проснётся» в unlockAt → нужен внешний claim/keeper.

Сравниваешь now() с миллисекундами из JS → в TON секунды; раздели Date.now() на 1000.

bounce: true на финальном payout бенефициару-кошельку → при редких сбоях деньги вернутся на timelock; для простых выплат часто ставят bounce: false осознанно.


Что дальше

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