update check ETH USDT USDC balance function

This commit is contained in:
NW
2025-01-08 12:01:02 +00:00
parent e64f185eda
commit 66f5251795
8 changed files with 686 additions and 304 deletions

View File

@@ -1,127 +1,352 @@
import axios from 'axios';
import db from '../config/database.js'; // Импортируем базу данных
// Массив публичных RPC-узлов
const rpcNodes = [
"https://rpc.ankr.com/eth",
"https://cloudflare-eth.com",
"https://nodes.mewapi.io/rpc/eth",
];
// Список популярных API для получения цен на криптовалюты
const cryptoPriceAPIs = [
{
name: 'CoinGecko',
url: 'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,litecoin,ethereum&vs_currencies=usd',
parser: (data) => ({
btc: data.bitcoin?.usd || 0,
ltc: data.litecoin?.usd || 0,
eth: data.ethereum?.usd || 0,
usdt: 1, // USDT — это стейблкоин, его цена всегда 1 USD
usdc: 1 // USDC — это стейблкоин, его цена всегда 1 USD
})
},
{
name: 'Binance',
urls: {
btc: 'https://api.binance.com/api/v3/ticker/price?symbol=BTCUSDT',
ltc: 'https://api.binance.com/api/v3/ticker/price?symbol=LTCUSDT',
eth: 'https://api.binance.com/api/v3/ticker/price?symbol=ETHUSDT'
},
parser: async (urls) => {
const btcResponse = await axios.get(urls.btc);
const ltcResponse = await axios.get(urls.ltc);
const ethResponse = await axios.get(urls.eth);
return {
btc: parseFloat(btcResponse.data.price) || 0,
ltc: parseFloat(ltcResponse.data.price) || 0,
eth: parseFloat(ethResponse.data.price) || 0,
usdt: 1, // USDT — это стейблкоин, его цена всегда 1 USD
usdc: 1 // USDC — это стейблкоин, его цена всегда 1 USD
};
}
},
{
name: 'CryptoCompare',
url: 'https://min-api.cryptocompare.com/data/pricemulti?fsyms=BTC,LTC,ETH&tsyms=USD',
parser: (data) => ({
btc: data.BTC?.USD || 0,
ltc: data.LTC?.USD || 0,
eth: data.ETH?.USD || 0,
usdt: 1, // USDT — это стейблкоин, его цена всегда 1 USD
usdc: 1 // USDC — это стейблкоин, его цена всегда 1 USD
})
}
];
// Задержка между запросами
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Кеш для хранения курсов криптовалют
let cryptoPricesCache = null;
let cacheTimestamp = 0;
const CACHE_TTL = 60 * 1000; // 1 минута
export default class WalletUtils {
constructor(btcAddress, ltcAddress, ethAddress, usdtAddress, usdcAddress, userId, minTimestamp) {
this.btcAddress = btcAddress;
this.ltcAddress = ltcAddress;
this.ethAddress = ethAddress;
this.usdtAddress = usdtAddress;
this.usdcAddress = usdcAddress;
this.userId = userId;
this.minTimestamp = minTimestamp;
}
static async getCryptoPrices() {
try {
const response = await axios.get('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,litecoin,ethereum&vs_currencies=usd');
return {
btc: response.data.bitcoin?.usd || 0,
ltc: response.data.litecoin?.usd || 0,
eth: response.data.ethereum?.usd || 0,
usdt: 1, // USDT — это стейблкоин, его цена всегда 1 USD
usdc: 1 // USDC — это стейблкоин, его цена всегда 1 USD
};
} catch (error) {
console.error('Error fetching crypto prices:', error);
return {
btc: 0, ltc: 0, eth: 0, usdt: 1, usdc: 1
};
constructor(btcAddress, ltcAddress, ethAddress, usdtAddress, usdcAddress, userId, minTimestamp) {
this.btcAddress = btcAddress;
this.ltcAddress = ltcAddress;
this.ethAddress = ethAddress;
this.usdtAddress = usdtAddress;
this.usdcAddress = usdcAddress;
this.userId = userId;
this.minTimestamp = minTimestamp;
}
}
async fetchApiRequest(url) {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error(`Error fetching data from ${url}:`, error);
return null;
static getBaseWalletType(walletType) {
if (walletType.includes('ERC-20')) return 'ETH';
return walletType;
}
}
async getBtcBalance() {
if (!this.btcAddress) return 0;
try {
const url = `https://blockchain.info/balance?active=${this.btcAddress}`;
const data = await this.fetchApiRequest(url);
return data?.[this.btcAddress]?.final_balance / 100000000 || 0;
} catch (error) {
console.error('Error getting BTC balance:', error);
return 0;
static async getCryptoPrices() {
// Если кеш актуален, возвращаем его
if (cryptoPricesCache && Date.now() - cacheTimestamp < CACHE_TTL) {
console.log('[DEBUG] Using cached crypto prices:', cryptoPricesCache);
return cryptoPricesCache;
}
// Если кеш устарел, запрашиваем новые данные
for (const api of cryptoPriceAPIs) {
try {
console.log(`[DEBUG] Trying to fetch prices from ${api.name}...`);
let data;
if (api.name === 'Binance') {
data = await api.parser(api.urls);
} else {
const response = await axios.get(api.url);
data = api.parser(response.data);
}
console.log(`[DEBUG] Successfully fetched prices from ${api.name}:`, data);
// Обновляем кеш
cryptoPricesCache = data;
cacheTimestamp = Date.now();
return data;
} catch (error) {
if (error.response && error.response.status === 429) {
console.log(`[DEBUG] Rate limit exceeded on ${api.name}. Retrying after 2 seconds...`);
await sleep(2000);
continue; // Пробуем снова с тем же API
} else {
console.error(`[DEBUG] Error fetching prices from ${api.name}:`, error.message);
}
}
}
// Если все API не сработали, используем fallback-значения
console.error('[DEBUG] All APIs failed. Using fallback prices.');
cryptoPricesCache = {
btc: 0, ltc: 0, eth: 0, usdt: 1, usdc: 1
};
cacheTimestamp = Date.now();
return cryptoPricesCache;
}
}
async getLtcBalance() {
if (!this.ltcAddress) return 0;
try {
const url = `https://api.blockcypher.com/v1/ltc/main/addrs/${this.ltcAddress}/balance`;
const data = await this.fetchApiRequest(url);
return data?.balance / 100000000 || 0;
} catch (error) {
console.error('Error getting LTC balance:', error);
return 0;
async fetchApiRequest(url) {
try {
console.log(`[DEBUG] Fetching data from: ${url}`); // Логируем URL запроса
const response = await axios.get(url);
return response.data;
} catch (error) {
console.error(`Error fetching data from ${url}:`, error);
return null;
}
}
}
async getEthBalance() {
if (!this.ethAddress) return 0;
try {
const url = `https://api.etherscan.io/api?module=account&action=balance&address=${this.ethAddress}&tag=latest`;
const data = await this.fetchApiRequest(url);
return data?.result ? parseFloat(data.result) / 1e18 : 0;
} catch (error) {
console.error('Error getting ETH balance:', error);
return 0;
async fetchRpcRequest(method, params) {
console.log(`[DEBUG] fetchRpcRequest called with method: ${method}, params: ${JSON.stringify(params)}`); // Логируем вызов метода
const results = [];
for (const node of rpcNodes) {
try {
const response = await axios.post(node, {
jsonrpc: "2.0",
method,
params,
id: 1,
});
if (response.data && response.data.result) {
results.push(response.data.result);
console.log(`Запрос успешно выполнен на узле ${node}`); // Логируем успешный запрос
} else {
console.warn(`Некорректный ответ от узла ${node}`); // Логируем некорректный ответ
}
} catch (error) {
console.error(`Ошибка на узле ${node}: ${error.message}`); // Логируем ошибку
}
}
if (results.length === 0) {
throw new Error("Нет доступных узлов для выполнения запроса.");
}
const uniqueResults = [...new Set(results)];
if (uniqueResults.length === 1) {
console.log("Баланс совпадает на всех узлах:", uniqueResults[0]); // Логируем совпадение балансов
return uniqueResults[0];
} else {
console.warn("Результаты отличаются на некоторых узлах. Возвращаем первый результат."); // Логируем различия
return results[0];
}
}
}
async getUsdtErc20Balance() {
if (!this.usdtAddress) return 0;
try {
const url = `https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=0xdac17f958d2ee523a2206206994597c13d831ec7&address=${this.usdtAddress}&tag=latest`;
const data = await this.fetchApiRequest(url);
return data?.result ? parseFloat(data.result) / 1e6 : 0;
} catch (error) {
console.error('Error getting USDT ERC20 balance:', error);
return 0;
async getBtcBalance() {
if (!this.btcAddress) {
console.log('[DEBUG] BTC address is not provided, skipping balance check.'); // Логируем отсутствие адреса
return 0;
}
try {
const url = `https://blockchain.info/balance?active=${this.btcAddress}`;
console.log(`[DEBUG] Fetching BTC balance from: ${url}`); // Логируем URL запроса
const data = await this.fetchApiRequest(url);
return data?.[this.btcAddress]?.final_balance / 100000000 || 0;
} catch (error) {
console.error('Error getting BTC balance:', error);
return 0;
}
}
}
async getUsdcErc20Balance() {
if (!this.usdcAddress) return 0;
try {
const url = `https://api.etherscan.io/api?module=account&action=tokenbalance&contractaddress=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48&address=${this.usdcAddress}&tag=latest`;
const data = await this.fetchApiRequest(url);
return data?.result ? parseFloat(data.result) / 1e6 : 0;
} catch (error) {
console.error('Error getting USDC ERC20 balance:', error);
return 0;
async getLtcBalance() {
if (!this.ltcAddress) {
console.log('[DEBUG] LTC address is not provided, skipping balance check.'); // Логируем отсутствие адреса
return 0;
}
try {
const url = `https://api.blockcypher.com/v1/ltc/main/addrs/${this.ltcAddress}/balance`;
console.log(`[DEBUG] Fetching LTC balance from: ${url}`); // Логируем URL запроса
const data = await this.fetchApiRequest(url);
return data?.balance / 100000000 || 0;
} catch (error) {
console.error('Error getting LTC balance:', error);
return 0;
}
}
}
async getAllBalances() {
const [
btcBalance,
ltcBalance,
ethBalance,
usdtErc20Balance,
usdcErc20Balance,
prices
] = await Promise.all([
this.getBtcBalance(),
this.getLtcBalance(),
this.getEthBalance(),
this.getUsdtErc20Balance(),
this.getUsdcErc20Balance(),
WalletUtils.getCryptoPrices()
]);
async getEthBalance() {
if (!this.ethAddress) {
console.log('[DEBUG] ETH address is not provided, skipping balance check.'); // Логируем отсутствие адреса
return 0;
}
try {
console.log(`[DEBUG] Fetching ETH balance for address: ${this.ethAddress}`); // Логируем адрес
const balanceHex = await this.fetchRpcRequest("eth_getBalance", [this.ethAddress, "latest"]);
return parseInt(balanceHex, 16) / 1e18;
} catch (error) {
console.error('Error getting ETH balance:', error);
return 0;
}
}
return {
BTC: { amount: btcBalance, usdValue: btcBalance * prices.btc },
LTC: { amount: ltcBalance, usdValue: ltcBalance * prices.ltc },
ETH: { amount: ethBalance, usdValue: ethBalance * prices.eth },
'USDT ERC-20': { amount: usdtErc20Balance, usdValue: usdtErc20Balance },
'USDC ERC-20': { amount: usdcErc20Balance, usdValue: usdcErc20Balance }
};
}
async getUsdtErc20Balance() {
if (!this.usdtAddress) {
console.log('[DEBUG] USDT address is not provided, skipping balance check.'); // Логируем отсутствие адреса
return 0;
}
try {
console.log(`[DEBUG] Fetching USDT ERC-20 balance for address: ${this.usdtAddress}`); // Логируем адрес
const balanceHex = await this.fetchRpcRequest("eth_call", [
{
to: "0xdac17f958d2ee523a2206206994597c13d831ec7",
data: `0x70a08231000000000000000000000000${this.usdtAddress.slice(2)}`,
},
"latest"
]);
return parseInt(balanceHex, 16) / 1e6;
} catch (error) {
console.error('Error getting USDT ERC-20 balance:', error);
return 0;
}
}
async getUsdcErc20Balance() {
if (!this.usdcAddress) {
console.log('[DEBUG] USDC address is not provided, skipping balance check.'); // Логируем отсутствие адреса
return 0;
}
try {
console.log(`[DEBUG] Fetching USDC ERC-20 balance for address: ${this.usdcAddress}`); // Логируем адрес
const balanceHex = await this.fetchRpcRequest("eth_call", [
{
to: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
data: `0x70a08231000000000000000000000000${this.usdcAddress.slice(2)}`,
},
"latest"
]);
return parseInt(balanceHex, 16) / 1e6;
} catch (error) {
console.error('Error getting USDC ERC-20 balance:', error);
return 0;
}
}
async getAllBalancesFromDB() {
const prices = await WalletUtils.getCryptoPrices();
const balances = {
BTC: { amount: 0, usdValue: 0 },
LTC: { amount: 0, usdValue: 0 },
ETH: { amount: 0, usdValue: 0 },
'USDT ERC-20': { amount: 0, usdValue: 0 },
'USDC ERC-20': { amount: 0, usdValue: 0 }
};
// Получаем балансы из таблицы crypto_wallets
const wallets = await db.allAsync(`
SELECT wallet_type, balance FROM crypto_wallets WHERE user_id = ?
`, [this.userId]);
for (const wallet of wallets) {
const baseType = WalletUtils.getBaseWalletType(wallet.wallet_type);
const balance = wallet.balance || 0;
switch (baseType) {
case 'BTC':
balances.BTC.amount += balance;
balances.BTC.usdValue += balance * prices.btc;
break;
case 'LTC':
balances.LTC.amount += balance;
balances.LTC.usdValue += balance * prices.ltc;
break;
case 'ETH':
balances.ETH.amount += balance;
balances.ETH.usdValue += balance * prices.eth;
break;
case 'USDT':
balances['USDT ERC-20'].amount += balance;
balances['USDT ERC-20'].usdValue += balance;
break;
case 'USDC':
balances['USDC ERC-20'].amount += balance;
balances['USDC ERC-20'].usdValue += balance;
break;
}
}
return balances;
}
async getAllBalances() {
return await this.getAllBalancesFromDB();
}
async getAllBalancesExt() {
console.log('[DEBUG] getAllBalancesExt called'); // Логируем вызов метода
const [
btcBalance,
ltcBalance,
ethBalance,
usdtErc20Balance,
usdcErc20Balance,
prices
] = await Promise.all([
this.getBtcBalance(),
this.getLtcBalance(),
this.getEthBalance(),
this.getUsdtErc20Balance(),
this.getUsdcErc20Balance(),
WalletUtils.getCryptoPrices()
]);
console.log('[DEBUG] Balances fetched:', { // Логируем полученные балансы
btcBalance,
ltcBalance,
ethBalance,
usdtErc20Balance,
usdcErc20Balance,
prices
});
return {
BTC: { amount: btcBalance, usdValue: btcBalance * prices.btc },
LTC: { amount: ltcBalance, usdValue: ltcBalance * prices.ltc },
ETH: { amount: ethBalance, usdValue: ethBalance * prices.eth },
'USDT ERC-20': { amount: usdtErc20Balance, usdValue: usdtErc20Balance },
'USDC ERC-20': { amount: usdcErc20Balance, usdValue: usdcErc20Balance }
};
}
}