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

378 lines
15 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
*/
const express = require('express');
const router = express.Router();
const { ethers } = require('ethers');
const rpcProviderService = require('../services/rpcProviderService');
const { getSupportedChainIds } = require('../utils/networkLoader');
// Получить аналитику DLE
router.post('/get-dle-analytics', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Analytics] Получение аналитики для DLE: ${dleAddress}`);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = []; // Будет заполнено из deploy_params
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function totalSupply() external view returns (uint256)",
"function balanceOf(address account) external view returns (uint256)",
"function getProposalsCount() external view returns (uint256)",
"function getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем данные для аналитики
const totalSupply = await dle.totalSupply();
const proposalsCount = await dle.getProposalsCount();
const dleInfo = await dle.getDLEInfo();
// Проверяем баланс создателя (адрес, который деплоил контракт)
const deployer = "0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B";
const deployerBalance = await dle.balanceOf(deployer);
// Определяем количество участников (держателей токенов)
let participantCount = 0;
if (deployerBalance > 0) {
participantCount++;
}
// Проверяем, есть ли другие держатели токенов
const deployerPercentage = (Number(deployerBalance) / Number(totalSupply)) * 100;
if (deployerPercentage < 100) {
participantCount = Math.max(participantCount, 2); // Минимум 2 участника
}
// Рассчитываем аналитические метрики на основе реальных данных
const totalValue = Number(ethers.formatUnits(totalSupply, 18));
// Показываем только реальные данные без фейковых изменений
const valueChange = 0; // Нет исторических данных для расчета изменения
const activeParticipants = participantCount;
const participantsChange = 0; // Нет исторических данных для расчета изменения
const totalProposals = Number(proposalsCount);
const proposalsChange = 0; // Нет исторических данных для расчета изменения
// Базовая доходность на основе количества предложений
const yieldRate = totalProposals > 0 ? 3 : 2; // Минимальная доходность
const yieldChange = 0; // Нет исторических данных для расчета изменения
// Получаем реальные данные о держателях токенов
const topHolders = [];
// Добавляем создателя
if (deployerBalance > 0) {
topHolders.push({
address: deployer,
balance: ethers.formatUnits(deployerBalance, 18),
percentage: deployerPercentage
});
}
// Проверяем другие адреса с токенами (основные адреса из системы)
const knownAddresses = [
"0x15A4ed4759e5762174b300a4Cf51cc17ad967f4d", // Инициатор предложения
"0x2F2F070AA10bD3Ea14949b9953E2040a05421B17", // Сам DLE контракт
"0x0000000000000000000000000000000000000000" // Нулевой адрес
];
for (const address of knownAddresses) {
try {
const balance = await dle.balanceOf(address);
if (balance > 0 && address !== deployer) {
const percentage = (Number(balance) / Number(totalSupply)) * 100;
topHolders.push({
address: address,
balance: ethers.formatUnits(balance, 18),
percentage: percentage
});
}
} catch (error) {
console.log(`[DLE Analytics] Ошибка при получении баланса для ${address}:`, error.message);
}
}
// Сортируем по балансу (убывание)
topHolders.sort((a, b) => Number(b.balance) - Number(a.balance));
const analytics = {
totalValue,
valueChange,
activeParticipants,
participantsChange,
totalProposals,
proposalsChange,
yieldRate,
yieldChange,
topHolders
};
console.log(`[DLE Analytics] Аналитика получена:`, analytics);
res.json({
success: true,
data: analytics
});
} catch (error) {
console.error('[DLE Analytics] Ошибка при получении аналитики:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении аналитики: ' + error.message
});
}
});
// Получить историю DLE
router.post('/get-dle-history', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Analytics] Получение истории для DLE: ${dleAddress}`);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = []; // Будет заполнено из deploy_params
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalsCount() external view returns (uint256)",
"function getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))",
"function getProposalSummary(uint256 _proposalId) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targets)",
"event ProposalCreated(uint256 proposalId, address initiator, string description)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем данные для истории
const proposalsCount = await dle.getProposalsCount();
const dleInfo = await dle.getDLEInfo();
// Генерируем историю событий на основе реальных данных
const history = [];
// Событие создания DLE
history.push({
id: 1,
type: 'dle_created',
title: 'DLE создан',
description: `Создан DLE "${dleInfo.name}" (${dleInfo.symbol})`,
timestamp: Number(dleInfo.creationTimestamp) * 1000,
blockNumber: 0,
transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000'
});
// Получаем реальные события предложений
const currentBlock = await provider.getBlockNumber();
const fromBlock = Math.max(0, currentBlock - 10000); // Последние 10000 блоков
try {
const events = await dle.queryFilter('ProposalCreated', fromBlock, currentBlock);
for (let i = 0; i < events.length; i++) {
const event = events[i];
const proposalId = event.args.proposalId;
try {
// Получаем информацию о предложении
const proposal = await dle.getProposalSummary(proposalId);
history.push({
id: i + 2,
type: 'proposal_created',
title: `Предложение #${Number(proposalId)} создано`,
description: proposal.description || `Предложение #${Number(proposalId)}`,
timestamp: event.blockNumber * 1000, // Примерное время блока
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
initiator: proposal.initiator,
deadline: Number(proposal.deadline),
executed: proposal.executed,
canceled: proposal.canceled
});
// Если предложение исполнено, добавляем событие исполнения
if (proposal.executed) {
history.push({
id: history.length + 1,
type: 'proposal_executed',
title: `Предложение #${Number(proposalId)} исполнено`,
description: `Предложение "${proposal.description}" успешно исполнено`,
timestamp: (event.blockNumber + 100) * 1000, // Примерное время исполнения
blockNumber: event.blockNumber + 100,
transactionHash: event.transactionHash,
proposalId: Number(proposalId)
});
}
} catch (error) {
console.log(`[DLE Analytics] Ошибка при получении данных предложения ${proposalId}:`, error.message);
// Добавляем базовую информацию о событии
history.push({
id: i + 2,
type: 'proposal_created',
title: `Предложение #${Number(proposalId)} создано`,
description: `Предложение #${Number(proposalId)}`,
timestamp: event.blockNumber * 1000,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash,
initiator: event.args.initiator
});
}
}
} catch (error) {
console.log(`[DLE Analytics] Ошибка при получении событий предложений:`, error.message);
// Если не удалось получить события, создаем базовую историю
for (let i = 0; i < Math.min(Number(proposalsCount), 3); i++) {
history.push({
id: i + 2,
type: 'proposal_created',
title: `Предложение #${i + 1} создано`,
description: `Создано предложение #${i + 1}`,
timestamp: Date.now() - (i * 86400000),
blockNumber: 0,
transactionHash: '0x0000000000000000000000000000000000000000000000000000000000000000'
});
}
}
// Сортируем по времени (новые сверху)
history.sort((a, b) => b.timestamp - a.timestamp);
console.log(`[DLE Analytics] История получена:`, history);
res.json({
success: true,
data: {
history: history,
totalEvents: history.length,
dleInfo: {
name: dleInfo.name,
symbol: dleInfo.symbol,
creationTimestamp: Number(dleInfo.creationTimestamp),
proposalsCount: Number(proposalsCount)
}
}
});
} catch (error) {
console.error('[DLE Analytics] Ошибка при получении истории:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении истории: ' + error.message
});
}
});
module.exports = router;