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
This commit is contained in:
@@ -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=
|
||||
|
||||
@@ -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 || ''
|
||||
};
|
||||
|
||||
212
src/handlers/userHandlers/wallet/depositHandler.js
Normal file
212
src/handlers/userHandlers/wallet/depositHandler.js
Normal file
@@ -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.' });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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');
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user