Files
DLE/backend/services/tokenBalanceService.js
2025-10-15 21:43:18 +03:00

164 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
/**
* Сервис получения балансов токенов пользователя из БД и RPC
*/
const { ethers } = require('ethers');
const db = require('../db');
const logger = require('../utils/logger');
async function getUserTokenBalances(address) {
if (!address) return [];
// Получаем ключ шифрования
const encryptionUtils = require('../utils/encryptionUtils');
const encryptionKey = encryptionUtils.getEncryptionKey();
// Получаем токены и RPC с расшифровкой
const tokensResult = await db.getQuery()(
'SELECT id, min_balance, readonly_threshold, editor_threshold, created_at, updated_at, decrypt_text(name_encrypted, $1) as name, decrypt_text(address_encrypted, $1) as address, decrypt_text(network_encrypted, $1) as network FROM auth_tokens',
[encryptionKey]
);
const tokens = tokensResult.rows;
// Убрано - используем rpcService вместо прямого запроса к БД
// Используем правильный RPC URL из базы данных
const rpcService = require('./rpcProviderService');
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
const results = [];
// Получаем все RPC провайдеры из базы данных для маппинга
const allRpcProviders = await rpcService.getAllRpcProviders();
const networkToChainId = {};
// Создаем маппинг из базы данных
for (const provider of allRpcProviders) {
if (provider.network_id) {
networkToChainId[provider.network_id] = provider.chain_id;
}
}
for (const token of tokens) {
// Получаем chain_id из названия сети из базы данных
const chainId = networkToChainId[token.network];
if (!chainId) {
logger.warn(`[tokenBalanceService] Неизвестная сеть: ${token.network}`);
continue;
}
logger.info(`[tokenBalanceService] Ищем RPC для token.network: ${token.network} (chainId: ${chainId})`);
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
if (!rpcUrl) {
logger.warn(`[tokenBalanceService] RPC URL не найден для сети ${token.network} (chainId: ${chainId})`);
// Пропускаем токен, если нет RPC URL
results.push({
network: token.network,
tokenAddress: token.address,
tokenName: token.name,
symbol: token.symbol || '',
balance: '0',
minBalance: token.min_balance,
readonlyThreshold: token.readonly_threshold || 1,
editorThreshold: token.editor_threshold || 2,
error: 'RPC URL не настроен'
});
continue;
}
logger.info(`[tokenBalanceService] Найден RPC URL: ${rpcUrl}`);
// Создаем провайдер с таймаутом
const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, {
polling: false,
staticNetwork: true
});
// Устанавливаем таймаут для запросов
provider._getConnection().timeout = 10000; // 10 секунд
const tokenContract = new ethers.Contract(token.address, ERC20_ABI, provider);
let balance = '0';
try {
logger.info(`[tokenBalanceService] Получение баланса для ${token.name} (${token.address}) в сети ${token.network} для адреса ${address}`);
// Создаем промис с таймаутом
const balancePromise = tokenContract.balanceOf(address);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Request timeout')), 10000)
);
const rawBalance = await Promise.race([balancePromise, timeoutPromise]);
balance = ethers.formatUnits(rawBalance, 18);
if (!balance || isNaN(Number(balance))) balance = '0';
logger.info(`[tokenBalanceService] Баланс получен для ${token.name}: ${balance}`);
} catch (e) {
logger.error(
`[tokenBalanceService] Ошибка получения баланса для ${token.name} (${token.address}) в сети ${token.network}:`,
e.message || e
);
// Проверяем тип ошибки для лучшей диагностики
const errorMessage = e.message || e.toString();
let errorType = 'Неизвестная ошибка';
if (errorMessage.includes('timeout') || errorMessage.includes('TIMEOUT')) {
errorType = 'Таймаут соединения - возможно, нужен VPN';
} else if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND')) {
errorType = 'Не удается подключиться к RPC провайдеру';
} else if (errorMessage.includes('NETWORK_ERROR')) {
errorType = 'Ошибка сети - проверьте интернет-соединение';
}
balance = '0';
// Добавляем информацию об ошибке в результат
results.push({
network: token.network,
tokenAddress: token.address,
tokenName: token.name,
symbol: token.symbol || '',
balance,
minBalance: token.min_balance,
readonlyThreshold: token.readonly_threshold || 1,
editorThreshold: token.editor_threshold || 2,
error: errorType,
errorDetails: errorMessage
});
continue;
}
// Добавляем успешный результат
results.push({
network: token.network,
tokenAddress: token.address,
tokenName: token.name,
symbol: token.symbol || '',
balance,
minBalance: token.min_balance,
readonlyThreshold: token.readonly_threshold || 1,
editorThreshold: token.editor_threshold || 2,
});
}
// Преобразуем в обычный массив для корректной сериализации
return JSON.parse(JSON.stringify(results));
}
module.exports = { getUserTokenBalances };