refactor(arch): split userWalletsHandler.js into 7 modular files (#52)
- 747-line monolith → 8 files (all ≤108 lines) - balanceHandler (96 lines): showBalance, handleBackToBalance - historyHandler (107 lines): handleTransactionHistory, handleWalletHistory - refreshHandler (75 lines): handleRefreshBalance with balance refresh - createHandler (94 lines): handleAddWallet, handleGenerateWallet - topUpHandler (60 lines): handleTopUpWallet - archiveHandler (86 lines): handleViewArchivedWallets - helpers (19 lines): getNetworkName, getWalletAddress - index.js (20 lines): re-exports all 11 handler methods - Removed duplicate getBaseWalletType (now uses WalletUtils) - Removed duplicate getNetworkName (now in helpers.js)
This commit is contained in:
@@ -1,747 +0,0 @@
|
||||
// userWalletsHandler.js
|
||||
|
||||
import db from '../../config/database.js';
|
||||
import WalletGenerator from '../../utils/walletGenerator.js';
|
||||
import WalletUtils from '../../utils/walletUtils.js';
|
||||
import UserService from "../../services/userService.js";
|
||||
import WalletService from "../../services/walletService.js";
|
||||
import bot from "../../context/bot.js";
|
||||
import Validators from '../../utils/validators.js';
|
||||
|
||||
export default class UserWalletsHandler {
|
||||
|
||||
static async showBalance(msg) {
|
||||
const chatId = msg.chat.id;
|
||||
const telegramId = msg.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
|
||||
if (!user) {
|
||||
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Пересчитываем баланс перед отображением
|
||||
await UserService.recalculateUserBalanceByTelegramId(telegramId);
|
||||
|
||||
// Получаем обновленные данные пользователя
|
||||
const updatedUser = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
|
||||
// Получаем активные криптокошельки
|
||||
const cryptoWallets = await db.allAsync(`
|
||||
SELECT wallet_type, address, balance
|
||||
FROM crypto_wallets
|
||||
WHERE user_id = ?
|
||||
ORDER BY wallet_type
|
||||
`, [updatedUser.id]);
|
||||
|
||||
let message = '💰 *Your Active Wallets:*\n\n';
|
||||
|
||||
if (cryptoWallets.length > 0) {
|
||||
const walletUtilsInstance = new WalletUtils(
|
||||
cryptoWallets.find(w => w.wallet_type === 'BTC')?.address, // BTC address
|
||||
cryptoWallets.find(w => w.wallet_type === 'LTC')?.address, // LTC address
|
||||
cryptoWallets.find(w => w.wallet_type === 'ETH')?.address, // ETH address
|
||||
cryptoWallets.find(w => w.wallet_type === 'USDT')?.address, // USDT address
|
||||
cryptoWallets.find(w => w.wallet_type === 'USDC')?.address, // USDC address
|
||||
updatedUser.id
|
||||
);
|
||||
|
||||
const balances = await walletUtilsInstance.getAllBalancesFromDB();
|
||||
let totalUsdValue = 0;
|
||||
|
||||
// Отображаем активные кошельки
|
||||
for (const [type, balance] of Object.entries(balances)) {
|
||||
const wallet = cryptoWallets.find(w => w.wallet_type === type.split(' ')[0]);
|
||||
|
||||
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`;
|
||||
totalUsdValue += balance.usdValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Общий баланс криптовалют
|
||||
message += `📊 *Total Crypto Balance:* $${totalUsdValue.toFixed(2)}\n`;
|
||||
|
||||
// Бонусный баланс
|
||||
message += `🎁 *Bonus Balance:* $${updatedUser.bonus_balance.toFixed(2)}\n`;
|
||||
|
||||
// Доступный баланс (bonus_balance + total_balance)
|
||||
const availableBalance = updatedUser.bonus_balance + (updatedUser.total_balance || 0);
|
||||
message += `💰 *Available Balance:* $${availableBalance.toFixed(2)}\n`;
|
||||
} else {
|
||||
message = 'You don\'t have any active wallets yet.';
|
||||
}
|
||||
|
||||
// Проверяем, есть ли архивные кошельки
|
||||
const archivedCount = await WalletService.getArchivedWalletsCount(updatedUser);
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '➕ Add Crypto Wallet', callback_data: 'add_wallet' },
|
||||
{ text: '💸 Top Up', callback_data: 'top_up_wallet' }
|
||||
],
|
||||
[{ text: '🔄 Refresh Balance', callback_data: 'refresh_balance' }]
|
||||
]
|
||||
};
|
||||
|
||||
// Добавляем кнопку архивных кошельков, если они есть
|
||||
if (archivedCount > 0) {
|
||||
keyboard.inline_keyboard.splice(2, 0, [
|
||||
{ text: `📁 Archived Wallets (${archivedCount})`, callback_data: 'view_archived_wallets' }
|
||||
]);
|
||||
}
|
||||
|
||||
// Добавляем кнопку истории транзакций
|
||||
keyboard.inline_keyboard.splice(3, 0, [
|
||||
{ text: '📊 Transaction History', callback_data: 'view_transaction_history_0' }
|
||||
]);
|
||||
|
||||
await bot.sendMessage(chatId, message, {
|
||||
reply_markup: keyboard,
|
||||
parse_mode: 'Markdown'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in showBalance:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading balance. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleTransactionHistory(callbackQuery, page = 0) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
|
||||
if (!user) {
|
||||
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch transactions with pagination
|
||||
const limit = 10;
|
||||
const offset = page * limit;
|
||||
const transactions = await db.allAsync(`
|
||||
SELECT amount, tx_hash, created_at, wallet_type
|
||||
FROM transactions
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, [user.id, limit, offset]);
|
||||
|
||||
let message = '';
|
||||
|
||||
if (transactions.length > 0) {
|
||||
message = '📊 *Transaction History:*\n\n';
|
||||
transactions.forEach(tx => {
|
||||
const date = new Date(tx.created_at).toLocaleString();
|
||||
message += `💰 Amount: ${tx.amount}\n`;
|
||||
message += `🔗 TX Hash: \`${tx.tx_hash}\`\n`;
|
||||
message += `🕒 Date: ${date}\n`;
|
||||
message += `💼 Wallet Type: ${tx.wallet_type}\n\n`;
|
||||
});
|
||||
} else {
|
||||
message = '📊 *Transaction History:*\n\nNo transactions found.';
|
||||
}
|
||||
|
||||
// Create pagination buttons
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '« Back', callback_data: 'back_to_balance' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
// Add "Previous" button if not on the first page
|
||||
if (page > 0) {
|
||||
keyboard.inline_keyboard.unshift([
|
||||
{ text: '⬅️ Previous', callback_data: `view_transaction_history_${page - 1}` }
|
||||
]);
|
||||
}
|
||||
|
||||
// Add "Next" button if there are more transactions
|
||||
const nextTransactions = await db.allAsync(`
|
||||
SELECT amount, tx_hash, created_at, wallet_type
|
||||
FROM transactions
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, [user.id, limit, offset + limit]);
|
||||
|
||||
if (nextTransactions.length > 0) {
|
||||
keyboard.inline_keyboard.push([
|
||||
{ text: '➡️ Next', callback_data: `view_transaction_history_${page + 1}` }
|
||||
]);
|
||||
}
|
||||
|
||||
await bot.editMessageText(message, {
|
||||
chat_id: chatId,
|
||||
message_id: callbackQuery.message.message_id,
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: keyboard
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleTransactionHistory:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleRefreshBalance(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
|
||||
try {
|
||||
// Отправляем промежуточный ответ на callback-запрос
|
||||
await bot.answerCallbackQuery(callbackQuery.id, { text: '🔄 Refreshing balances...' });
|
||||
|
||||
const user = await UserService.getUserByTelegramId(callbackQuery.from.id.toString());
|
||||
|
||||
if (!user) {
|
||||
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем активные кошельки пользователя из таблицы crypto_wallets
|
||||
const activeWallets = await db.allAsync(`
|
||||
SELECT wallet_type, address
|
||||
FROM crypto_wallets
|
||||
WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#'
|
||||
`, [user.id]);
|
||||
|
||||
console.log('[DEBUG] Active wallets:', activeWallets); // Логируем активные кошельки
|
||||
|
||||
// Создаем объект для хранения адресов кошельков
|
||||
const walletAddresses = {
|
||||
btc: activeWallets.find(w => w.wallet_type === 'BTC')?.address || null,
|
||||
ltc: activeWallets.find(w => w.wallet_type === 'LTC')?.address || null,
|
||||
eth: activeWallets.find(w => w.wallet_type === 'ETH')?.address || null,
|
||||
usdt: activeWallets.find(w => w.wallet_type === 'USDT')?.address || null,
|
||||
usdc: activeWallets.find(w => w.wallet_type === 'USDC')?.address || null,
|
||||
};
|
||||
|
||||
console.log('[DEBUG] Wallet addresses:', walletAddresses); // Логируем адреса кошельков
|
||||
|
||||
// Используем getAllBalancesExt для обновления балансов
|
||||
const walletUtilsInstance = new WalletUtils(
|
||||
walletAddresses.btc, // BTC address
|
||||
walletAddresses.ltc, // LTC address
|
||||
walletAddresses.eth, // ETH address
|
||||
walletAddresses.usdt, // USDT address
|
||||
walletAddresses.usdc, // USDC address
|
||||
user.id,
|
||||
Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||
);
|
||||
|
||||
console.log('[DEBUG] Calling getAllBalancesExt...'); // Логируем вызов метода
|
||||
const balances = await walletUtilsInstance.getAllBalancesExt();
|
||||
console.log('[DEBUG] Balances:', balances); // Логируем полученные балансы
|
||||
|
||||
// Обновляем балансы в таблице crypto_wallets только если они изменились
|
||||
for (const [type, balance] of Object.entries(balances)) {
|
||||
// Определяем адрес кошелька для данного типа
|
||||
let address;
|
||||
switch (type) {
|
||||
case 'BTC':
|
||||
address = walletAddresses.btc;
|
||||
break;
|
||||
case 'LTC':
|
||||
address = walletAddresses.ltc;
|
||||
break;
|
||||
case 'ETH':
|
||||
address = walletAddresses.eth;
|
||||
break;
|
||||
case 'USDT':
|
||||
address = walletAddresses.usdt;
|
||||
break;
|
||||
case 'USDC':
|
||||
address = walletAddresses.usdc;
|
||||
break;
|
||||
default:
|
||||
console.warn(`[DEBUG] Unknown wallet type: ${type}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!address) {
|
||||
console.warn(`[DEBUG] Address not found for wallet type: ${type}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Получаем текущий баланс для данного адреса
|
||||
const currentBalance = await db.getAsync(`
|
||||
SELECT balance
|
||||
FROM crypto_wallets
|
||||
WHERE user_id = ? AND address = ?
|
||||
`, [user.id, address]);
|
||||
|
||||
// Проверяем, изменился ли баланс
|
||||
if (currentBalance?.balance !== balance.amount) {
|
||||
await db.runAsync(`
|
||||
UPDATE crypto_wallets
|
||||
SET balance = ?
|
||||
WHERE user_id = ? AND address = ?
|
||||
`, [balance.amount, user.id, address]); // Обновляем баланс по уникальному адресу
|
||||
console.log(`Баланс для ${type} (${address}) обновлен: ${currentBalance?.balance || 0} -> ${balance.amount}`);
|
||||
} else {
|
||||
console.log(`Баланс для ${type} (${address}) не изменился, обновление не требуется.`);
|
||||
}
|
||||
}
|
||||
|
||||
// Пересчитываем баланс пользователя
|
||||
await UserService.recalculateUserBalanceByTelegramId(callbackQuery.from.id);
|
||||
|
||||
// Отображаем обновленные балансы
|
||||
await this.showBalance({
|
||||
chat: { id: chatId },
|
||||
from: { id: callbackQuery.from.id }
|
||||
});
|
||||
|
||||
// Удаляем сообщение "Refreshing balances..."
|
||||
await bot.deleteMessage(chatId, messageId);
|
||||
} catch (error) {
|
||||
console.error('Error in handleRefreshBalance:', error);
|
||||
|
||||
// Уведомляем пользователя об ошибке
|
||||
await bot.answerCallbackQuery(callbackQuery.id, { text: '❌ Error refreshing balances. Please try again.' });
|
||||
|
||||
// Отправляем сообщение об ошибке в чат
|
||||
await bot.sendMessage(chatId, '❌ Error refreshing balances. Please try again.', {
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '« Back', callback_data: 'back_to_balance' }
|
||||
]]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
static async handleAddWallet(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
|
||||
const cryptoOptions = [
|
||||
['BTC', 'ETH', 'LTC'],
|
||||
['USDT', 'USDC']
|
||||
];
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
...cryptoOptions.map(row =>
|
||||
row.map(coin => ({
|
||||
text: coin,
|
||||
callback_data: `generate_wallet_${coin.replace(' ', '_')}`
|
||||
}))
|
||||
),
|
||||
[{ text: '« Back', callback_data: 'back_to_balance' }]
|
||||
]
|
||||
};
|
||||
|
||||
await bot.editMessageText(
|
||||
'🔐 Select cryptocurrency to generate wallet:',
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: callbackQuery.message.message_id,
|
||||
reply_markup: keyboard
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static async handleGenerateWallet(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
const walletType = callbackQuery.data.replace('generate_wallet_', '').replace('_', ' ');
|
||||
|
||||
if (!Validators.isValidWalletType(walletType)) {
|
||||
await bot.sendMessage(chatId, 'Invalid wallet type.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId);
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
await db.runAsync('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
// Получаем существующий кошелек этого типа
|
||||
const existingWallet = await db.getAsync(
|
||||
'SELECT id, address FROM crypto_wallets WHERE user_id = ? AND wallet_type = ?',
|
||||
[user.id, walletType]
|
||||
);
|
||||
|
||||
if (existingWallet) {
|
||||
// Архивируем старый кошелек, добавляя суффикс с таймштампом
|
||||
const timestamp = Date.now();
|
||||
await db.runAsync(
|
||||
'UPDATE crypto_wallets SET wallet_type = ? WHERE id = ?',
|
||||
[`${walletType}_${timestamp}`, existingWallet.id]
|
||||
);
|
||||
}
|
||||
|
||||
// Создаем новый кошелек с использованием WalletService
|
||||
const walletResult = await WalletService.createWallet(user.id, walletType);
|
||||
|
||||
if (!walletResult || !walletResult.address) {
|
||||
console.error('Wallet creation failed:', {
|
||||
error: walletResult,
|
||||
userId: user.id,
|
||||
walletType
|
||||
});
|
||||
throw new Error('Failed to generate wallet address');
|
||||
}
|
||||
|
||||
// Получаем адрес для отображения
|
||||
const displayAddress = walletResult.address;
|
||||
const network = this.getNetworkName(walletType);
|
||||
|
||||
console.log('Wallet created successfully:', {
|
||||
address: displayAddress,
|
||||
derivationPath: walletResult.derivationPath,
|
||||
userId: user.id,
|
||||
walletType,
|
||||
network
|
||||
});
|
||||
|
||||
let message = `✅ New wallet generated successfully!\n\n`;
|
||||
message += `Type: ${walletType}\n`;
|
||||
message += `Network: ${network}\n`;
|
||||
message += `Address: \`${displayAddress}\`\n\n`;
|
||||
|
||||
if (existingWallet) {
|
||||
message += `ℹ️ Your previous wallet has been archived and will remain accessible for existing funds.\n`;
|
||||
}
|
||||
|
||||
message += `\n⚠️ Important: Your recovery phrase has been securely stored. Keep your wallet address safe!`;
|
||||
|
||||
await bot.editMessageText(message, {
|
||||
chat_id: chatId,
|
||||
message_id: callbackQuery.message.message_id,
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '« Back to Balance', callback_data: 'back_to_balance' }
|
||||
]]
|
||||
}
|
||||
});
|
||||
|
||||
await db.runAsync('COMMIT');
|
||||
} catch (error) {
|
||||
await db.runAsync('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating wallet:', error);
|
||||
await bot.editMessageText(
|
||||
'❌ Error generating wallet. Please try again.',
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: callbackQuery.message.message_id,
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '« Back to Balance', callback_data: 'back_to_balance' }
|
||||
]]
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static async handleTopUpWallet(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId);
|
||||
|
||||
// Get crypto wallets
|
||||
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' }
|
||||
]]
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = '💰 *Available wallets for replenishment, all you need to do is click on the wallet where you will replenish funds and it will be copied to the clipboard, then paste it on the crypto exchange as the recipient of funds.:*\n\n';
|
||||
|
||||
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,
|
||||
user.id,
|
||||
Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||
);
|
||||
|
||||
const balances = await walletUtilsInstance.getAllBalances();
|
||||
|
||||
for (const [type, balance] of Object.entries(balances)) {
|
||||
if (cryptoWallets.some(w => w.wallet_type === type.split(' ')[0] ||
|
||||
(type.includes('ERC-20') && w.wallet_type === 'ETH'))) {
|
||||
const wallet = cryptoWallets.find(w =>
|
||||
w.wallet_type === type.split(' ')[0] ||
|
||||
(type.includes('ERC-20') && w.wallet_type === 'ETH')
|
||||
);
|
||||
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`;
|
||||
}
|
||||
}
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
[{ 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: keyboard
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleTopUpWallet:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading wallets. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleWalletHistory(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
try {
|
||||
const user = UserService.getUserByTelegramId(telegramId);
|
||||
|
||||
const transactions = await db.allAsync(`
|
||||
SELECT type, amount, tx_hash, created_at, wallet_type
|
||||
FROM transactions
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 10
|
||||
`, [user.id]);
|
||||
|
||||
if (transactions.length === 0) {
|
||||
await bot.editMessageText(
|
||||
'No transactions found.',
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: callbackQuery.message.message_id,
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '« Back', callback_data: 'back_to_balance' }
|
||||
]]
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let message = '📊 *Recent Transactions:*\n\n';
|
||||
transactions.forEach(tx => {
|
||||
const date = new Date(tx.created_at).toLocaleString();
|
||||
const symbol = tx.type === 'deposit' ? '➕' : '➖';
|
||||
message += `${symbol} ${tx.amount} ${tx.wallet_type}\n`;
|
||||
message += `🔗 TX: \`${tx.tx_hash}\`\n`;
|
||||
message += `🕒 ${date}\n\n`;
|
||||
});
|
||||
|
||||
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' }
|
||||
]]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleWalletHistory:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleViewArchivedWallets(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
|
||||
// Get archived wallets and validate timestamps
|
||||
const archivedWallets = await db.allAsync(`
|
||||
SELECT wallet_type, address
|
||||
FROM crypto_wallets
|
||||
WHERE user_id = ? AND wallet_type LIKE '%_%'
|
||||
ORDER BY wallet_type
|
||||
`, [user.id]);
|
||||
|
||||
// Filter out wallets with invalid timestamps
|
||||
const validArchivedWallets = archivedWallets.filter(wallet => {
|
||||
const [, timestamp] = wallet.wallet_type.split('_');
|
||||
const date = new Date(parseInt(timestamp));
|
||||
return !isNaN(date.getTime()); // Check if date is valid
|
||||
});
|
||||
|
||||
if (validArchivedWallets.length === 0) {
|
||||
await bot.editMessageText(
|
||||
'No archived wallets found.',
|
||||
{
|
||||
chat_id: chatId,
|
||||
message_id: callbackQuery.message.message_id,
|
||||
reply_markup: {
|
||||
inline_keyboard: [[
|
||||
{ text: '« Back', callback_data: 'back_to_balance' }
|
||||
]]
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Group wallets by base type
|
||||
const groupedWallets = {};
|
||||
let totalUsdValue = 0;
|
||||
|
||||
for (const wallet of validArchivedWallets) {
|
||||
const [baseType, timestamp] = wallet.wallet_type.split('_');
|
||||
if (!groupedWallets[baseType]) {
|
||||
groupedWallets[baseType] = [];
|
||||
}
|
||||
groupedWallets[baseType].push({
|
||||
address: wallet.address,
|
||||
timestamp: parseInt(timestamp)
|
||||
});
|
||||
}
|
||||
|
||||
// Create wallet service instance
|
||||
const walletUtilsInstance = new WalletUtils(
|
||||
groupedWallets['BTC']?.[0]?.address,
|
||||
groupedWallets['LTC']?.[0]?.address,
|
||||
groupedWallets['ETH']?.[0]?.address || groupedWallets['USDT']?.[0]?.address || groupedWallets['USDC']?.[0]?.address,
|
||||
user.id,
|
||||
Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||
);
|
||||
|
||||
// Get all balances
|
||||
const balances = await walletUtilsInstance.getAllBalances();
|
||||
|
||||
let message = '📁 *Archived Wallets:*\n\n';
|
||||
|
||||
// Process each cryptocurrency type
|
||||
for (const baseType of Object.keys(groupedWallets).sort()) {
|
||||
let typeTotal = 0;
|
||||
let typeUsdTotal = 0;
|
||||
|
||||
message += `🔒 *${baseType}*\n`;
|
||||
|
||||
// Sort wallets by timestamp (newest first)
|
||||
const sortedWallets = groupedWallets[baseType].sort((a, b) => b.timestamp - a.timestamp);
|
||||
|
||||
for (const wallet of sortedWallets) {
|
||||
const date = new Date(wallet.timestamp);
|
||||
let balance = 0;
|
||||
let usdValue = 0;
|
||||
|
||||
// Get balance based on wallet type
|
||||
switch (baseType) {
|
||||
case 'BTC':
|
||||
balance = balances.BTC.amount;
|
||||
usdValue = balances.BTC.usdValue;
|
||||
break;
|
||||
case 'LTC':
|
||||
balance = balances.LTC.amount;
|
||||
usdValue = balances.LTC.usdValue;
|
||||
break;
|
||||
case 'ETH':
|
||||
case 'USDT':
|
||||
case 'USDC':
|
||||
balance = balances[baseType]?.amount || 0;
|
||||
usdValue = balances[baseType]?.usdValue || 0;
|
||||
break;
|
||||
}
|
||||
|
||||
typeTotal += balance;
|
||||
typeUsdTotal += usdValue;
|
||||
|
||||
message += `├ Balance: ${balance.toFixed(8)} ${baseType}\n`;
|
||||
message += `├ Value: $${usdValue.toFixed(2)}\n`;
|
||||
message += `├ Address: \`${wallet.address}\`\n`;
|
||||
message += `└ Archived: ${date.toLocaleDateString()}\n\n`;
|
||||
}
|
||||
|
||||
message += `📊 *Total ${baseType}*:\n`;
|
||||
message += `├ Amount: ${typeTotal.toFixed(8)} ${baseType}\n`;
|
||||
message += `└ Value: $${typeUsdTotal.toFixed(2)}\n\n`;
|
||||
|
||||
totalUsdValue += typeUsdTotal;
|
||||
}
|
||||
|
||||
message += `💰 *Total Value of Archived Wallets:* $${totalUsdValue.toFixed(2)}`;
|
||||
|
||||
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' }
|
||||
]]
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleViewArchivedWallets:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading archived wallets. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleBackToBalance(callbackQuery) {
|
||||
await this.showBalance({
|
||||
chat: { id: callbackQuery.message.chat.id },
|
||||
from: { id: callbackQuery.from.id }
|
||||
});
|
||||
await bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
static getWalletAddress(wallets, walletType) {
|
||||
if (walletType.includes('ERC-20')) return wallets.ETH.address;
|
||||
if (walletType === 'BTC') return wallets.BTC.address;
|
||||
if (walletType === 'LTC') return wallets.LTC.address;
|
||||
if (walletType === 'ETH') return wallets.ETH.address;
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
|
||||
static getNetworkName(walletType) {
|
||||
if (walletType.includes('USDT')) return 'Ethereum Network (ERC-20)';
|
||||
if (walletType.includes('USDC')) return 'Ethereum Network (ERC-20)';
|
||||
if (walletType === 'BTC') return 'Bitcoin Network';
|
||||
if (walletType === 'LTC') return 'Litecoin Network';
|
||||
if (walletType === 'ETH') return 'Ethereum Network';
|
||||
return 'Unknown Network';
|
||||
}
|
||||
}
|
||||
87
src/handlers/userHandlers/wallet/archiveHandler.js
Normal file
87
src/handlers/userHandlers/wallet/archiveHandler.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import db from '../../../config/database.js';
|
||||
import WalletUtils from '../../../utils/walletUtils.js';
|
||||
import UserService from '../../../services/userService.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
|
||||
export default class ArchiveHandler {
|
||||
static async handleViewArchivedWallets(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
|
||||
const archivedWallets = await db.allAsync(`
|
||||
SELECT wallet_type, address FROM crypto_wallets
|
||||
WHERE user_id = ? AND wallet_type LIKE '%_%' ORDER BY wallet_type
|
||||
`, [user.id]);
|
||||
|
||||
const validArchivedWallets = archivedWallets.filter(wallet => {
|
||||
const [, timestamp] = wallet.wallet_type.split('_');
|
||||
return !isNaN(new Date(parseInt(timestamp)).getTime());
|
||||
});
|
||||
|
||||
if (validArchivedWallets.length === 0) {
|
||||
await bot.editMessageText('No archived wallets found.', {
|
||||
chat_id: chatId, message_id: callbackQuery.message.message_id,
|
||||
reply_markup: { inline_keyboard: [[{ text: '« Back', callback_data: 'back_to_balance' }]] }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const groupedWallets = {};
|
||||
for (const wallet of validArchivedWallets) {
|
||||
const [baseType, timestamp] = wallet.wallet_type.split('_');
|
||||
if (!groupedWallets[baseType]) groupedWallets[baseType] = [];
|
||||
groupedWallets[baseType].push({ address: wallet.address, timestamp: parseInt(timestamp) });
|
||||
}
|
||||
|
||||
const walletUtilsInstance = new WalletUtils(
|
||||
groupedWallets['BTC']?.[0]?.address,
|
||||
groupedWallets['LTC']?.[0]?.address,
|
||||
groupedWallets['ETH']?.[0]?.address || groupedWallets['USDT']?.[0]?.address || groupedWallets['USDC']?.[0]?.address,
|
||||
user.id, Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||
);
|
||||
|
||||
const balances = await walletUtilsInstance.getAllBalances();
|
||||
let message = '📁 *Archived Wallets:*\n\n';
|
||||
let totalUsdValue = 0;
|
||||
|
||||
for (const baseType of Object.keys(groupedWallets).sort()) {
|
||||
let typeTotal = 0;
|
||||
let typeUsdTotal = 0;
|
||||
message += `🔒 *${baseType}*\n`;
|
||||
|
||||
const sortedWallets = groupedWallets[baseType].sort((a, b) => b.timestamp - a.timestamp);
|
||||
for (const wallet of sortedWallets) {
|
||||
const balance = balances[baseType]?.amount || 0;
|
||||
const usdValue = balances[baseType]?.usdValue || 0;
|
||||
typeTotal += balance;
|
||||
typeUsdTotal += usdValue;
|
||||
const date = new Date(wallet.timestamp);
|
||||
|
||||
message += `├ Balance: ${balance.toFixed(8)} ${baseType}\n`;
|
||||
message += `├ Value: $${usdValue.toFixed(2)}\n`;
|
||||
message += `├ Address: \`${wallet.address}\`\n`;
|
||||
message += `└ Archived: ${date.toLocaleDateString()}\n\n`;
|
||||
}
|
||||
|
||||
message += `📊 *Total ${baseType}*:\n`;
|
||||
message += `├ Amount: ${typeTotal.toFixed(8)} ${baseType}\n`;
|
||||
message += `└ Value: $${typeUsdTotal.toFixed(2)}\n\n`;
|
||||
totalUsdValue += typeUsdTotal;
|
||||
}
|
||||
|
||||
message += `💰 *Total Value of Archived Wallets:* $${totalUsdValue.toFixed(2)}`;
|
||||
|
||||
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' }]] }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleViewArchivedWallets:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading archived wallets. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
97
src/handlers/userHandlers/wallet/balanceHandler.js
Normal file
97
src/handlers/userHandlers/wallet/balanceHandler.js
Normal file
@@ -0,0 +1,97 @@
|
||||
import db from '../../../config/database.js';
|
||||
import WalletUtils from '../../../utils/walletUtils.js';
|
||||
import UserService from '../../../services/userService.js';
|
||||
import WalletService from '../../../services/walletService.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
|
||||
export default class BalanceHandler {
|
||||
static async showBalance(msg) {
|
||||
const chatId = msg.chat.id;
|
||||
const telegramId = msg.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
if (!user) {
|
||||
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
await UserService.recalculateUserBalanceByTelegramId(telegramId);
|
||||
const updatedUser = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
|
||||
const cryptoWallets = await db.allAsync(`
|
||||
SELECT wallet_type, address, balance
|
||||
FROM crypto_wallets WHERE user_id = ?
|
||||
ORDER BY wallet_type
|
||||
`, [updatedUser.id]);
|
||||
|
||||
let message = '💰 *Your Active Wallets:*\n\n';
|
||||
|
||||
if (cryptoWallets.length > 0) {
|
||||
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,
|
||||
updatedUser.id
|
||||
);
|
||||
|
||||
const balances = await walletUtilsInstance.getAllBalancesFromDB();
|
||||
let totalUsdValue = 0;
|
||||
|
||||
for (const [type, balance] of Object.entries(balances)) {
|
||||
const wallet = cryptoWallets.find(w => w.wallet_type === type.split(' ')[0]);
|
||||
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`;
|
||||
totalUsdValue += balance.usdValue;
|
||||
}
|
||||
}
|
||||
|
||||
message += `📊 *Total Crypto Balance:* $${totalUsdValue.toFixed(2)}\n`;
|
||||
message += `🎁 *Bonus Balance:* $${updatedUser.bonus_balance.toFixed(2)}\n`;
|
||||
const availableBalance = updatedUser.bonus_balance + (updatedUser.total_balance || 0);
|
||||
message += `💰 *Available Balance:* $${availableBalance.toFixed(2)}\n`;
|
||||
} else {
|
||||
message = 'You don\'t have any active wallets yet.';
|
||||
}
|
||||
|
||||
const archivedCount = await WalletService.getArchivedWalletsCount(updatedUser);
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
[
|
||||
{ text: '➕ Add Crypto Wallet', callback_data: 'add_wallet' },
|
||||
{ text: '💸 Top Up', callback_data: 'top_up_wallet' }
|
||||
],
|
||||
[{ text: '🔄 Refresh Balance', callback_data: 'refresh_balance' }]
|
||||
]
|
||||
};
|
||||
|
||||
if (archivedCount > 0) {
|
||||
keyboard.inline_keyboard.splice(2, 0, [
|
||||
{ text: `📁 Archived Wallets (${archivedCount})`, callback_data: 'view_archived_wallets' }
|
||||
]);
|
||||
}
|
||||
|
||||
keyboard.inline_keyboard.splice(3, 0, [
|
||||
{ text: '📊 Transaction History', callback_data: 'view_transaction_history_0' }
|
||||
]);
|
||||
|
||||
await bot.sendMessage(chatId, message, { reply_markup: keyboard, parse_mode: 'Markdown' });
|
||||
} catch (error) {
|
||||
console.error('Error in showBalance:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading balance. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleBackToBalance(callbackQuery) {
|
||||
await this.showBalance({
|
||||
chat: { id: callbackQuery.message.chat.id },
|
||||
from: { id: callbackQuery.from.id }
|
||||
});
|
||||
await bot.deleteMessage(callbackQuery.message.chat.id, callbackQuery.message.message_id);
|
||||
}
|
||||
}
|
||||
95
src/handlers/userHandlers/wallet/createHandler.js
Normal file
95
src/handlers/userHandlers/wallet/createHandler.js
Normal file
@@ -0,0 +1,95 @@
|
||||
import db from '../../../config/database.js';
|
||||
import WalletService from '../../../services/walletService.js';
|
||||
import Validators from '../../../utils/validators.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
import UserService from '../../../services/userService.js';
|
||||
|
||||
export default class CreateHandler {
|
||||
static async handleAddWallet(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const cryptoOptions = [['BTC', 'ETH', 'LTC'], ['USDT', 'USDC']];
|
||||
|
||||
const keyboard = {
|
||||
inline_keyboard: [
|
||||
...cryptoOptions.map(row =>
|
||||
row.map(coin => ({
|
||||
text: coin,
|
||||
callback_data: `generate_wallet_${coin.replace(' ', '_')}`
|
||||
}))
|
||||
),
|
||||
[{ text: '« Back', callback_data: 'back_to_balance' }]
|
||||
]
|
||||
};
|
||||
|
||||
await bot.editMessageText('🔐 Select cryptocurrency to generate wallet:', {
|
||||
chat_id: chatId, message_id: callbackQuery.message.message_id,
|
||||
reply_markup: keyboard
|
||||
});
|
||||
}
|
||||
|
||||
static async handleGenerateWallet(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
const walletType = callbackQuery.data.replace('generate_wallet_', '').replace('_', ' ');
|
||||
|
||||
if (!Validators.isValidWalletType(walletType)) {
|
||||
await bot.sendMessage(chatId, 'Invalid wallet type.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId);
|
||||
if (!user) throw new Error('User not found');
|
||||
|
||||
await db.runAsync('BEGIN TRANSACTION');
|
||||
|
||||
try {
|
||||
const existingWallet = await db.getAsync(
|
||||
'SELECT id, address FROM crypto_wallets WHERE user_id = ? AND wallet_type = ?',
|
||||
[user.id, walletType]
|
||||
);
|
||||
|
||||
if (existingWallet) {
|
||||
const timestamp = Date.now();
|
||||
await db.runAsync(
|
||||
'UPDATE crypto_wallets SET wallet_type = ? WHERE id = ?',
|
||||
[`${walletType}_${timestamp}`, existingWallet.id]
|
||||
);
|
||||
}
|
||||
|
||||
const walletResult = await WalletService.createWallet(user.id, walletType);
|
||||
if (!walletResult?.address) throw new Error('Failed to generate wallet address');
|
||||
|
||||
const network = WalletService.getNetworkName
|
||||
? WalletService.getNetworkName(walletType)
|
||||
: WalletUtils.getNetworkName(walletType);
|
||||
|
||||
let message = `✅ New wallet generated successfully!\n\n`;
|
||||
message += `Type: ${walletType}\nNetwork: ${network}\n`;
|
||||
message += `Address: \`${walletResult.address}\`\n\n`;
|
||||
|
||||
if (existingWallet) {
|
||||
message += `ℹ️ Your previous wallet has been archived.\n`;
|
||||
}
|
||||
message += `\n⚠️ Important: Your recovery phrase has been securely stored.`;
|
||||
|
||||
await bot.editMessageText(message, {
|
||||
chat_id: chatId, message_id: callbackQuery.message.message_id,
|
||||
parse_mode: 'Markdown',
|
||||
reply_markup: { inline_keyboard: [[{ text: '« Back to Balance', callback_data: 'back_to_balance' }]] }
|
||||
});
|
||||
|
||||
await db.runAsync('COMMIT');
|
||||
} catch (error) {
|
||||
await db.runAsync('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error generating wallet:', error);
|
||||
await bot.editMessageText('❌ Error generating wallet. Please try again.', {
|
||||
chat_id: chatId, message_id: callbackQuery.message.message_id,
|
||||
reply_markup: { inline_keyboard: [[{ text: '« Back to Balance', callback_data: 'back_to_balance' }]] }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/handlers/userHandlers/wallet/helpers.js
Normal file
20
src/handlers/userHandlers/wallet/helpers.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import WalletUtils from '../../../utils/walletUtils.js';
|
||||
|
||||
export default class WalletHelpers {
|
||||
static getNetworkName(walletType) {
|
||||
if (walletType.includes('USDT')) return 'Ethereum Network (ERC-20)';
|
||||
if (walletType.includes('USDC')) return 'Ethereum Network (ERC-20)';
|
||||
if (walletType === 'BTC') return 'Bitcoin Network';
|
||||
if (walletType === 'LTC') return 'Litecoin Network';
|
||||
if (walletType === 'ETH') return 'Ethereum Network';
|
||||
return 'Unknown Network';
|
||||
}
|
||||
|
||||
static getWalletAddress(wallets, walletType) {
|
||||
if (walletType.includes('ERC-20')) return wallets.ETH.address;
|
||||
if (walletType === 'BTC') return wallets.BTC.address;
|
||||
if (walletType === 'LTC') return wallets.LTC.address;
|
||||
if (walletType === 'ETH') return wallets.ETH.address;
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
}
|
||||
108
src/handlers/userHandlers/wallet/historyHandler.js
Normal file
108
src/handlers/userHandlers/wallet/historyHandler.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import db from '../../../config/database.js';
|
||||
import UserService from '../../../services/userService.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
|
||||
export default class HistoryHandler {
|
||||
static async handleTransactionHistory(callbackQuery, page = 0) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId.toString());
|
||||
if (!user) {
|
||||
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
const limit = 10;
|
||||
const offset = page * limit;
|
||||
const transactions = await db.allAsync(`
|
||||
SELECT amount, tx_hash, created_at, wallet_type
|
||||
FROM transactions WHERE user_id = ?
|
||||
ORDER BY created_at DESC LIMIT ? OFFSET ?
|
||||
`, [user.id, limit, offset]);
|
||||
|
||||
let message = '';
|
||||
|
||||
if (transactions.length > 0) {
|
||||
message = '📊 *Transaction History:*\n\n';
|
||||
transactions.forEach(tx => {
|
||||
const date = new Date(tx.created_at).toLocaleString();
|
||||
message += `💰 Amount: ${tx.amount}\n`;
|
||||
message += `🔗 TX Hash: \`${tx.tx_hash}\`\n`;
|
||||
message += `🕒 Date: ${date}\n`;
|
||||
message += `💼 Wallet Type: ${tx.wallet_type}\n\n`;
|
||||
});
|
||||
} else {
|
||||
message = '📊 *Transaction History:*\n\nNo transactions found.';
|
||||
}
|
||||
|
||||
const keyboard = { inline_keyboard: [[{ text: '« Back', callback_data: 'back_to_balance' }]] };
|
||||
|
||||
if (page > 0) {
|
||||
keyboard.inline_keyboard.unshift([
|
||||
{ text: '⬅️ Previous', callback_data: `view_transaction_history_${page - 1}` }
|
||||
]);
|
||||
}
|
||||
|
||||
const nextTransactions = await db.allAsync(`
|
||||
SELECT amount FROM transactions WHERE user_id = ?
|
||||
ORDER BY created_at DESC LIMIT ? OFFSET ?
|
||||
`, [user.id, limit, offset + limit]);
|
||||
|
||||
if (nextTransactions.length > 0) {
|
||||
keyboard.inline_keyboard.push([
|
||||
{ text: '➡️ Next', callback_data: `view_transaction_history_${page + 1}` }
|
||||
]);
|
||||
}
|
||||
|
||||
await bot.editMessageText(message, {
|
||||
chat_id: chatId, message_id: callbackQuery.message.message_id,
|
||||
parse_mode: 'Markdown', reply_markup: keyboard
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleTransactionHistory:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
|
||||
}
|
||||
}
|
||||
|
||||
static async handleWalletHistory(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
try {
|
||||
const user = await UserService.getUserByTelegramId(telegramId);
|
||||
const transactions = await db.allAsync(`
|
||||
SELECT type, amount, tx_hash, created_at, wallet_type
|
||||
FROM transactions WHERE user_id = ?
|
||||
ORDER BY created_at DESC LIMIT 10
|
||||
`, [user.id]);
|
||||
|
||||
if (transactions.length === 0) {
|
||||
await bot.editMessageText('No transactions found.', {
|
||||
chat_id: chatId, message_id: callbackQuery.message.message_id,
|
||||
reply_markup: { inline_keyboard: [[{ text: '« Back', callback_data: 'back_to_balance' }]] }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let message = '📊 *Recent Transactions:*\n\n';
|
||||
transactions.forEach(tx => {
|
||||
const date = new Date(tx.created_at).toLocaleString();
|
||||
const symbol = tx.type === 'deposit' ? '➕' : '➖';
|
||||
message += `${symbol} ${tx.amount} ${tx.wallet_type}\n`;
|
||||
message += `🔗 TX: \`${tx.tx_hash}\`\n`;
|
||||
message += `🕒 ${date}\n\n`;
|
||||
});
|
||||
|
||||
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' }]] }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleWalletHistory:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading transaction history. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/handlers/userHandlers/wallet/index.js
Normal file
21
src/handlers/userHandlers/wallet/index.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import BalanceHandler from './balanceHandler.js';
|
||||
import HistoryHandler from './historyHandler.js';
|
||||
import RefreshHandler from './refreshHandler.js';
|
||||
import CreateHandler from './createHandler.js';
|
||||
import TopUpHandler from './topUpHandler.js';
|
||||
import ArchiveHandler from './archiveHandler.js';
|
||||
import WalletHelpers from './helpers.js';
|
||||
|
||||
export default {
|
||||
showBalance: BalanceHandler.showBalance,
|
||||
handleTransactionHistory: HistoryHandler.handleTransactionHistory,
|
||||
handleWalletHistory: HistoryHandler.handleWalletHistory,
|
||||
handleRefreshBalance: RefreshHandler.handleRefreshBalance,
|
||||
handleAddWallet: CreateHandler.handleAddWallet,
|
||||
handleGenerateWallet: CreateHandler.handleGenerateWallet,
|
||||
handleTopUpWallet: TopUpHandler.handleTopUpWallet,
|
||||
handleViewArchivedWallets: ArchiveHandler.handleViewArchivedWallets,
|
||||
handleBackToBalance: BalanceHandler.handleBackToBalance,
|
||||
getNetworkName: WalletHelpers.getNetworkName,
|
||||
getWalletAddress: WalletHelpers.getWalletAddress,
|
||||
};
|
||||
76
src/handlers/userHandlers/wallet/refreshHandler.js
Normal file
76
src/handlers/userHandlers/wallet/refreshHandler.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import db from '../../../config/database.js';
|
||||
import WalletUtils from '../../../utils/walletUtils.js';
|
||||
import UserService from '../../../services/userService.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
|
||||
export default class RefreshHandler {
|
||||
static async handleRefreshBalance(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const messageId = callbackQuery.message.message_id;
|
||||
|
||||
try {
|
||||
await bot.answerCallbackQuery(callbackQuery.id, { text: '🔄 Refreshing balances...' });
|
||||
|
||||
const user = await UserService.getUserByTelegramId(callbackQuery.from.id.toString());
|
||||
if (!user) {
|
||||
await bot.sendMessage(chatId, 'Profile not found. Please use /start to create one.');
|
||||
return;
|
||||
}
|
||||
|
||||
const activeWallets = await db.allAsync(`
|
||||
SELECT wallet_type, address FROM crypto_wallets
|
||||
WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#'
|
||||
`, [user.id]);
|
||||
|
||||
const walletAddresses = {
|
||||
btc: activeWallets.find(w => w.wallet_type === 'BTC')?.address || null,
|
||||
ltc: activeWallets.find(w => w.wallet_type === 'LTC')?.address || null,
|
||||
eth: activeWallets.find(w => w.wallet_type === 'ETH')?.address || null,
|
||||
usdt: activeWallets.find(w => w.wallet_type === 'USDT')?.address || null,
|
||||
usdc: activeWallets.find(w => w.wallet_type === 'USDC')?.address || null,
|
||||
};
|
||||
|
||||
const walletUtilsInstance = new WalletUtils(
|
||||
walletAddresses.btc, walletAddresses.ltc, walletAddresses.eth,
|
||||
walletAddresses.usdt, walletAddresses.usdc,
|
||||
user.id, Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||
);
|
||||
|
||||
const balances = await walletUtilsInstance.getAllBalancesExt();
|
||||
|
||||
const walletTypeMap = { BTC: 'btc', LTC: 'ltc', ETH: 'eth', USDT: 'usdt', USDC: 'usdc' };
|
||||
|
||||
for (const [type, balance] of Object.entries(balances)) {
|
||||
const address = walletAddresses[walletTypeMap[type]];
|
||||
if (!address) continue;
|
||||
|
||||
const currentBalance = await db.getAsync(
|
||||
'SELECT balance FROM crypto_wallets WHERE user_id = ? AND address = ?',
|
||||
[user.id, address]
|
||||
);
|
||||
|
||||
if (currentBalance?.balance !== balance.amount) {
|
||||
await db.runAsync(
|
||||
'UPDATE crypto_wallets SET balance = ? WHERE user_id = ? AND address = ?',
|
||||
[balance.amount, user.id, address]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await UserService.recalculateUserBalanceByTelegramId(callbackQuery.from.id);
|
||||
|
||||
const BalanceHandler = (await import('./balanceHandler.js')).default;
|
||||
await BalanceHandler.showBalance({
|
||||
chat: { id: chatId }, from: { id: callbackQuery.from.id }
|
||||
});
|
||||
|
||||
await bot.deleteMessage(chatId, messageId);
|
||||
} catch (error) {
|
||||
console.error('Error in handleRefreshBalance:', error);
|
||||
await bot.answerCallbackQuery(callbackQuery.id, { text: '❌ Error refreshing balances.' });
|
||||
await bot.sendMessage(chatId, '❌ Error refreshing balances. Please try again.', {
|
||||
reply_markup: { inline_keyboard: [[{ text: '« Back', callback_data: 'back_to_balance' }]] }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
61
src/handlers/userHandlers/wallet/topUpHandler.js
Normal file
61
src/handlers/userHandlers/wallet/topUpHandler.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import db from '../../../config/database.js';
|
||||
import WalletUtils from '../../../utils/walletUtils.js';
|
||||
import UserService from '../../../services/userService.js';
|
||||
import bot from '../../../context/bot.js';
|
||||
|
||||
export default class TopUpHandler {
|
||||
static async handleTopUpWallet(callbackQuery) {
|
||||
const chatId = callbackQuery.message.chat.id;
|
||||
const telegramId = callbackQuery.from.id;
|
||||
|
||||
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' }]] }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let message = '💰 *Available wallets for replenishment.* Click on a wallet to copy the address:\n\n';
|
||||
|
||||
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,
|
||||
user.id,
|
||||
Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||
);
|
||||
|
||||
const balances = await walletUtilsInstance.getAllBalances();
|
||||
|
||||
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`;
|
||||
}
|
||||
}
|
||||
|
||||
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' }]] }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in handleTopUpWallet:', error);
|
||||
await bot.sendMessage(chatId, 'Error loading wallets. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import userHandler from "./handlers/userHandlers/userHandler.js";
|
||||
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/userWalletsHandler.js";
|
||||
import userWalletsHandler from "./handlers/userHandlers/wallet/index.js";
|
||||
import userDeletionHandler from "./handlers/userHandlers/userDeletionHandler.js";
|
||||
import adminHandler from "./handlers/adminHandlers/adminHandler.js";
|
||||
import adminUserLocationHandler from "./handlers/adminHandlers/adminUserLocationHandler.js";
|
||||
|
||||
Reference in New Issue
Block a user