Обработка отклонённой транзакции пользователем

Отличишь отказ пользователя (код 300) от других ошибок sendTransaction и корректно обновишь UI.

Отказ — нормальный исход

Пользователь часто закрывает кошелёк или жмёт Reject. В TON Connect это не «падение сети», а ответ протокола: USER_REJECTS_ERROR, код 300. UI должен показать спокойный статус («Отменено»), а не красный «Error».

Никогда не предполагай, что после sendTransaction транзакция точно ушла в сеть.


Разбираем ошибку Reject

tsx
// @tonconnect/ui-react ^3.0.0
import { useState } from "react";
import {
  useTonConnectUI,
  useTonWallet,
  UserRejectsError,
} from "@tonconnect/ui-react";

type PayStatus = "idle" | "pending" | "sent" | "rejected" | "failed";

function isUserReject(e: unknown): boolean {
  return (
    typeof e === "object" &&
    e !== null &&
    "code" in e &&
    (e as { code: number }).code === 300
  );
}

export function PayWithRejectHandling() {
  const [tonConnectUI] = useTonConnectUI();
  const wallet = useTonWallet();
  const [status, setStatus] = useState<PayStatus>("idle");

  async function pay() {
    if (!wallet) {
      await tonConnectUI.openModal();
      return;
    }

    setStatus("pending");
    try {
      const result = await tonConnectUI.sendTransaction({
        validUntil: Math.floor(Date.now() / 1000) + 300,
        network: "-239",
        messages: [
          {
            address:
              "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N",
            amount: "10000000", // 0.01 Gram
          },
        ],
      });
      setStatus("sent");
      console.log(result.boc);
    } catch (e) {
      if (e instanceof UserRejectsError || isUserReject(e)) {
        setStatus("rejected");
        return;
      }
      setStatus("failed");
      console.error(e);
    }
  }

  return (
    <div>
      <button type="button" onClick={pay} disabled={status === "pending"}>
        Оплатить
      </button>
      {status === "rejected" && <p>Ты отменил транзакцию в кошельке</p>}
      {status === "failed" && <p>Не удалось отправить. Попробуй ещё раз</p>}
      {status === "sent" && <p>Отправленопроверь эксплорер</p>}
    </div>
  );
}

Коды протокола, с которыми сталкиваешься чаще всего:

КодИмяЧто делать в UI
300USER_REJECTS_ERROR«Отменено», без ретрая по кругу
1BAD_REQUEST_ERRORПроверь messages / validUntil / адрес
400METHOD_NOT_SUPPORTEDКошелёк не умеет метод — другой кошелёк или упростить запрос

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

sendTransaction возвращает Promise: resolve → пользователь подтвердил и кошелёк отдал boc; reject → отказ или ошибка.

UserRejectsError / code === 300 — сознательный отказ, не баг твоего кода.

После Reject не помечай заказ как оплаченный и не списывай баланс в своём бэкенде — on-chain подтверждения нет.

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

Показываешь Reject как «ошибка сервера» → пользователь думает, что сломалось приложение. Отделяй 300 от остальных кодов.

Сразу открываешь модалку снова после Reject → выглядит как спам. Дай кнопку «Повторить» по желанию пользователя.

Считаешь отсутствие boc успехом → успех только resolve Promise (и дальше — проверка в сети, если критично для бизнеса).


Что дальше

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