update check ETH USDT USDC balance function
This commit is contained in:
@@ -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 }
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user