Некастодиальный USDT-кошелёк (TRC-20, TRON) как Telegram Mini App, с ботом через webhook, P2P-модулем на верифицированных обменниках, смарт-контрактом эскроу и крипточеками — всё в одном Render Web Service.
GitHub
↓
Render (ОДИН Web Service)
↓
Express (server/server.js)
├── /api/* REST API (wallet, p2p, checks, merchants, admin)
├── /telegram/webhook Приём апдейтов Telegram-бота
├── /health Health-check для Render
└── /app/* Статика Mini App (miniapp/dist), собранная при деплое
↓
MongoDB (Atlas) + TRON (TronGrid) + Bot API (api.telegram.org)
Admin-панель — отдельный Static Site (см. admin/), намеренно не влита в
основной сервис: она не нужна ежесекундно и её проще обновлять/защищать
отдельно (см. rationale в конце файла).
OneFive_App/
├── server/ Backend: Express + MongoDB + TronGrid + встроенный бот (webhook)
│ └── telegram/ bot.js (хендлеры команд) + webhook.js (роут + setWebHook)
├── bot/ ⚠️ DEV-ONLY: отдельный процесс с polling, для локальной разработки
├── miniapp/ React miniapp — сам кошелёк, P2P, чеки
├── admin/ React админ-панель (отдельный деплой)
└── contracts/ Смарт-контракт эскроу (Solidity/TVM) + деплой (TronBox)
- Некастодиальность: приватные ключи/сид-фразы генерируются и хранятся ТОЛЬКО на устройстве пользователя (AES-GCM, PIN-код, Telegram CloudStorage). Backend их не видит и не хранит — проверено аудитом (grep по всему репо на секреты, см. ниже).
- Подпись транзакций — исключительно на клиенте, backend только
рассылает подписанные транзакции (
/api/wallet/broadcast, под rate limit). - Telegram auth — каждый запрос от miniapp проверяется по HMAC подписи
initData(middleware/telegramAuth.js),initDataUnsafeнигде не используется как источник авторизации. - Webhook защищён секретным заголовком
X-Telegram-Bot-Api-Secret-Token. - Bot ↔ Backend — отдельный секрет
BOT_API_SECRET, не пересекается сADMIN_SECRETили Telegram-авторизацией. - Крипточеки — атомарные переходы статусов в MongoDB
(
findOneAndUpdateс условием на текущий статус) защищают от гонки двух параллельных запросов; реальная защита от двойной траты — сам блокчейн (второй sweep с уже пустого адреса физически не пройдёт). - Rate limiting — общий лимит на
/api/*, ужесточённый на/api/wallet/broadcastи/api/checks/:address/claim.
- Онбординг: создание / импорт кошелька, PIN, автоблокировка (lock из профиля)
- Backup seed flow (просмотр по PIN + проверка нескольких слов) — см.
CreateWallet.jsx - Баланс USDT/TRX, история операций, отправка/приём (QR)
- P2P: лента верифицированных обменников + реальная сделка через эскроу-контракт
- Крипточеки: создание, привязка к @username, отмена, срок действия, атомарная защита от двойной активации, уведомления через Bot API
- Смарт-контракт эскроу с автовозвратом по таймауту (Solidity, TronBox)
- Telegram-бот через webhook в одном процессе с API
- Единая сборка/деплой на один Render Web Service (
/api,/telegram/webhook,/app) - Rate limiting на чувствительных операциях
- Аудит на утечки секретов (см. раздел ниже)
- Админка: дашборд, пользователи, обменники (курс/топ), транзакции
- Смарт-контракт эскроу НЕ задеплоен и НЕ протестирован — код написан, но не прогнан ни в Shasta testnet, ни тем более в mainnet. Перед реальными деньгами обязательно: тестнет → unit-тесты (access control, reentrancy, double release, cancellation, fee logic) → отдельный аудит.
- Ссылка на крипточек не протестирована в реальном Telegram-клиенте —
логика написана по документации (hash-путь внутри HashRouter, чтобы не
задеть лимит
start_paramв 64 символа), но живое поведение стоит проверить на реальном боте до масштабирования. - Webhook проверен на реальном Render-деплое ✅ (см. постмортем ниже).
- Полноценные unit/integration-тесты отсутствуют — только ручной код-ревью
и синтаксическая проверка (
node --check+esbuild), без реальногоnpm install(нет сети в среде разработки Claude).
При первом реальном деплое на Render всплыли 3 отдельные проблемы подряд — фиксирую, чтобы не наступать на те же грабли:
vite: not foundпри сборке.viteлежит вdevDependenciesminiapp, а Render (и большинство PaaS) выставляетNODE_ENV=production, при которомnpm installпропускает devDependencies. Фикс — корневойpackage.jsonтеперь ставит зависимости miniapp с флагом--include=dev./appотдавалNOT_FOUNDдаже после успешной сборки. Вvite.config.jsне былоbase: '/app/'— собранныйindex.htmlссылался на JS/CSS от корня домена, а не от/app/. Заодно поправили Express-роут, чтобы матчил и/appбез слэша на конце.- Пустой экран в miniapp даже после успешной раздачи
/app.TronWeb/hdkeyнаписаны под Node.js и ожидаютBuffer/process/global, которых нет в браузере — без полифиллов JS падал ещё до первого рендера React, молча, без единой ошибки на экране. Фикс —vite-plugin-node-polyfillsвvite.config.js+ защитныйwindow.addEventListener('error', ...)вmain.jsx, который теперь печатает текст ошибки прямо на экране (критично для дебага на телефоне без доступа к консоли разработчика).
cd server && cp .env.example .env && npm install && npm run dev
cd bot && cp .env.example .env && npm install && npm run dev # отдельный терминал
cd miniapp && cp .env.example .env && npm install && npm run dev
cd admin && cp .env.example .env && npm install && npm run devНужен публичный HTTPS-туннель (например, ngrok http 3000) для локального
теста webhook:
cd server && cp .env.example .env
# впиши PUBLIC_URL=https://<твой-ngrok-адрес>.ngrok.io
npm install && npm run devБот из bot/ в этом варианте НЕ запускать — иначе будет конфликт 409
(polling и webhook с одним BOT_TOKEN одновременно).
cd contracts && cp .env.example .env
npm install
npm run compile
npm run migrate:shasta # ОБЯЗАТЕЛЬНО тестнет перед mainnetПосле деплоя впиши адрес в server/.env (ESCROW_CONTRACT_ADDRESS) и
miniapp/.env (VITE_ESCROW_CONTRACT_ADDRESS).
Service Type: Web Service
Branch: main
Root Directory: OneFive_App (или корень репозитория, если структура выше — корень)
Build Command: npm run build
Start Command: npm start
Health Check Path: /health
Render сам подставляет RENDER_EXTERNAL_URL — можно не задавать PUBLIC_URL
вручную, server/telegram/webhook.js подхватит его автоматически.
| Переменная | Обязательна | Назначение |
|---|---|---|
BOT_TOKEN |
да | токен из BotFather |
MINIAPP_URL |
да | https://<render-app>.onrender.com/app |
TELEGRAM_WEBHOOK_SECRET |
да | защита /telegram/webhook |
PUBLIC_URL |
нет* | *не нужен на Render — есть RENDER_EXTERNAL_URL автоматически |
MONGODB_URI |
да | строка подключения MongoDB Atlas |
TRON_FULL_NODE |
да | по умолчанию https://api.trongrid.io |
TRON_USDT_CONTRACT |
да | адрес USDT-TRC20 |
TRONGRID_API_KEY |
нет | увеличенные лимиты TronGrid |
ESCROW_CONTRACT_ADDRESS |
нет** | **пусто, пока контракт не задеплоен в testnet/mainnet |
PLATFORM_PAYMENT_ADDRESS |
нет | адрес платформы для оплаты подписки обменников |
ADMIN_SECRET |
да | доступ к /api/admin/* |
BOT_API_SECRET |
да | server-to-server канал бот → backend |
CORS_ORIGIN |
да | домен miniapp/админки |
NODE_ENV |
да | production |
PORT |
нет | Render подставляет сам |
Frontend (miniapp/.env, admin/.env) — только переменные с префиксом
VITE_*, все публичные (адрес контракта, URL API). Секреты туда никогда
не попадают — это проверено аудитом (раздел ниже).
Прогнано по всему репозиторию перед этим релизом:
- ✅ Нет захардкоженных приватных ключей / токенов / сид-фраз
- ✅
MONGODB_URIиспользуется только вserver/config/db.js, нигде не передаётся на фронтенд, ошибки подключения санитизируются перед логом - ✅
BOT_TOKENиспользуется только вserver/telegram/,server/services/telegramNotify.jsи dev-фолбэкеbot/bot.js— нигде во фронтенде - ✅ Единственные оставшиеся
localhost/127.0.0.1— в dev-фолбэках (bot/bot.js,admin/.envдефолт) и локальной сети TronBox — все переопределяются в production через ENV - ✅ Все критические операции (broadcast, claim чека, fund/release эскроу)
требуют проверенной Telegram
initData ⚠️ Rate limiting — базовый (in-memory, per-IP). Для серьёзного трафика стоит вынести в Redis — сейчас достаточно для MVP, но это грубая защита, не production-grade для высоких нагрузок
/start → бот (через webhook, единый процесс с API) отправляет
приветствие и кнопку 💼 Открыть кошелёк → открывает MINIAPP_URL.
Mini App открывается на /app, читает Telegram.WebApp.initData,
шлёт его в заголовке x-telegram-init-data на каждый запрос к /api/*.
Wallet: кошелёк создаётся/импортируется на клиенте, ключ шифруется PIN-кодом и живёт в Telegram CloudStorage — backend знает только публичный адрес.
Checks: одноразовый TRON-адрес генерируется на клиенте, ссылка на чек
содержит зашифрованный ключ в hash-пути (#/claim/адрес/ключ), backend
никогда не видит ключ — только публичные метаданные для UX/уведомлений.
P2P: пока — курсы верифицированных обменников через админку + эскроу-сделка (fund/release/dispute) через смарт-контракт. Testnet-only до полного прогона тестов контракта.
git add .
git commit -m "OneFive MVP: webhook bot, single Render service, checks, rate limiting, security audit"
git push origin main