309 lines
12 KiB
JavaScript
309 lines
12 KiB
JavaScript
// walletService.js
|
||
|
||
import db from "../config/database.js";
|
||
import config from "../config/config.js";
|
||
import WalletUtils from "../utils/walletUtils.js";
|
||
import WalletGenerator from "../utils/walletGenerator.js";
|
||
import crypto from 'crypto';
|
||
|
||
class WalletService {
|
||
static async getArchivedWalletsCount(user) {
|
||
try {
|
||
const archivedWallets = await db.getAsync(
|
||
`SELECT COUNT(*) AS total
|
||
FROM crypto_wallets
|
||
WHERE user_id = ? AND wallet_type LIKE '%#_%' ESCAPE '#'`,
|
||
[user.id]
|
||
);
|
||
return archivedWallets.total;
|
||
} catch (error) {
|
||
console.error('Error fetching archived wallets count:', error);
|
||
throw new Error('Failed to fetch archived wallets count');
|
||
}
|
||
}
|
||
|
||
static async getActiveWalletsBalance(userId) {
|
||
try {
|
||
const wallets = await db.allAsync(
|
||
`SELECT wallet_type, balance
|
||
FROM crypto_wallets
|
||
WHERE user_id = ? AND wallet_type NOT LIKE '%#_%' ESCAPE '#'`,
|
||
[userId]
|
||
);
|
||
|
||
const prices = await WalletUtils.getCryptoPrices();
|
||
|
||
let totalBalance = 0;
|
||
for (const wallet of wallets) {
|
||
const baseType = WalletUtils.getBaseWalletType(wallet.wallet_type);
|
||
const balance = wallet.balance || 0;
|
||
|
||
switch (baseType) {
|
||
case 'BTC':
|
||
totalBalance += balance * prices.btc;
|
||
break;
|
||
case 'LTC':
|
||
totalBalance += balance * prices.ltc;
|
||
break;
|
||
case 'ETH':
|
||
totalBalance += balance * prices.eth;
|
||
break;
|
||
case 'USDT':
|
||
totalBalance += balance; // USDT is 1:1 with USD
|
||
break;
|
||
case 'USDC':
|
||
totalBalance += balance; // USDC is 1:1 with USD
|
||
break;
|
||
}
|
||
}
|
||
|
||
return totalBalance;
|
||
} catch (error) {
|
||
console.error('Error fetching active wallets balance:', error);
|
||
throw new Error('Failed to fetch active wallets balance');
|
||
}
|
||
}
|
||
|
||
static async getArchivedWalletsBalance(userId) {
|
||
try {
|
||
const wallets = await db.allAsync(
|
||
`SELECT wallet_type, balance
|
||
FROM crypto_wallets
|
||
WHERE user_id = ? AND wallet_type LIKE '%#_%' ESCAPE '#'`,
|
||
[userId]
|
||
);
|
||
|
||
const prices = await WalletUtils.getCryptoPrices();
|
||
|
||
let totalBalance = 0;
|
||
for (const wallet of wallets) {
|
||
const baseType = WalletUtils.getBaseWalletType(wallet.wallet_type);
|
||
const balance = wallet.balance || 0;
|
||
|
||
switch (baseType) {
|
||
case 'BTC':
|
||
totalBalance += balance * prices.btc;
|
||
break;
|
||
case 'LTC':
|
||
totalBalance += balance * prices.ltc;
|
||
break;
|
||
case 'ETH':
|
||
totalBalance += balance * prices.eth;
|
||
break;
|
||
case 'USDT':
|
||
totalBalance += balance; // USDT is 1:1 with USD
|
||
break;
|
||
case 'USDC':
|
||
totalBalance += balance; // USDC is 1:1 with USD
|
||
break;
|
||
}
|
||
}
|
||
|
||
return totalBalance;
|
||
} catch (error) {
|
||
console.error('Error fetching archived wallets balance:', error);
|
||
throw new Error('Failed to fetch archived wallets balance');
|
||
}
|
||
}
|
||
|
||
// Метод для получения кошельков по типу
|
||
static async getWalletsByType(walletType) {
|
||
try {
|
||
const wallets = await db.allAsync(
|
||
`SELECT *
|
||
FROM crypto_wallets
|
||
WHERE wallet_type = ? OR wallet_type LIKE ?`,
|
||
[walletType, `${walletType}_%`]
|
||
);
|
||
|
||
return wallets;
|
||
} catch (error) {
|
||
console.error('Error fetching wallets by type:', error);
|
||
throw new Error('Failed to fetch wallets by type');
|
||
}
|
||
}
|
||
|
||
static async createWallet(userId, walletType) {
|
||
try {
|
||
// Генерация нового кошелька
|
||
const mnemonic = await WalletGenerator.generateMnemonic();
|
||
const wallets = await WalletGenerator.generateWallets(mnemonic, userId);
|
||
|
||
if (!wallets || typeof wallets !== 'object') {
|
||
throw new Error('Failed to generate wallets');
|
||
}
|
||
|
||
// Проверяем наличие базового типа кошелька
|
||
const baseType = walletType === 'USDT' || walletType === 'USDC' ? 'ETH' : walletType;
|
||
if (!wallets[baseType.toUpperCase()]) {
|
||
throw new Error(`Unsupported wallet type: ${walletType}`);
|
||
}
|
||
|
||
// Проверяем наличие ключа шифрования
|
||
if (!config.ENCRYPTION_KEY || typeof config.ENCRYPTION_KEY !== 'string') {
|
||
throw new Error('Encryption key is not configured');
|
||
}
|
||
|
||
// Проверяем и преобразуем userId
|
||
if (typeof userId !== 'number' && typeof userId !== 'string') {
|
||
throw new Error('Invalid user ID');
|
||
}
|
||
const userIdStr = userId.toString();
|
||
|
||
// Создаем ключ шифрования с использованием хэша
|
||
const baseKey = config.ENCRYPTION_KEY;
|
||
const combinedKey = baseKey + userIdStr;
|
||
|
||
// Создаем ключ и IV с использованием SHA-256
|
||
const key = crypto.createHash('sha256')
|
||
.update(config.ENCRYPTION_KEY + userIdStr)
|
||
.digest();
|
||
|
||
const iv = crypto.randomBytes(16); // Генерируем случайный IV
|
||
|
||
// Создаем шифр
|
||
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
||
|
||
// Шифруем мнемонику
|
||
let encrypted = cipher.update(mnemonic, 'utf8', 'hex');
|
||
encrypted += cipher.final('hex');
|
||
|
||
// Сохраняем зашифрованные данные вместе с IV
|
||
const encryptedMnemonic = iv.toString('hex') + ':' + encrypted;
|
||
|
||
// Определяем путь деривации
|
||
let derivationPath;
|
||
if (walletType === 'USDT') {
|
||
derivationPath = "m/44'/60'/0'/0/1"; // Путь для USDT
|
||
} else if (walletType === 'USDC') {
|
||
derivationPath = "m/44'/60'/0'/0/2"; // Путь для USDC
|
||
} else {
|
||
derivationPath = wallets[walletType.toUpperCase()].path;
|
||
}
|
||
|
||
// Получаем адрес для базового типа
|
||
const walletData = wallets[baseType.toUpperCase()];
|
||
if (!walletData || !walletData.address) {
|
||
console.error('Wallet generation failed:', {
|
||
baseType,
|
||
wallets: Object.keys(wallets),
|
||
walletData
|
||
});
|
||
throw new Error('Failed to generate wallet address');
|
||
}
|
||
const address = walletData.address;
|
||
|
||
// Вставляем новый кошелек в базу данных
|
||
await db.runAsync(
|
||
`INSERT INTO crypto_wallets
|
||
(user_id, wallet_type, address, derivation_path, mnemonic)
|
||
VALUES (?, ?, ?, ?, ?)`,
|
||
[userId, walletType, address, derivationPath, encryptedMnemonic]
|
||
);
|
||
|
||
// Проверяем успешность вставки
|
||
const insertedWallet = await db.getAsync(
|
||
`SELECT * FROM crypto_wallets
|
||
WHERE user_id = ? AND wallet_type = ?`,
|
||
[userId, walletType]
|
||
);
|
||
|
||
if (!insertedWallet) {
|
||
throw new Error('Failed to verify wallet insertion');
|
||
}
|
||
|
||
// Проверяем целостность записанной мнемоники
|
||
// Разделяем IV и зашифрованные данные для проверки
|
||
const [verify_ivHex, verify_encryptedData] = insertedWallet.mnemonic.split(':');
|
||
const verify_iv = Buffer.from(verify_ivHex, 'hex');
|
||
|
||
// Создаем ключ для проверки
|
||
const verify_key = crypto.createHash('sha256')
|
||
.update(config.ENCRYPTION_KEY + userId)
|
||
.digest();
|
||
|
||
// Создаем дешифратор для проверки
|
||
const decipher = crypto.createDecipheriv('aes-256-cbc', verify_key, verify_iv);
|
||
|
||
// Дешифруем данные
|
||
let decryptedMnemonic;
|
||
try {
|
||
decryptedMnemonic = decipher.update(verify_encryptedData, 'hex', 'utf8');
|
||
decryptedMnemonic += decipher.final('utf8');
|
||
|
||
if (!decryptedMnemonic) {
|
||
throw new Error('Failed to decrypt mnemonic');
|
||
}
|
||
} catch (error) {
|
||
console.error('Decryption error:', error);
|
||
throw new Error('Failed to decrypt mnemonic: ' + error.message);
|
||
}
|
||
|
||
if (decryptedMnemonic !== mnemonic) {
|
||
console.error('Mnemonic verification failed for wallet:', walletType);
|
||
// Удаляем кошелек в случае ошибки
|
||
await db.runAsync(
|
||
`DELETE FROM crypto_wallets
|
||
WHERE user_id = ? AND wallet_type = ?`,
|
||
[userId, walletType]
|
||
);
|
||
throw new Error('Mnemonic verification failed');
|
||
}
|
||
|
||
console.log(`Successfully created and verified wallet: ${walletType}`, {
|
||
address,
|
||
derivationPath,
|
||
userId,
|
||
walletType
|
||
});
|
||
return {
|
||
address,
|
||
derivationPath,
|
||
userId,
|
||
walletType
|
||
};
|
||
|
||
} catch (error) {
|
||
console.error('Error creating wallet:', error);
|
||
throw new Error('Failed to create wallet: ' + error.message);
|
||
}
|
||
}
|
||
|
||
static async decryptMnemonic(encryptedMnemonic, userId) {
|
||
try {
|
||
// Проверяем наличие ключа шифрования
|
||
if (!config.ENCRYPTION_KEY || typeof config.ENCRYPTION_KEY !== 'string') {
|
||
throw new Error('Encryption key is not configured');
|
||
}
|
||
|
||
// Разделяем IV и зашифрованные данные
|
||
const [ivHex, encryptedData] = encryptedMnemonic.split(':');
|
||
if (!ivHex || !encryptedData) {
|
||
throw new Error('Invalid encrypted mnemonic format');
|
||
}
|
||
|
||
// Создаем ключ дешифрования
|
||
const key = crypto.createHash('sha256')
|
||
.update(config.ENCRYPTION_KEY + userId.toString())
|
||
.digest();
|
||
|
||
// Преобразуем IV из hex
|
||
const iv = Buffer.from(ivHex, 'hex');
|
||
|
||
// Создаем дешифратор
|
||
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
||
|
||
// Дешифруем данные
|
||
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
|
||
decrypted += decipher.final('utf8');
|
||
|
||
return decrypted;
|
||
} catch (error) {
|
||
console.error('Error decrypting mnemonic:', error);
|
||
throw new Error('Failed to decrypt mnemonic: ' + error.message);
|
||
}
|
||
}
|
||
}
|
||
|
||
export default WalletService;
|