# CryptoWallet API — Deployment Bundle Multi-chain **custodial** wallet API (ETH / BSC / BTC / TRX / SOL). - Сервер генерит mnemonic, хранит зашифрованной (AES-256-GCM, master-key из HashiCorp Vault) - Сервер сам подписывает tx по запросу юзера (юзер на клиенте жмёт "подтвердить") Auth — JWT, выданный сервисом **bitok** (внешний). Секреты — HashiCorp Vault (AppRole). ## Pre-deploy setup (один раз) ```bash # 1. Master-key в Vault vault kv put dev-secrets/crypto/master key=$(openssl rand -hex 32) # 2. CSRF secret в Vault vault kv put dev-secrets/csrf secret_key=$(openssl rand -hex 32) salt=csrf-salt digest=sha256 # 3. DB schema — APPEND-ONLY / NON-DESTRUCTIVE # Безопасно прогонять на existing БД. См. ниже "Schema is non-destructive". psql -h -U postgres_user -d postgres -f cryptowallet-schema.sql # 4. bitok public key в Vault (для kid из JWT header) vault kv put dev-secrets/jwt/kid active= vault kv put dev-secrets/jwt/kids/ \ algorithm=RS256 \ public_key="$(cat /path/to/bitok-public.pem)" ``` ⚠️ **Master-key менять нельзя** — все existing `encrypted_mnemonic` станут нерасшифровываемыми. API на старте делает self-test: пытается декриптить любой существующий mnemonic и фейлится если ключ не подошёл. ## Deploy ```bash # Залить bundle на сервер scp -P 2222 -r deployserver/ server@:~/cryptowallet/ # На сервере: убедись что .env заполнен (VAULT_*, JWT_*, CORS_ORIGINS, REDIS_PASSWORD, ...) ssh server@ -p 2222 cd ~/cryptowallet/deployserver docker compose up -d --build docker compose logs -f api curl http://localhost:3001/api/health ``` API **не делает migrations / DROP / ALTER** при старте — только INSERT/UPDATE/SELECT. Schema (если нужны новые колонки/таблицы для нового функционала) обновляется только руками: `psql -f cryptowallet-schema.sql` (script append-only — `CREATE TABLE IF NOT EXISTS` / `ALTER TABLE ADD COLUMN IF NOT EXISTS`, никаких DROP). ## Update / Rebuild ```bash # Залить новый src + rebuild api (keydb данные не теряются) scp -P 2222 -r deployserver/apps deployserver/Dockerfile deployserver/docker-compose.yml \ server@:~/cryptowallet/deployserver/ ssh server@ -p 2222 'cd cryptowallet/deployserver && docker compose up -d --build' ``` ## Endpoints ### Core wallet management | Method | Path | Описание | |---|---|---| | GET | `/api/health` | Liveness (public) | | GET | `/api/docs` | Swagger UI | | GET | `/api/docs/swagger.json` | OpenAPI JSON | | POST | **`/api/wallets/create`** | Сервер создаёт коша (no body, returns 5 addresses) | | GET | `/api/wallets` | Список адресов юзера | | GET | `/api/wallets/portfolio` | Аггрегированный USD portfolio по всем 5 chains (KeyDB cached 1h, stale fallback) | | POST | **`/api/wallets/mnemonic/reveal`** | Reveal seed (body confirm + 5/час rate-limit) | | GET | `/api/wallets/{chain}/balance` | Баланс (native + все известные токены чейна) | | GET | `/api/wallets/{chain}/transactions` | История tx | | POST | **`/api/wallets/{chain}/send`** | Сервер подписывает + broadcast. Body: `{to, amount, token?, feeTier?}` | | POST | `/api/wallets/{chain}/send/cost-estimate` | Pre-flight gas/fee estimate (no broadcast) | | GET | **`/api/wallets/{chain}/gas-suggestions`** | Slow/normal/fast tiers (ETH/BSC, parsed из eth_feeHistory) | | POST | **`/api/wallets/{chain}/sign-raw-evm-tx`** | Подписать произвольную EVM tx (для Relay execute steps). Поддерживает opt-in `bridgeAmount`+`bridgeToken` → автоматически взимает 0.7% app fee на EVM fee wallet ПЕРЕД main tx (atomic). | | POST | `/api/wallets/SOL/sign-and-broadcast-tx` | Sign+broadcast serialized VersionedTransaction (Jupiter/Relay SOL steps) | ### Bridge & Swap (NEW) | Method | Path | Описание | |---|---|---| | POST | **`/api/bridge/execute`** | One-click bridge orchestrator. Auto-routes: TRX source → NearIntents 1Click; EVM/SOL source → Jumper (LiFi) или Relay. Atomic fee tx ДО main bridge. Idempotency-Key support. Returns `{feeTxid?, approveTxid?, bridgeTxid, trackerUrl, provider}`. | | GET | **`/api/jumper/quote-best`** | Bridge quote с NearIntents priority + smart LiFi fallback. JWT-binds fromAddress. | | GET | `/api/jumper/{chains,tools,tokens,connections,status}` | LiFi metadata proxies (JWT-protected) | | POST | `/api/jumper/advanced/{routes,stepTransaction}` | LiFi multi-route preview / step tx fetcher | | POST | `/api/wallets/{chain}/swap/quote` | Custodial swap quote (BSC PancakeSwap / SOL Jupiter / TRX SunSwap) — 30s locked minOut | | POST | **`/api/wallets/{chain}/swap`** | Confirm custodial swap (BSC/SOL/TRX). BSC + SOL: 0.7% atomic fee ДО main swap. TRX: existing on-chain FeeSwapRouter. | | POST | `/api/wallets/{chain}/swap/cost-estimate` | Swap pre-flight cost estimate | | — | `/api/relay/*` | Relay.link bridge proxy (quote / execute / status / cost-estimate). Unchanged from previous deploys. | ### App fee 0.7% (NEW — hardcoded recipients) | Method | Path | Описание | |---|---|---| | POST | **`/api/wallets/{chain}/app-fee`** | Standalone fee transfer endpoint. Для **Relay frontend hook** — клиент явно вызывает после Relay execute. Body: `{amount, token?}`. Server validates JWT-bind + computes 0.7% + signs + broadcasts. Idempotency-Key support. | Fee wallets (захардкожены в `lib/app-fee.ts`, нельзя override через env): | Chain | Recipient address | |---|---| | EVM (ETH+BSC) | `0xeb9fbf0d137ef5ea7b9959044c2ed44ec1206c68` | | SOL | `DQkQegoX698XkcXZ6VX9P1qUpbV64Sgjz1BCPFgfWpjD` | | TRX | `TRwpFjnfMBe4aDJbHYEqeUVCG1auF8wFXP` | | BTC | (not collectable — no fee wallet) | Fee рассчёт: `feeAmount = amount × 70 / 10000` (BigInt-precision, 0 если < 144 smallest units). ### Tokens & Prices (NEW) | Method | Path | Описание | |---|---|---| | GET | **`/api/tokens`** `?chain={ETH,BSC,BTC,TRX,SOL}` `?bridgeable=true` | Token registry. `bridgeable=true` фильтрует только tokens с реальным bridge route (используется Jumper UI dropdowns) | | GET | `/api/prices` `?symbols=BTC,ETH,USDT` `?chain={...}` | CoinGecko cached USD prices | | GET | **`/api/prices/dynamics`** `?symbols=...` | 24h price change % для нескольких символов одним запросом | ### Provider integrations (NEW) - **NearIntents 1Click** (`https://1click.chaindefuser.com`) — direct TRX bridges. Asset map fetched dynamically через `/v0/tokens` (cached 1h in-memory). Quote → user transfers TRX/USDT to depositAddress → solver delivers cross-chain. No JWT для test env (0.2% NearIntents fee included в quote). Implementation: `src/lib/nearintents-client.ts`. - **Jumper / LiFi** (`https://li.quest/v1`) — multi-chain bridge proxy + quote-best smart routing с NearIntents priority. Implementation: `src/routes/jumper-proxy.routes.ts`. - **Relay.link** (`https://api.relay.link`) — EVM/SOL/BTC bridge proxy. **Unchanged** (preservation invariant). Implementation: `src/routes/relay-proxy.routes.ts`. ### Audit log events (extended) - `wallet.create`, `wallet.send`, `wallet.swap` (existing) - `wallet.sign_raw_evm` (existing, +`bridgeAmount` meta) - `wallet.bsc_fee` (existing) / `wallet.app_fee` (NEW — standalone fee endpoint) - `bridge.execute`, `bridge.execute.broadcast` (NEW — bridge orchestrator) ## Security highlights - **AES-256-GCM** для encrypted_mnemonic (12-byte random IV, 16-byte auth tag, fail-secure) - **Master-key set-once** (rotation запрещена в коде) - **Crypto self-test на старте** — fail-fast если master-key не декриптит existing mnemonics - **Race-safe createWallet** — `db.transaction` + `UPDATE WHERE encrypted_mnemonic IS NULL` (set-once primitive) - **Atomic erc20 update** — ETH-адрес кладётся в `users.erc20` внутри той же транзакции - **TRX MITM defense** — local recompute txID + 4-layer raw_data verification перед подписью - **EVM gas cap** 500 gwei (применён к tx, не только check) - **EVM gas oracle** через `eth_feeHistory` p25/p50/p75 — minimum-but-works fees (BSC floor 0.05, ETH 0.5 gwei) - **BTC fee** tier-based (slow=144 blocks, normal=6, fast=1) + floor 2 sat/vB - **TRX fee_limit** cap 30 TRX (раньше 100, излишне) - **Address checksum validation** (BTC bitcoinjs-lib, TRX bs58check, SOL PublicKey, EVM EIP-55) - **assertAddressMatch** — derived(mnemonic, path) === DB.address перед подписью - **SOL confirmTransaction** — ждём подтверждения сети - **BTC** P2WPKH bech32, dust 294, broadcast 20s timeout - **POST mnemonic/reveal** + CSRF + body confirm token + 5/час rate-limit + audit-log - **Logger sanitization** — password/token/mnemonic/hex64/BIP39-phrase patterns маскируются - **Audit log в stdout** (структурированный JSON с `"level":"audit"`) — `wallet.create` / `wallet.send` / `wallet.swap` / `wallet.sign_raw_evm` / `wallet.bsc_fee` / `wallet.app_fee` / `mnemonic.reveal` / `bridge.execute` / `bridge.execute.broadcast` - **Hourly key rotation** — JWT keys + CSRF secret из Vault (master-key НЕ ротируется) - **Fail-fast** — сервис не стартует без master-key, JWT_ISSUER, JWT_AUDIENCE - **Container hardening** — read-only fs, cap_drop ALL, no-new-privileges, pids/mem/cpu limits, non-root uid 1001, loopback-only port - **Relay proxy** whitelist method+path — `/quote` (POST), `/intents/status/v3` (GET), `/execute/{swap|bridge}` (POST). Никаких freeform action'ов - **Jumper proxy** whitelist аналогично — `/quote`, `/quote-best`, `/chains`, `/tools`, `/tokens`, `/connections`, `/status`, `/advanced/{routes,stepTransaction}`. JWT-binds `fromAddress` к user wallet. - **NearIntents direct integration** для TRX bridges — `signAndBroadcast(TRX)` через battle-tested sendTrx (4-layer MITM defense сохраняется). Asset map fetched через `/v0/tokens` (auth source) и cached 1h в-memory. depositAddress validated через Tron base58 regex `^T[1-9A-HJ-NP-Za-km-z]{33}$`. - **App fee 0.7%** atomic enforcement в bridge-execute orchestrator + custodial swap orchestrator. Fee tx FIRST → если fail, main tx НЕ broadcast (atomic abort). Recipients hardcoded — нельзя override через body/env. - **BridgeSimulationError** для EVM bridges — pre-broadcast `eth_call` dry-run; если revert → 400 `SIMULATION_FAILED`, fees не сгорают. - **InsufficientBalanceError** pre-check на bridge — отвергаем 400 ДО подписи если balance < amount + reserve. - **Anti-MEV** в bridge-execute — server re-quotes ПЕРЕД sign, валидирует `minAmountOut ≥ acceptedMinOut` (50 bps cushion). Если price moved → 409. - **Idempotency-Key** на mutating endpoints (send / sign-raw / swap / bridge-execute / app-fee) — KeyDB cache 10 min, retry-safe. - **TronGrid 429 retry** с exponential backoff (0/1.5/4/8s) для free-tier rate limits. ## Schema is non-destructive `cryptowallet-schema.sql` **append-only**. Re-run на боксе с уже настроенной БД = **zero DDL changes**. Если оператор добавил кастомные таблицы / индексы / constraints вручную — они **никогда** не будут перезаписаны или удалены. Что делает script: - `CREATE TABLE IF NOT EXISTS users` / `wallets` - `ALTER TABLE users ADD COLUMN ` (только если колонки нет — `encrypted_mnemonic`, `erc20`, `passport_data`) - `CREATE UNIQUE INDEX users_email_lower_unique` (если индекса нет) - `CREATE INDEX idx_users_active` / `idx_wallets_*` (если индексов нет) - `ADD CONSTRAINT` × 4 (только если данного constraint name нет) Что script **НЕ делает**: - ❌ Никогда не `DROP TABLE` - ❌ Никогда не `DROP CONSTRAINT` - ❌ Никогда не `DROP COLUMN` - ❌ Никогда не перезаписывает существующие constraints / indexes Legacy cleanup (audit_log, idempotency_keys, sessions от старых версий) — **manual one-time** операторская задача, не часть этого script'а: ```bash psql ... -c "DROP TABLE IF EXISTS audit_log CASCADE;" psql ... -c "DROP TABLE IF EXISTS idempotency_keys CASCADE;" psql ... -c "DROP TABLE IF EXISTS sessions CASCADE;" ``` ## Logs Файловых логов **нет**. Весь код пишет в `process.stdout` (см. `apps/api/src/lib/logger.ts` и `lib/audit-log.ts`). Docker подбирает stdout через json-file driver и показывает через `docker compose logs`: ```bash docker compose logs -f api # все логи (structured JSON) docker compose logs api | grep '"level":"audit"' # только audit events docker compose logs api | grep '"level":"ERROR"' # только ошибки ``` ## Production hardening checklist (опционально) - [ ] Vault server-mode (raft/file backend) с unseal flow - [ ] TLS termination на reverse-proxy (Caddy / Nginx) перед `127.0.0.1:3001` - [ ] Swagger UI скрыть за basic-auth (endpoints всё ещё доступны через `/api/docs/swagger.json`) - [ ] Postgres backups (pg_dump → S3 по cron) - [ ] Vault root token ротация - [ ] Mnemonic-reveal endpoint — 2FA / time-based confirmation tokens - [ ] Rate-limit tune под реальный трафик