Настройка приёма платежей в TON
TON — не Ethereum с другим RPC. Главная особенность, которая ломает интуицию: транзакции асинхронны и имеют древовидную структуру. Когда пользователь отправляет TON на ваш адрес, это одна транзакция. Когда отправляет Jetton (USDT на TON, например) — это цепочка из трёх сообщений: transfer → internal message → notification. Мониторинг должен учитывать это.
Два сценария: нативный TON и Jetton (USDT/USDC)
Приём нативного TON
Самый простой случай. Генерируем уникальный адрес или используем один адрес с comment (memo) для идентификации платежа — TON нативно поддерживает текстовые комментарии в транзакциях.
Мониторинг через TON Center API или TonAPI:
import { TonClient } from '@ton/ton';
import { Address } from '@ton/core';
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: process.env.TONCENTER_API_KEY,
});
async function checkIncomingTransactions(
address: string,
lastLt: string // last known logical time
) {
const addr = Address.parse(address);
const transactions = await client.getTransactions(addr, {
limit: 20,
lt: lastLt,
archival: false,
});
for (const tx of transactions) {
// Только входящие, не bounce
if (tx.inMessage && tx.inMessage.info.type === 'internal') {
const info = tx.inMessage.info;
const value = info.value.coins; // в nanoTON
const comment = tx.inMessage.body; // текстовый комментарий
// Матчим комментарий с нашим payment ID
console.log(`Received: ${value} nanoTON, comment: ${comment}`);
}
}
}
Важно: проверяем bounce флаг и bounced флаг. Bounced транзакция означает, что получатель вернул средства — её не нужно засчитывать как оплату.
Приём Jetton (USDT, USDC, NOT и др.)
Jetton Transfer работает иначе: пользователь отправляет сообщение своему JettonWallet контракту, который шлёт внутреннее сообщение к JettonWallet получателя, а тот отправляет transfer_notification на адрес получателя.
Чтобы получать уведомления о Jetton переводах, в параметрах forward_ton_amount и forward_payload нужно указать сумму TON для уведомления и данные:
transfer_notification#7362d09c
query_id: uint64
amount: coins // количество Jetton
sender: MsgAddress // адрес отправителя
forward_payload: ^Cell // наш custom payload (payment ID)
Для приёма Jetton на сервере нужно мониторить не основной адрес, а JettonWallet нашего адреса:
// Получаем адрес нашего JettonWallet для USDT
async function getJettonWalletAddress(
ownerAddress: string,
jettonMasterAddress: string
): Promise<string> {
const master = client.open(
JettonMaster.create(Address.parse(jettonMasterAddress))
);
const walletAddr = await master.getWalletAddress(
Address.parse(ownerAddress)
);
return walletAddr.toString();
}
// USDT на TON mainnet
const USDT_MASTER = 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs';
Уникальные адреса vs comment-based идентификация
Подход 1: Comment/Memo идентификация
Один адрес для всех платежей, пользователь указывает уникальный comment (payment ID). Просто в реализации, но требует обязательного UX — объяснить пользователю необходимость комментария. Ошибка пользователя = потерянный платёж (нужен manual reconciliation).
Подход 2: Уникальный адрес на каждый платёж
Генерируем HD wallet адреса (TON поддерживает стандартные мнемоники BIP39 + нестандартную деривацию). Каждый заказ — отдельный адрес. Никаких комментариев, нет ошибок пользователя, простой мониторинг.
import { mnemonicToPrivateKey } from '@ton/crypto';
import { WalletContractV4 } from '@ton/ton';
async function derivePaymentAddress(
masterMnemonic: string[],
orderIndex: number
): Promise<string> {
// TON не стандартный BIP44, используем разные subwalletId
const keyPair = await mnemonicToPrivateKey(masterMnemonic);
const wallet = WalletContractV4.create({
publicKey: keyPair.publicKey,
workchain: 0,
walletId: 698983191 + orderIndex, // уникальный subwalletId
});
return wallet.address.toString({ bounceable: false });
}
Минус: нужно периодически сводить средства с дочерних адресов на основной (consolidation sweep).
Polling vs Webhook
TON Center API поддерживает webhook подписки на транзакции по адресу — это удобнее polling. TonAPI (tonapi.io) — более богатый API с WebSocket стримингом.
Для self-hosted варианта: TON node (C++ или Go реализация) с собственным индексатором. Но это существенно сложнее — нода занимает ~2TB и требует синхронизации несколько дней.
Для большинства проектов: TonAPI с WebSocket + polling как fallback с cursor по lt (logical time).
Confirmation
В TON нет понятия "количество подтверждений" как в Bitcoin. Транзакция либо включена в блок и финальна, либо нет. TON использует BFT-подобный консенсус — finality достигается в течение нескольких секунд после включения в блок. Практически: достаточно дождаться 1 блока (~5 секунд) и проверить, что транзакция не bounce.
Тестирование
TON Testnet — отдельная сеть. Testnet TON получаем через бот @testgiver_ton_bot. API endpoint: https://testnet.toncenter.com/api/v2/jsonRPC. Для разработки также полезен Sandbox от Blueprint — локальный TVM эмулятор без обращения к сети.







