378 lines
15 KiB
JavaScript
378 lines
15 KiB
JavaScript
/**
|
||
* Copyright (c) 2024-2026 Тарабанов Александр Викторович
|
||
* 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/VC-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;
|