Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 08.03 · 25 мин
Средний
TON ConnectTransactionsMessagesnanoTONTON

Отправка транзакций

Отправка транзакций через TON Connect — это основной способ взаимодействия пользователя с блокчейном из вашего dApp. Через TON Connect можно отправлять сложные транзакции: вызовы смарт-контрактов, мультисообщения и даже деплой контрактов. Правильная реализация отправки транзакций критична для безопасности пользовательских средств.

После подключения кошелька через TON Connect dApp может запрашивать у пользователя подписание транзакций. В этом уроке разберём формат запроса, работу с nanoTON, payload для вызова контрактов и обработку ошибок.


sendTransaction

Для отправки транзакции используйте хук useTonConnectUI():

import { useTonConnectUI } from '@tonconnect/ui-react';

function SendButton() {
  const [tonConnectUI] = useTonConnectUI();

  const handleSend = async () => {
    const result = await tonConnectUI.sendTransaction({
      validUntil: Math.floor(Date.now() / 1000) + 300,
      messages: [
        {
          address: 'EQBYivdc0GAk-nnczaMnYNuSjpeXu2nJS3DZ4KqLjosX5sVC',
          amount: '100000000',
        },
      ],
    });

    console.log('BOC:', result.boc);
  };

  return <button onClick={handleSend}>Отправить 0.1 TON</button>;
}

При вызове sendTransaction SDK отправляет запрос кошельку через bridge. Кошелёк показывает пользователю детали транзакции и просит подтверждение. После подписания кошелёк возвращает подписанный BOC (Bag of Cells).


Формат запроса

interface SendTransactionRequest {
  validUntil: number;      // Unix timestamp -- срок действия
  network?: string;        // '-239' (mainnet) или '-3' (testnet)
  messages: Message[];     // Массив исходящих сообщений (1-4)
}

interface Message {
  address: string;         // Адрес получателя
  amount: string;          // Сумма в nanoTON (строка)
  payload?: string;        // BOC-encoded тело сообщения (base64)
  stateInit?: string;      // BOC-encoded stateInit для деплоя (base64)
}

Ключевые поля

validUntil — Unix timestamp, после которого транзакция считается просроченной. Кошелёк откажется подписывать просроченную транзакцию.

WARNING

Всегда устанавливайте validUntil

Транзакция без ограничения по времени — это угроза безопасности. Если пользователь не подтвердит транзакцию сразу, она может быть подписана позже, когда условия уже изменились (курс, баланс контракта и т.д.). Рекомендуемый срок: 5 минут (300 секунд).

network — идентификатор сети. Если указан, кошелёк проверит, что подключён к правильной сети. Если не указан, транзакция отправится в текущую сеть кошелька.

messages — массив сообщений. TON позволяет отправлять до 4 сообщений в одной транзакции (ограничение внешнего сообщения кошелька v4/v5).


Работа с nanoTON

Суммы в TON Connect передаются в nanoTON (1 TON = 10^9 nanoTON) как строки, а не числа:

1 TON       = '1000000000'      (10^9)
0.1 TON     = '100000000'       (10^8)
0.05 TON    = '50000000'        (5 * 10^7)
0.001 TON   = '1000000'         (10^6)

Почему строки?

JavaScript Number может хранить целые числа точно только до 2^53 (примерно 9 * 10^15). Для больших сумм (миллионы TON) точность теряется. Строки и BigInt решают эту проблему:

// Правильно: BigInt
const amount = BigInt(0.1 * 1e9).toString(); // '100000000'

// Или используя @ton/core:
import { toNano } from '@ton/core';

const amount = toNano('0.1').toString(); // '100000000'
TIP

Используйте toNano() из @ton/core

Функция toNano('0.1') безопасно конвертирует строковое представление TON в nanoTON, избегая проблем с плавающей запятой. Не пишите 0.1 * 1e9 — результат может быть неточным.


Простой перевод TON

Перевод TON на другой адрес — минимальный запрос:

const transaction = {
  validUntil: Math.floor(Date.now() / 1000) + 300, // 5 минут
  messages: [
    {
      address: 'EQBYivdc0GAk-nnczaMnYNuSjpeXu2nJS3DZ4KqLjosX5sVC',
      amount: toNano('0.5').toString(),
    },
  ],
};

const result = await tonConnectUI.sendTransaction(transaction);

Кошелёк получит запрос, покажет пользователю адрес получателя и сумму, и попросит подтверждение.


Payload: вызов смарт-контрактов

Для вызова метода смарт-контракта добавьте payload — BOC-encoded тело сообщения:

import { beginCell, toNano } from '@ton/core';

// Пример: вызов контракта с op-кодом и параметрами
const body = beginCell()
  .storeUint(0x7362d09c, 32) // op: transfer (Jetton)
  .storeUint(0, 64)          // query_id
  .storeCoins(toNano('100')) // amount of jettons
  .storeAddress(recipientAddress) // destination
  .storeAddress(senderAddress)    // response_destination
  .storeBit(false)                // no custom payload
  .storeCoins(toNano('0.01'))    // forward_ton_amount
  .storeBit(false)                // no forward payload
  .endCell();

const transaction = {
  validUntil: Math.floor(Date.now() / 1000) + 300,
  messages: [
    {
      address: jettonWalletAddress,          // адрес Jetton Wallet отправителя
      amount: toNano('0.05').toString(),     // газ для обработки
      payload: body.toBoc().toString('base64'), // BOC в base64
    },
  ],
};

const result = await tonConnectUI.sendTransaction(transaction);

Кошелёк может распознать известные op-коды (например, Jetton transfer) и показать пользователю понятное описание вместо raw-данных.


stateInit: деплой контракта

Если нужно развернуть новый контракт одновременно с отправкой сообщения, используйте поле stateInit:

const transaction = {
  validUntil: Math.floor(Date.now() / 1000) + 300,
  messages: [
    {
      address: newContractAddress,
      amount: toNano('0.1').toString(),
      stateInit: stateInitBoc.toString('base64'), // код + начальные данные
      payload: initMessageBody.toBoc().toString('base64'),
    },
  ],
};

stateInit содержит код контракта и начальные данные. Адрес контракта вычисляется детерминистически из stateInit (как мы разбирали в уроке о Jetton Wallet).


Multi-message транзакции

TON позволяет отправлять до 4 сообщений в одной транзакции. Все сообщения отправляются атомарно — либо все, либо ни одного:

const transaction = {
  validUntil: Math.floor(Date.now() / 1000) + 300,
  messages: [
    {
      address: recipientA,
      amount: toNano('1').toString(),
    },
    {
      address: recipientB,
      amount: toNano('2').toString(),
    },
    {
      address: contractC,
      amount: toNano('0.05').toString(),
      payload: somePayload.toBoc().toString('base64'),
    },
  ],
};
NOTE

Multi-message — это особенность TON Wallet v4/v5

Кошельки v4 и v5 поддерживают до 4 исходящих сообщений в одном внешнем сообщении. Более старые версии кошельков (v3) поддерживают только 1 сообщение. SDK автоматически проверяет поддержку.


Обработка ошибок

sendTransaction может завершиться ошибкой по нескольким причинам:

import { useTonConnectUI } from '@tonconnect/ui-react';

function SendButton() {
  const [tonConnectUI] = useTonConnectUI();

  const handleSend = async () => {
    try {
      const result = await tonConnectUI.sendTransaction({
        validUntil: Math.floor(Date.now() / 1000) + 300,
        messages: [
          {
            address: 'EQBYivdc0GAk-nnczaMnYNuSjpeXu2nJS3DZ4KqLjosX5sVC',
            amount: toNano('0.1').toString(),
          },
        ],
      });

      // Успех: result.boc содержит подписанную транзакцию
      console.log('Транзакция отправлена:', result.boc);
    } catch (error) {
      // Пользователь отклонил транзакцию
      // Или кошелёк отключился
      // Или истёк validUntil
      console.error('Ошибка:', error);
    }
  };

  return <button onClick={handleSend}>Отправить</button>;
}

Типичные ошибки

ОшибкаПричинаРешение
User rejectedПользователь нажал “Отклонить” в кошелькеИнформировать пользователя, не повторять автоматически
TimeoutПользователь не ответил до истечения validUntilСоздать новую транзакцию с обновлённым validUntil
Connection lostBridge-соединение разорваноПереподключить кошелёк через TonConnectButton
Bad requestНеверный формат сообщенияПроверить address, amount, payload

Итоги

КонцепцияОписание
sendTransactionМетод SDK для отправки транзакции через кошелёк
validUntilUnix timestamp — защита от устаревших транзакций
nanoTONЕдиница измерения: 1 TON = 10^9 nanoTON, передаётся как строка
payloadBOC-encoded тело сообщения для вызова контрактов
stateInitBOC-encoded код + данные для деплоя нового контракта
Multi-messageДо 4 сообщений в одной транзакции (v4/v5 кошельки)

В следующем уроке разберём аутентификацию через ton_proof — механизм, который позволяет dApp убедиться, что пользователь действительно владеет указанным кошельком, без создания блокчейн-транзакции.


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

  1. Не показывают пользователю детали транзакции перед отправкой: адрес получателя, сумму и payload нужно отображать для верификации.
  2. Забывают обрабатывать timeout транзакции: если пользователь не подтвердил в кошельке за определённое время, нужно показать соответствующее состояние.
  3. Отправляют несколько транзакций параллельно без учёта seqno: каждая следующая транзакция должна иметь корректный sequence number.
  4. Не проверяют статус транзакции после отправки: нужно отслеживать подтверждение на блокчейне, а не считать транзакцию успешной после отправки в кошелёк.

Проверка знанийKnowledge check
Чем payload отличается от обычного перевода TON, и зачем в amount указывать сумму при вызове контракта?
ОтветAnswer
Payload -- это BOC-encoded тело сообщения, содержащее op-код и параметры для вызова метода смарт-контракта. Без payload сообщение -- это простой перевод TON. Сумма в amount при вызове контракта -- это газ, необходимый для обработки сообщения контрактом (например, 0.05 TON для покрытия комиссий). Без достаточного газа контракт не сможет выполнить операцию.

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5