From d44a15064f5be269a945a00efc6e747a498c9078 Mon Sep 17 00:00:00 2001 From: NW Date: Thu, 25 Jun 2026 18:55:19 +0100 Subject: [PATCH] feat: add StealthEX deposit integration for wallet top-up - New depositHandler.js: wallet selection, amount picker, instruction page with StealthEX link - Updated topUpHandler.js: shows deposit buttons per wallet + deposit via StealthEX - Routes: deposit_select_wallet, deposit_wallet_, deposit_amount_, deposit_copy_ - Config: STEALTHEX_REF env var for optional referral - Fixed archived wallets filter in deposit wallet query --- .env.example | 4 + src/config/config.js | 4 +- .../userHandlers/wallet/depositHandler.js | 212 ++++++++++++++++++ src/handlers/userHandlers/wallet/index.js | 5 + .../userHandlers/wallet/topUpHandler.js | 66 ++++-- src/router/routes.js | 17 ++ 6 files changed, 283 insertions(+), 25 deletions(-) create mode 100644 src/handlers/userHandlers/wallet/depositHandler.js diff --git a/.env.example b/.env.example index f8a2ccc..fc261ce 100644 --- a/.env.example +++ b/.env.example @@ -29,6 +29,10 @@ COMMISSION_WALLET_USDT= COMMISSION_WALLET_USDC= COMMISSION_WALLET_ETH= +# --- StealthEX Deposit Integration --- +# Optional: your StealthEX referral ID (leave empty if none) +STEALTHEX_REF= + # --- WireGuard --- WG_ENABLED=false WG_PRIVATE_KEY= diff --git a/src/config/config.js b/src/config/config.js index 9291968..3ba6c03 100644 --- a/src/config/config.js +++ b/src/config/config.js @@ -42,5 +42,7 @@ export default { USDT: process.env.COMMISSION_WALLET_USDT, USDC: process.env.COMMISSION_WALLET_USDC, ETH: process.env.COMMISSION_WALLET_ETH - } + }, + + STEALTHEX_REF: process.env.STEALTHEX_REF || '' }; diff --git a/src/handlers/userHandlers/wallet/depositHandler.js b/src/handlers/userHandlers/wallet/depositHandler.js new file mode 100644 index 0000000..c1dc242 --- /dev/null +++ b/src/handlers/userHandlers/wallet/depositHandler.js @@ -0,0 +1,212 @@ +import db from '../../../config/database.js'; +import config from '../../../config/config.js'; +import UserService from '../../../services/userService.js'; +import bot from '../../../context/bot.js'; +import logger from '../../../utils/logger.js'; +import { editOrSendCallback } from '../../../utils/messageUtils.js'; + +const DEPOSIT_AMOUNTS = [25, 50, 100, 250, 500]; + +const STEALTHEX_CRYPTO_MAP = { + BTC: 'btc', + ETH: 'eth', + LTC: 'ltc', + USDT: 'usdterc20', + USDC: 'usdcerc20' +}; + +const CRYPTO_SYMBOLS = { + BTC: '₿', + ETH: 'Ξ', + LTC: 'Ł', + USDT: '💲', + USDC: '💲' +}; + +export default class DepositHandler { + static async handleDepositSelectWallet(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const telegramId = callbackQuery.from.id; + + try { + const user = await UserService.getUserByTelegramId(telegramId); + if (!user) { + await editOrSendCallback(callbackQuery, 'Profile not found. Please use /start to create one.'); + return; + } + + const cryptoWallets = await db.allAsync( + "SELECT wallet_type, address FROM crypto_wallets WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#' ORDER BY wallet_type", + [user.id] + ); + + if (cryptoWallets.length === 0) { + await bot.editMessageText( + '❌ You don\'t have any wallets yet. Create one first.', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + reply_markup: { + inline_keyboard: [ + [{ text: '➕ Add Wallet', callback_data: 'add_wallet' }], + [{ text: '« Back', callback_data: 'back_to_balance' }] + ] + } + } + ); + return; + } + + const walletButtons = cryptoWallets.map(w => { + const symbol = CRYPTO_SYMBOLS[w.wallet_type] || ''; + return [{ + text: `${symbol} ${w.wallet_type}`, + callback_data: `deposit_wallet_${w.wallet_type}` + }]; + }); + + walletButtons.push([{ text: '« Back', callback_data: 'back_to_balance' }]); + + await bot.editMessageText( + '💳 *Deposit via StealthEX*\n\nSelect the wallet you want to top up:', + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + parse_mode: 'Markdown', + reply_markup: { inline_keyboard: walletButtons } + } + ); + } catch (error) { + logger.error({ err: error }, 'Error in handleDepositSelectWallet'); + await editOrSendCallback(callbackQuery, 'Error loading wallets. Please try again.'); + } + } + + static async handleDepositSelectAmount(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const walletType = callbackQuery.data.replace('deposit_wallet_', ''); + + const amountButtons = DEPOSIT_AMOUNTS.map(amount => ([{ + text: `$${amount}`, + callback_data: `deposit_amount_${walletType}_${amount}` + }])); + + amountButtons.push([{ text: '« Back', callback_data: 'top_up_wallet' }]); + + await bot.editMessageText( + `💳 *Deposit ${walletType}*\n\nSelect the amount (USD) you want to deposit:`, + { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + parse_mode: 'Markdown', + reply_markup: { inline_keyboard: amountButtons } + } + ); + } + + static async handleDepositInstruction(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const telegramId = callbackQuery.from.id; + const parts = callbackQuery.data.replace('deposit_amount_', '').split('_'); + const walletType = parts[0]; + const amount = parts[1]; + + try { + const user = await UserService.getUserByTelegramId(telegramId); + if (!user) { + await editOrSendCallback(callbackQuery, 'Profile not found. Please use /start to create one.'); + return; + } + + const wallet = await db.getAsync( + 'SELECT address FROM crypto_wallets WHERE user_id = ? AND wallet_type = ?', + [user.id, walletType] + ); + + if (!wallet) { + await editOrSendCallback(callbackQuery, 'Wallet not found. Please try again.'); + return; + } + + const stealthexFrom = 'eur'; + const stealthexTo = STEALTHEX_CRYPTO_MAP[walletType] || walletType.toLowerCase(); + const refId = config.STEALTHEX_REF; + const stealthexUrl = `https://stealthex.io/exchange/new/?amount=${amount}&from=${stealthexFrom}&to=${stealthexTo}${refId ? `&ref_id=${refId}` : ''}`; + + let message = `💳 *Deposit ${walletType} — $${amount}*\n\n`; + message += `📋 *Instructions:*\n`; + message += `1\\. Copy your wallet address below\n`; + message += `2\\. Click "Open StealthEX" button\n`; + message += `3\\. The exchange is pre\\-configured: EUR → ${walletType}, $${amount}\n`; + message += `4\\. Complete the payment on StealthEX\n`; + message += `5\\. Paste your wallet address as the receiving address in StealthEX\n\n`; + message += `🔐 *Your ${walletType} Wallet Address:*\n`; + message += `\`${wallet.address}\`\n\n`; + message += `⚠️ *Important:* Always double\\-check the wallet address before confirming\\.`; + + const keyboard = { + inline_keyboard: [ + [{ text: `🌐 Open StealthEX — EUR → ${walletType} ($${amount})`, url: stealthexUrl }], + [ + { text: '📋 Copy Address', callback_data: `deposit_copy_${walletType}` }, + { text: '🔄 Change Amount', callback_data: `deposit_wallet_${walletType}` } + ], + [ + { text: '💸 Choose Different Wallet', callback_data: 'top_up_wallet' }, + { text: '« Back to Balance', callback_data: 'back_to_balance' } + ] + ] + }; + + await bot.editMessageText(message, { + chat_id: chatId, + message_id: callbackQuery.message.message_id, + parse_mode: 'MarkdownV2', + reply_markup: keyboard + }); + } catch (error) { + logger.error({ err: error }, 'Error in handleDepositInstruction'); + await editOrSendCallback(callbackQuery, 'Error creating deposit instructions. Please try again.'); + } + } + + static async handleDepositCopyAddress(callbackQuery) { + const chatId = callbackQuery.message.chat.id; + const telegramId = callbackQuery.from.id; + const walletType = callbackQuery.data.replace('deposit_copy_', ''); + + try { + const user = await UserService.getUserByTelegramId(telegramId); + if (!user) { + await bot.answerCallbackQuery(callbackQuery.id, { text: 'Profile not found.' }); + return; + } + + const wallet = await db.getAsync( + 'SELECT address FROM crypto_wallets WHERE user_id = ? AND wallet_type = ?', + [user.id, walletType] + ); + + if (!wallet) { + await bot.answerCallbackQuery(callbackQuery.id, { text: 'Wallet not found.' }); + return; + } + + await bot.sendMessage(chatId, `${walletType} wallet address:\n\n\`${wallet.address}\``, { + parse_mode: 'Markdown', + reply_markup: { + inline_keyboard: [ + [{ text: '« Back to Deposit', callback_data: `deposit_wallet_${walletType}` }] + ] + } + }); + + await bot.answerCallbackQuery(callbackQuery.id, { + text: `📋 ${walletType} address sent! Copy it from the message below.` + }); + } catch (error) { + logger.error({ err: error }, 'Error in handleDepositCopyAddress'); + await bot.answerCallbackQuery(callbackQuery.id, { text: 'Error copying address.' }); + } + } +} \ No newline at end of file diff --git a/src/handlers/userHandlers/wallet/index.js b/src/handlers/userHandlers/wallet/index.js index 9975c06..d6bca92 100644 --- a/src/handlers/userHandlers/wallet/index.js +++ b/src/handlers/userHandlers/wallet/index.js @@ -4,6 +4,7 @@ import RefreshHandler from './refreshHandler.js'; import CreateHandler from './createHandler.js'; import TopUpHandler from './topUpHandler.js'; import ArchiveHandler from './archiveHandler.js'; +import DepositHandler from './depositHandler.js'; import WalletHelpers from './helpers.js'; export default { @@ -16,6 +17,10 @@ export default { handleTopUpWallet: TopUpHandler.handleTopUpWallet, handleViewArchivedWallets: ArchiveHandler.handleViewArchivedWallets, handleBackToBalance: BalanceHandler.handleBackToBalance, + handleDepositSelectWallet: DepositHandler.handleDepositSelectWallet, + handleDepositSelectAmount: DepositHandler.handleDepositSelectAmount, + handleDepositInstruction: DepositHandler.handleDepositInstruction, + handleDepositCopyAddress: DepositHandler.handleDepositCopyAddress, getNetworkName: WalletHelpers.getNetworkName, getWalletAddress: WalletHelpers.getWalletAddress, }; \ No newline at end of file diff --git a/src/handlers/userHandlers/wallet/topUpHandler.js b/src/handlers/userHandlers/wallet/topUpHandler.js index 0e38ae5..20e0940 100644 --- a/src/handlers/userHandlers/wallet/topUpHandler.js +++ b/src/handlers/userHandlers/wallet/topUpHandler.js @@ -12,48 +12,66 @@ export default class TopUpHandler { try { const user = await UserService.getUserByTelegramId(telegramId); - const cryptoWallets = await db.allAsync(` - SELECT wallet_type, address FROM crypto_wallets - WHERE user_id = ? ORDER BY wallet_type - `, [user.id]); - - if (cryptoWallets.length === 0) { - await bot.editMessageText('You don\'t have any wallets yet.', { - chat_id: chatId, message_id: callbackQuery.message.message_id, - reply_markup: { inline_keyboard: [[{ text: '➕ Add Wallet', callback_data: 'add_wallet' }]] } - }); + if (!user) { + await editOrSendCallback(callbackQuery, 'Profile not found. Please use /start to create one.'); return; } - let message = '💰 *Available wallets for replenishment.* Click on a wallet to copy the address:\n\n'; + const cryptoWallets = await db.allAsync(` + SELECT wallet_type, address, balance FROM crypto_wallets + WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#' + ORDER BY wallet_type + `, [user.id]); + + if (cryptoWallets.length === 0) { + await bot.editMessageText('You don\'t have any wallets yet. Create one first.', { + chat_id: chatId, message_id: callbackQuery.message.message_id, + reply_markup: { inline_keyboard: [[{ text: '➕ Add Wallet', callback_data: 'add_wallet' }], [{ text: '« Back', callback_data: 'back_to_balance' }]] } + }); + return; + } const walletUtilsInstance = new WalletUtils( cryptoWallets.find(w => w.wallet_type === 'BTC')?.address, cryptoWallets.find(w => w.wallet_type === 'LTC')?.address, cryptoWallets.find(w => w.wallet_type === 'ETH')?.address, + cryptoWallets.find(w => w.wallet_type === 'USDT')?.address, + cryptoWallets.find(w => w.wallet_type === 'USDC')?.address, user.id, Date.now() - 30 * 24 * 60 * 60 * 1000 ); - const balances = await walletUtilsInstance.getAllBalances(); + const balances = await walletUtilsInstance.getAllBalancesFromDB(); - for (const [type, balance] of Object.entries(balances)) { - const wallet = cryptoWallets.find(w => - w.wallet_type === type.split(' ')[0] || - (type.includes('ERC-20') && w.wallet_type === 'ETH') - ); - if (wallet) { - message += `🔐 *${type}*\n`; - message += `├ Balance: ${balance.amount.toFixed(8)} ${type.split(' ')[0]}\n`; - message += `├ Value: $${balance.usdValue.toFixed(2)}\n`; - message += `└ Address: \`${wallet.address}\`\n\n`; - } + let message = '💰 *Your Wallets*\n\n'; + + for (const wallet of cryptoWallets) { + const balanceData = balances[wallet.wallet_type]; + const amount = balanceData ? balanceData.amount.toFixed(8) : '0.00000000'; + const usdValue = balanceData ? balanceData.usdValue.toFixed(2) : '0.00'; + message += `🔐 *${wallet.wallet_type}*\n`; + message += `├ Balance: ${amount} ${wallet.wallet_type}\n`; + message += `├ Value: $${usdValue}\n`; + message += `└ Address: \`${wallet.address}\`\n\n`; } + const walletButtons = cryptoWallets.map(w => ([{ + text: `💳 Deposit ${w.wallet_type}`, + callback_data: `deposit_wallet_${w.wallet_type}` + }])); + + const keyboard = { + inline_keyboard: [ + [{ text: '💳 Deposit via StealthEX', callback_data: 'deposit_select_wallet' }], + ...walletButtons, + [{ text: '« Back', callback_data: 'back_to_balance' }] + ] + }; + await bot.editMessageText(message, { chat_id: chatId, message_id: callbackQuery.message.message_id, parse_mode: 'Markdown', - reply_markup: { inline_keyboard: [[{ text: '« Back', callback_data: 'back_to_balance' }]] } + reply_markup: keyboard }); } catch (error) { logger.error({ err: error }, 'Error in handleTopUpWallet'); diff --git a/src/router/routes.js b/src/router/routes.js index 1041635..2c6a6aa 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -8,6 +8,7 @@ import userPurchaseHandler from '../handlers/userHandlers/userPurchaseHandler.js import userLocationHandler from '../handlers/userHandlers/userLocationHandler.js'; import userProductHandler from '../handlers/userHandlers/userProductHandler.js'; import userWalletsHandler from '../handlers/userHandlers/wallet/index.js'; +import DepositHandler from '../handlers/userHandlers/wallet/depositHandler.js'; import userDeletionHandler from '../handlers/userHandlers/userDeletionHandler.js'; import adminHandler from '../handlers/adminHandlers/adminHandler.js'; import adminUserLocationHandler from '../handlers/adminHandlers/adminUserLocationHandler.js'; @@ -103,6 +104,22 @@ export function registerRoutes() { logDebug(cq.data, 'handleTopUpWallet'); await userWalletsHandler.handleTopUpWallet(cq); }); + callbackRouter.registerExact('deposit_select_wallet', async (cq) => { + logDebug(cq.data, 'handleDepositSelectWallet'); + await DepositHandler.handleDepositSelectWallet(cq); + }); + callbackRouter.registerPrefix('deposit_wallet_', async (cq) => { + logDebug(cq.data, 'handleDepositSelectAmount'); + await DepositHandler.handleDepositSelectAmount(cq); + }); + callbackRouter.registerPrefix('deposit_amount_', async (cq) => { + logDebug(cq.data, 'handleDepositInstruction'); + await DepositHandler.handleDepositInstruction(cq); + }); + callbackRouter.registerPrefix('deposit_copy_', async (cq) => { + logDebug(cq.data, 'handleDepositCopyAddress'); + await DepositHandler.handleDepositCopyAddress(cq); + }); callbackRouter.registerExact('wallet_history', async (cq) => { logDebug(cq.data, 'handleWalletHistory'); await userWalletsHandler.handleWalletHistory(cq);