TON — не Ethereum з іншим RPC. Асинхронна модель транзакцій та деревоподібна структура повідомлень ламають інтуїцію розробника. Коли користувач відправляє нативний TON на вашу адресу — це одна транзакція. Коли Jetton (USDT на TON) — ланцюжок із трьох: transfer → internal message → notification. Помилка в моніторингу веде до втрачених платежів та головного болю з поверненнями.
Нещодавно до нас звернувся проект з 5000 щоденних Jetton-платежів. Після аудиту їхньої системи ми виявили, що вони не враховували bounced-транзакції, і 2% платежів зараховувалися помилково. Ми перебудували архітектуру на унікальні адреси та Gasless-релей, скоротивши втрати до нуля. Ми налаштовуємо прийом платежів під ключ: пишіть, оцінимо проект і підберемо оптимальну архітектуру за один день.
Як приймати нативний TON та Jetton
Нативний TON
Генеруємо унікальну адресу або використовуємо одну адресу з comment (memo) для ідентифікації. Моніторинг через 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, який шле внутрішнє повідомлення до контракту отримувача, а той — transfer_notification на адресу отримувача. У forward_ton_amount закладаємо суму для повідомлення, у forward_payload — payment ID:
transfer_notification#7362d09c query_id: uint64 amount: coins // кількість Jetton sender: MsgAddress // адреса відправника forward_payload: ^Cell // наш custom payload (payment ID) Моніторимо не основну адресу, а 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'; Що вибрати: унікальні адреси чи comment?
| Характеристика | Коментар (memo) | Унікальна адреса |
|---|---|---|
| Складність реалізації | Низька | Середня (HD wallet) |
| Помилки користувача | 1-3% забувають коментар | 0% |
| Зведення коштів | Не потребує | Потребує sweep |
| Моніторинг | Одна адреса | Багато адрес |
| Рекомендація | До 100 платежів/день | Від 1000 платежів/день |
Comment/Memo ідентифікація
Одна адреса, користувач вказує comment (payment ID). Просто, але потребує UX — пояснити необхідність коментаря. Помилка = втрачений платіж (потрібен manual reconciliation).
Унікальна адреса на кожен платіж
Генеруємо HD wallet (BIP39 + нестандартна деривація). Кожне замовлення — окрема адреса. Жодних коментарів, немає помилок, простий моніторинг:
import { mnemonicToPrivateKey } from '@ton/crypto'; import { WalletContractV4 } from '@ton/ton'; async function derivePaymentAddress( masterMnemonic: string[], orderIndex: number ): Promise<string> { const keyPair = await mnemonicToPrivateKey(masterMnemonic); const wallet = WalletContractV4.create({ publicKey: keyPair.publicKey, workchain: 0, walletId: 698983191 + orderIndex, // унікальний subwalletId }); return wallet.address.toString({ bounceable: false }); } Мінус: потрібно зводити кошти на основну адресу (sweep).
Polling чи Webhook?
| Метод | Затримка | Навантаження | Складність |
|---|---|---|---|
| Polling (TON Center) | ~5-30 сек | Середня | Низька |
| Webhook (TON Center) | ~1-2 сек | Низька | Середня |
| WebSocket (TonAPI) | ~0.5 сек | Низька | Висока |
| Власна нода | ~0 сек | Дуже висока | Дуже висока |
Для продакшену — TonAPI + WebSocket, з fallback через polling. Self-hosted нода виправдана при мільйонах транзакцій на день.
Gasless та bounce: часті проблеми
Gasless
Gasless дозволяє користувачеві платити без балансу TON для комісії. Це критично для Jetton-платежів: щоб відправити USDT, потрібен TON на газ. Сервіс покриває комісію через релей. Налаштовуємо релей через TON Connect або контракт-релей. Gasless підвищує конверсію на 15-30% у мобільних додатках.
Bounce
Якщо не фільтрувати bounced транзакції, ви можете зарахувати платіж, який насправді не дійшов. У TON bounce — нормальне явище: контракт отримувача може відхилити повідомлення. Перевіряйте bounced флаг у тілі повідомлення. Для Jetton додатково відстежуйте transfer_notification — його відсутність теж ознака невдачі.
Етапи налаштування прийому платежів TON
- Аналіз — оцінка навантаження, типів активів (TON, Jetton), вибір архітектури.
- Вибір методу ідентифікації — comment чи унікальні адреси.
- Розробка моніторингу — інтеграція з TON Center / TonAPI, обробка webhook/WebSocket.
- Інтеграція з бекендом — маппінг транзакцій на замовлення, обробка помилок.
- Тестування на testnet — використання бота для роздачі тестових TON та Sandbox від Blueprint.
- Деплой — налаштування продакшен-середовища, моніторинг та алерти.
Строки та вартість
Строки налаштування базової схеми — від 2 до 4 тижнів. Вартість розраховується індивідуально залежно від складності: кількості активів, навантаження та необхідності Gasless-релея. Отримайте консультацію з архітектури — зв'яжіться для оцінки проекту.
Типові помилки при прийомі TON
- Ігнорування bounced-транзакцій — зарахування невдалих платежів.
- Моніторинг основної адреси замість JettonWallet — пропуск Jetton-платежів.
- Використання лише polling без fallback — втрата транзакцій при високому навантаженні.
- Відсутність тестування на testnet — помилки в продакшені.
- Не врахування асинхронності — спроба синхронно чекати відповіді від контракту.
Тестування та деплой
Testnet: використовуємо бота для роздачі тестових TON. API endpoint https://testnet.toncenter.com/api/v2/jsonRPC. Для локальної розробки — Sandbox від Blueprint: емулятор TVM без мережі. Модель асинхронних повідомлень потребує особливого підходу до тестування. Замовте налаштування прийому платежів TON, щоб виключити помилки в моніторингу та автоматизувати облік.







