feat: Добавлены формы деплоя модулей DLE с полными настройками

- Создана форма деплоя TreasuryModule с детальными настройками казны
- Создана форма деплоя TimelockModule с настройками временных задержек
- Создана форма деплоя DLEReader с простой конфигурацией
- Добавлены маршруты и индексы для всех модулей
- Исправлены пути импорта BaseLayout
- Добавлены авторские права во все файлы
- Улучшена архитектура деплоя модулей отдельно от основного DLE
This commit is contained in:
2025-09-23 02:57:59 +03:00
parent 9f94295d15
commit de0f8aecf2
63 changed files with 11631 additions and 1920 deletions

View File

@@ -65,7 +65,8 @@ router.post('/read-dle-info', async (req, res) => {
"function balanceOf(address account) external view returns (uint256)",
"function quorumPercentage() external view returns (uint256)",
"function getCurrentChainId() external view returns (uint256)",
"function logoURI() external view returns (string memory)"
"function logoURI() external view returns (string memory)",
"function getModuleAddress(bytes32 _moduleId) external view returns (address)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
@@ -174,6 +175,36 @@ router.post('/read-dle-info', async (req, res) => {
}
}
// Читаем информацию о модулях
const modules = {};
try {
console.log(`[Blockchain] Читаем модули для DLE: ${dleAddress}`);
// Определяем известные модули
const moduleNames = ['reader', 'treasury', 'timelock'];
for (const moduleName of moduleNames) {
try {
// Вычисляем moduleId (keccak256 от имени модуля)
const moduleId = ethers.keccak256(ethers.toUtf8Bytes(moduleName));
// Получаем адрес модуля
const moduleAddress = await dle.getModuleAddress(moduleId);
if (moduleAddress && moduleAddress !== ethers.ZeroAddress) {
modules[moduleName] = moduleAddress;
console.log(`[Blockchain] Модуль ${moduleName}: ${moduleAddress}`);
} else {
console.log(`[Blockchain] Модуль ${moduleName} не инициализирован`);
}
} catch (moduleError) {
console.log(`[Blockchain] Ошибка при чтении модуля ${moduleName}:`, moduleError.message);
}
}
} catch (modulesError) {
console.log(`[Blockchain] Ошибка при чтении модулей:`, modulesError.message);
}
const blockchainData = {
name: dleInfo.name,
symbol: dleInfo.symbol,
@@ -193,7 +224,8 @@ router.post('/read-dle-info', async (req, res) => {
quorumPercentage: Number(quorumPercentage),
currentChainId: Number(currentChainId),
rpcUsed: rpcUrl,
participantCount: participantCount
participantCount: participantCount,
modules: modules // Информация о модулях
};
console.log(`[Blockchain] Данные DLE прочитаны из блокчейна:`, blockchainData);
@@ -212,92 +244,7 @@ router.post('/read-dle-info', async (req, res) => {
}
});
// Получение поддерживаемых сетей из смарт-контракта
router.post('/get-supported-chains', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[Blockchain] Получение поддерживаемых сетей для DLE: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// ABI для проверки поддерживаемых сетей
const dleAbi = [
"function isChainSupported(uint256 _chainId) external view returns (bool)",
"function getCurrentChainId() external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Список всех возможных сетей для проверки
const allChains = [
{ chainId: 1, name: 'Ethereum', description: 'Основная сеть Ethereum' },
{ chainId: 137, name: 'Polygon', description: 'Сеть Polygon' },
{ chainId: 56, name: 'BSC', description: 'Binance Smart Chain' },
{ chainId: 42161, name: 'Arbitrum', description: 'Arbitrum One' },
{ chainId: 10, name: 'Optimism', description: 'Optimism' },
{ chainId: 8453, name: 'Base', description: 'Base' },
{ chainId: 43114, name: 'Avalanche', description: 'Avalanche C-Chain' },
{ chainId: 250, name: 'Fantom', description: 'Fantom Opera' },
{ chainId: 11155111, name: 'Sepolia', description: 'Ethereum Testnet Sepolia' },
{ chainId: 17000, name: 'Holesky', description: 'Ethereum Testnet Holesky' },
{ chainId: 80002, name: 'Polygon Amoy', description: 'Polygon Testnet Amoy' },
{ chainId: 84532, name: 'Base Sepolia', description: 'Base Sepolia Testnet' },
{ chainId: 421614, name: 'Arbitrum Sepolia', description: 'Arbitrum Sepolia Testnet' },
{ chainId: 80001, name: 'Mumbai', description: 'Polygon Testnet Mumbai' },
{ chainId: 97, name: 'BSC Testnet', description: 'Binance Smart Chain Testnet' },
{ chainId: 421613, name: 'Arbitrum Goerli', description: 'Arbitrum Testnet Goerli' }
];
const supportedChains = [];
// Проверяем каждую сеть через смарт-контракт
for (const chain of allChains) {
try {
const isSupported = await dle.isChainSupported(chain.chainId);
if (isSupported) {
supportedChains.push(chain);
}
} catch (error) {
console.log(`[Blockchain] Ошибка при проверке сети ${chain.chainId}:`, error.message);
// Продолжаем проверку других сетей
}
}
console.log(`[Blockchain] Найдено поддерживаемых сетей: ${supportedChains.length}`);
res.json({
success: true,
data: {
chains: supportedChains,
totalCount: supportedChains.length
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при получении поддерживаемых сетей:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении поддерживаемых сетей: ' + error.message
});
}
});
// УДАЛЕНО: дублируется в dleMultichain.js
// Получение списка всех предложений
router.post('/get-proposals', async (req, res) => {
@@ -354,7 +301,7 @@ router.post('/get-proposals', async (req, res) => {
// Пробуем несколько раз для новых предложений
let proposal, isPassed;
let retryCount = 0;
const maxRetries = 3;
const maxRetries = 1;
while (retryCount < maxRetries) {
try {
@@ -708,111 +655,9 @@ router.post('/load-deactivation-proposals', async (req, res) => {
}
});
// Создать предложение о добавлении модуля
router.post('/create-add-module-proposal', async (req, res) => {
try {
const { dleAddress, description, duration, moduleId, moduleAddress, chainId } = req.body;
if (!dleAddress || !description || !duration || !moduleId || !moduleAddress || !chainId) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
// УДАЛЕНО: дублируется в dleModules.js
console.log(`[Blockchain] Создание предложения о добавлении модуля: ${moduleId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function createAddModuleProposal(string memory _description, uint256 _duration, bytes32 _moduleId, address _moduleAddress, uint256 _chainId) external returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Создаем предложение
const tx = await dle.createAddModuleProposal(description, duration, moduleId, moduleAddress, chainId);
const receipt = await tx.wait();
console.log(`[Blockchain] Предложение о добавлении модуля создано:`, receipt);
res.json({
success: true,
data: {
proposalId: receipt.logs[0].args.proposalId,
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при создании предложения о добавлении модуля:', error);
res.status(500).json({
success: false,
error: 'Ошибка при создании предложения о добавлении модуля: ' + error.message
});
}
});
// Создать предложение об удалении модуля
router.post('/create-remove-module-proposal', async (req, res) => {
try {
const { dleAddress, description, duration, moduleId, chainId } = req.body;
if (!dleAddress || !description || !duration || !moduleId || !chainId) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[Blockchain] Создание предложения об удалении модуля: ${moduleId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function createRemoveModuleProposal(string memory _description, uint256 _duration, bytes32 _moduleId, uint256 _chainId) external returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Создаем предложение
const tx = await dle.createRemoveModuleProposal(description, duration, moduleId, chainId);
const receipt = await tx.wait();
console.log(`[Blockchain] Предложение об удалении модуля создано:`, receipt);
res.json({
success: true,
data: {
proposalId: receipt.logs[0].args.proposalId,
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при создании предложения об удалении модуля:', error);
res.status(500).json({
success: false,
error: 'Ошибка при создании предложения об удалении модуля: ' + error.message
});
}
});
// УДАЛЕНО: дублируется в dleModules.js
// УДАЛЯЕМ эту функцию - создание предложений выполняется только через frontend с MetaMask
// router.post('/create-proposal', ...) - УДАЛЕНО
@@ -925,264 +770,15 @@ router.post('/cancel-proposal', async (req, res) => {
}
});
// Проверить подключение к сети
router.post('/check-chain-connection', async (req, res) => {
try {
const { dleAddress, chainId } = req.body;
if (!dleAddress || chainId === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
// УДАЛЕНО: дублируется в dleMultichain.js
console.log(`[Blockchain] Проверка подключения к сети ${chainId} для DLE: ${dleAddress}`);
// УДАЛЕНО: дублируется в dleMultichain.js
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
// УДАЛЕНО: дублируется в dleMultichain.js
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function checkChainConnection(uint256 _chainId) public view returns (bool isAvailable)"
];
// УДАЛЕНО: дублируется в dleMultichain.js
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Проверяем подключение
const isAvailable = await dle.checkChainConnection(chainId);
console.log(`[Blockchain] Подключение к сети ${chainId}: ${isAvailable}`);
res.json({
success: true,
data: {
chainId: chainId,
isAvailable: isAvailable
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при проверке подключения к сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при проверке подключения к сети: ' + error.message
});
}
});
// Синхронизировать во все сети
router.post('/sync-to-all-chains', async (req, res) => {
try {
const { dleAddress, proposalId, userAddress } = req.body;
if (!dleAddress || proposalId === undefined || !userAddress) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[Blockchain] Синхронизация предложения ${proposalId} во все сети для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function syncToAllChains(uint256 _proposalId) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Синхронизируем во все сети
const tx = await dle.syncToAllChains(proposalId);
const receipt = await tx.wait();
console.log(`[Blockchain] Синхронизация выполнена:`, receipt);
res.json({
success: true,
data: {
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при синхронизации во все сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при синхронизации во все сети: ' + error.message
});
}
});
// Получить количество поддерживаемых сетей
router.post('/get-supported-chain-count', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[Blockchain] Получение количества поддерживаемых сетей для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getSupportedChainCount() public view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем количество сетей
const count = await dle.getSupportedChainCount();
console.log(`[Blockchain] Количество поддерживаемых сетей: ${count}`);
res.json({
success: true,
data: {
count: Number(count)
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при получении количества поддерживаемых сетей:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении количества поддерживаемых сетей: ' + error.message
});
}
});
// Получить ID поддерживаемой сети по индексу
router.post('/get-supported-chain-id', async (req, res) => {
try {
const { dleAddress, index } = req.body;
if (!dleAddress || index === undefined) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[Blockchain] Получение ID сети по индексу ${index} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getSupportedChainId(uint256 _index) public view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем ID сети
const chainId = await dle.getSupportedChainId(index);
console.log(`[Blockchain] ID сети по индексу ${index}: ${chainId}`);
res.json({
success: true,
data: {
index: Number(index),
chainId: Number(chainId)
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при получении ID поддерживаемой сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении ID поддерживаемой сети: ' + error.message
});
}
});
// Исполнить предложение по подписям
router.post('/execute-proposal-by-signatures', async (req, res) => {
try {
const { dleAddress, proposalId, signers, signatures, userAddress } = req.body;
if (!dleAddress || proposalId === undefined || !signers || !signatures || !userAddress) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[Blockchain] Исполнение предложения ${proposalId} по подписям в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function executeProposalBySignatures(uint256 _proposalId, address[] calldata signers, bytes[] calldata signatures) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Исполняем предложение по подписям
const tx = await dle.executeProposalBySignatures(proposalId, signers, signatures);
const receipt = await tx.wait();
console.log(`[Blockchain] Предложение исполнено по подписям:`, receipt);
res.json({
success: true,
data: {
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при исполнении предложения по подписям:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении предложения по подписям: ' + error.message
});
}
});
// УДАЛЕНО: дублируется в dleMultichain.js
// Получить параметры управления
router.post('/get-governance-params', async (req, res) => {
@@ -1707,139 +1303,11 @@ router.post('/is-active', async (req, res) => {
}
});
// Проверить активность модуля
router.post('/is-module-active', async (req, res) => {
try {
const { dleAddress, moduleId } = req.body;
if (!dleAddress || !moduleId) {
return res.status(400).json({
success: false,
error: 'Адрес DLE и ID модуля обязательны'
});
}
// УДАЛЕНО: дублируется в dleModules.js
console.log(`[Blockchain] Проверка активности модуля: ${moduleId} для DLE: ${dleAddress}`);
// УДАЛЕНО: дублируется в dleModules.js
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function isModuleActive(bytes32 _moduleId) external view returns (bool)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Проверяем активность модуля
const isActive = await dle.isModuleActive(moduleId);
console.log(`[Blockchain] Активность модуля ${moduleId}: ${isActive}`);
res.json({
success: true,
data: {
isActive: isActive
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при проверке активности модуля:', error);
res.status(500).json({
success: false,
error: 'Ошибка при проверке активности модуля: ' + error.message
});
}
});
// Получить адрес модуля
router.post('/get-module-address', async (req, res) => {
try {
const { dleAddress, moduleId } = req.body;
if (!dleAddress || !moduleId) {
return res.status(400).json({
success: false,
error: 'Адрес DLE и ID модуля обязательны'
});
}
console.log(`[Blockchain] Получение адреса модуля: ${moduleId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getModuleAddress(bytes32 _moduleId) external view returns (address)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем адрес модуля
const moduleAddress = await dle.getModuleAddress(moduleId);
console.log(`[Blockchain] Адрес модуля ${moduleId}: ${moduleAddress}`);
res.json({
success: true,
data: {
moduleAddress: moduleAddress
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при получении адреса модуля:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении адреса модуля: ' + error.message
});
}
});
// Получить все модули (заглушка)
router.post('/get-all-modules', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[Blockchain] Получение всех модулей для DLE: ${dleAddress}`);
// Пока возвращаем заглушку, так как в смарт контракте нет функции для получения всех модулей
// В реальности нужно будет реализовать через события или другие методы
res.json({
success: true,
data: {
modules: []
}
});
} catch (error) {
console.error('[Blockchain] Ошибка при получении всех модулей:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении всех модулей: ' + error.message
});
}
});
// УДАЛЕНО: дублируется в dleModules.js
// Получить аналитику DLE
router.post('/get-dle-analytics', async (req, res) => {

View File

@@ -42,7 +42,7 @@ router.post('/read-dle-info', async (req, res) => {
// ABI для чтения данных DLE
const dleAbi = [
"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 getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))",
"function totalSupply() external view returns (uint256)",
"function balanceOf(address account) external view returns (uint256)",
"function quorumPercentage() external view returns (uint256)",
@@ -163,7 +163,6 @@ router.post('/read-dle-info', async (req, res) => {
location: dleInfo.location,
coordinates: dleInfo.coordinates,
jurisdiction: Number(dleInfo.jurisdiction),
oktmo: Number(dleInfo.oktmo),
okvedCodes: dleInfo.okvedCodes,
kpp: Number(dleInfo.kpp),
creationTimestamp: Number(dleInfo.creationTimestamp),

File diff suppressed because it is too large Load Diff

View File

@@ -40,13 +40,21 @@ router.post('/get-supported-chains', async (req, res) => {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function listSupportedChains() external view returns (uint256[] memory)"
"function getSupportedChainCount() external view returns (uint256)",
"function getSupportedChainId(uint256 _index) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем поддерживаемые сети
const supportedChains = await dle.listSupportedChains();
// Получаем количество поддерживаемых сетей
const chainCount = await dle.getSupportedChainCount();
// Получаем ID каждой сети
const supportedChains = [];
for (let i = 0; i < Number(chainCount); i++) {
const chainId = await dle.getSupportedChainId(i);
supportedChains.push(chainId);
}
console.log(`[DLE Multichain] Поддерживаемые сети:`, supportedChains);

View File

@@ -42,9 +42,11 @@ router.post('/get-proposals', async (req, res) => {
// ABI для чтения предложений (используем правильные функции из смарт-контракта)
const dleAbi = [
"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)",
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function proposals(uint256) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, bytes memory operation, uint256 governanceChainId, uint256 snapshotTimepoint)",
"function quorumPercentage() external view returns (uint256)",
"function getPastTotalSupply(uint256 timepoint) external view returns (uint256)",
"event ProposalCreated(uint256 proposalId, address initiator, string description)"
];
@@ -68,15 +70,34 @@ router.post('/get-proposals', async (req, res) => {
console.log(`[DLE Proposals] Читаем предложение ID: ${proposalId}`);
// Пробуем несколько раз для новых предложений
let proposal, isPassed;
let proposalState, isPassed, quorumReached, forVotes, againstVotes, quorumRequired;
let retryCount = 0;
const maxRetries = 3;
const maxRetries = 1;
while (retryCount < maxRetries) {
try {
proposal = await dle.getProposalSummary(proposalId);
proposalState = await dle.getProposalState(proposalId);
const result = await dle.checkProposalResult(proposalId);
isPassed = result.passed;
quorumReached = result.quorumReached;
// Получаем данные о голосах из структуры Proposal
try {
const proposalData = await dle.proposals(proposalId);
forVotes = Number(proposalData.forVotes);
againstVotes = Number(proposalData.againstVotes);
// Вычисляем требуемый кворум
const quorumPct = Number(await dle.quorumPercentage());
const pastSupply = Number(await dle.getPastTotalSupply(proposalData.snapshotTimepoint));
quorumRequired = Math.floor((pastSupply * quorumPct) / 100);
} catch (voteError) {
console.log(`[DLE Proposals] Ошибка получения голосов для предложения ${proposalId}:`, voteError.message);
forVotes = 0;
againstVotes = 0;
quorumRequired = 0;
}
break; // Успешно прочитали
} catch (error) {
retryCount++;
@@ -90,33 +111,29 @@ router.post('/get-proposals', async (req, res) => {
}
console.log(`[DLE Proposals] Данные предложения ${proposalId}:`, {
id: Number(proposal.id),
description: proposal.description,
forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes),
executed: proposal.executed,
canceled: proposal.canceled,
deadline: Number(proposal.deadline),
initiator: proposal.initiator,
governanceChainId: Number(proposal.governanceChainId),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
targets: proposal.targets
id: Number(proposalId),
description: events[i].args.description,
state: Number(proposalState),
isPassed: isPassed,
quorumReached: quorumReached,
forVotes: Number(forVotes),
againstVotes: Number(againstVotes),
quorumRequired: Number(quorumRequired),
initiator: events[i].args.initiator
});
const proposalInfo = {
id: Number(proposal.id),
description: proposal.description,
forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes),
executed: proposal.executed,
canceled: proposal.canceled,
deadline: Number(proposal.deadline),
initiator: proposal.initiator,
governanceChainId: Number(proposal.governanceChainId),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
targetChains: proposal.targets.map(chainId => Number(chainId)),
id: Number(proposalId),
description: events[i].args.description,
state: Number(proposalState),
isPassed: isPassed,
blockNumber: events[i].blockNumber
quorumReached: quorumReached,
forVotes: Number(forVotes),
againstVotes: Number(againstVotes),
quorumRequired: Number(quorumRequired),
initiator: events[i].args.initiator,
blockNumber: events[i].blockNumber,
transactionHash: events[i].transactionHash
};
proposals.push(proposalInfo);
@@ -182,29 +199,40 @@ router.post('/get-proposal-info', async (req, res) => {
// ABI для чтения информации о предложении
const dleAbi = [
"function proposals(uint256) external view returns (tuple(string description, uint256 duration, bytes operation, uint256 governanceChainId, uint256 startTime, bool executed, uint256 forVotes, uint256 againstVotes))",
"function checkProposalResult(uint256 _proposalId) external view returns (bool)"
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
"event ProposalCreated(uint256 proposalId, address initiator, string description)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Читаем информацию о предложении
const proposal = await dle.proposals(proposalId);
const isPassed = await dle.checkProposalResult(proposalId);
// Ищем событие ProposalCreated для этого предложения
const currentBlock = await provider.getBlockNumber();
const fromBlock = Math.max(0, currentBlock - 10000);
const events = await dle.queryFilter('ProposalCreated', fromBlock, currentBlock);
const proposalEvent = events.find(event => Number(event.args.proposalId) === proposalId);
if (!proposalEvent) {
return res.status(404).json({
success: false,
error: 'Предложение не найдено'
});
}
// Получаем состояние и результат предложения
const result = await dle.checkProposalResult(proposalId);
const state = await dle.getProposalState(proposalId);
// governanceChainId не сохраняется в предложении, используем текущую цепочку
const governanceChainId = 11155111; // Sepolia chain ID
const proposalInfo = {
description: proposal.description,
duration: Number(proposal.duration),
operation: proposal.operation,
governanceChainId: Number(proposal.governanceChainId),
startTime: Number(proposal.startTime),
executed: proposal.executed,
forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes),
isPassed: isPassed
id: Number(proposalId),
description: proposalEvent.args.description,
initiator: proposalEvent.args.initiator,
blockNumber: proposalEvent.blockNumber,
transactionHash: proposalEvent.transactionHash,
state: Number(state),
isPassed: result.passed,
quorumReached: result.quorumReached
};
console.log(`[DLE Proposals] Информация о предложении получена:`, proposalInfo);
@@ -300,24 +328,30 @@ router.post('/get-proposal-votes', async (req, res) => {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getProposalVotes(uint256 _proposalId) external view returns (uint256 forVotes, uint256 againstVotes, uint256 totalVotes, uint256 quorumRequired)"
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем голоса по предложению
const votes = await dle.getProposalVotes(proposalId);
// Получаем результат предложения
const result = await dle.checkProposalResult(proposalId);
const state = await dle.getProposalState(proposalId);
console.log(`[DLE Proposals] Голоса по предложению ${proposalId}:`, votes);
console.log(`[DLE Proposals] Результат предложения ${proposalId}:`, { result, state });
res.json({
success: true,
data: {
proposalId: Number(proposalId),
forVotes: Number(votes.forVotes),
againstVotes: Number(votes.againstVotes),
totalVotes: Number(votes.totalVotes),
quorumRequired: Number(votes.quorumRequired)
isPassed: result.passed,
quorumReached: result.quorumReached,
state: Number(state),
// Пока не можем получить точные голоса, так как функция не существует в контракте
forVotes: 0,
againstVotes: 0,
totalVotes: 0,
quorumRequired: 0
}
});
@@ -539,19 +573,22 @@ router.post('/get-quorum-at', async (req, res) => {
}
});
// Исполнить предложение
// Исполнить предложение (подготовка транзакции для MetaMask)
router.post('/execute-proposal', async (req, res) => {
try {
const { dleAddress, proposalId, userAddress, privateKey } = req.body;
console.log('[DLE Proposals] Получен запрос на исполнение предложения:', req.body);
if (!dleAddress || proposalId === undefined || !userAddress || !privateKey) {
const { dleAddress, proposalId } = req.body;
if (!dleAddress || proposalId === undefined) {
console.log('[DLE Proposals] Ошибка валидации: отсутствуют обязательные поля');
return res.status(400).json({
success: false,
error: 'Все поля обязательны, включая приватный ключ'
error: 'Необходимы dleAddress и proposalId'
});
}
console.log(`[DLE Proposals] Исполнение предложения ${proposalId} в DLE: ${dleAddress}`);
console.log(`[DLE Proposals] Подготовка исполнения предложения ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
@@ -562,32 +599,34 @@ router.post('/execute-proposal', async (req, res) => {
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [
"function executeProposal(uint256 _proposalId) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Исполняем предложение
const tx = await dle.executeProposal(proposalId);
const receipt = await tx.wait();
// Подготавливаем данные для транзакции (не отправляем)
const txData = await dle.executeProposal.populateTransaction(proposalId);
console.log(`[DLE Proposals] Предложение исполнено:`, receipt);
console.log(`[DLE Proposals] Данные транзакции исполнения подготовлены:`, txData);
res.json({
success: true,
data: {
transactionHash: receipt.hash
to: dleAddress,
data: txData.data,
value: "0x0",
gasLimit: "0x1e8480", // 2,000,000 gas
message: `Подготовлены данные для исполнения предложения ${proposalId}. Отправьте транзакцию через MetaMask.`
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при исполнении предложения:', error);
console.error('[DLE Proposals] Ошибка при подготовке исполнения предложения:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении предложения: ' + error.message
error: 'Ошибка при подготовке исполнения предложения: ' + error.message
});
}
});
@@ -795,4 +834,248 @@ router.post('/list-proposals', async (req, res) => {
}
});
// Голосовать за предложение
router.post('/vote-proposal', async (req, res) => {
try {
const { dleAddress, proposalId, support } = req.body;
if (!dleAddress || proposalId === undefined || support === undefined) {
return res.status(400).json({
success: false,
error: 'Необходимы dleAddress, proposalId и support'
});
}
console.log(`[DLE Proposals] Голосование за предложение ${proposalId} в DLE: ${dleAddress}, поддержка: ${support}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function vote(uint256 _proposalId, bool _support) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Подготавливаем данные для транзакции (не отправляем)
const txData = await dle.vote.populateTransaction(proposalId, support);
console.log(`[DLE Proposals] Данные транзакции голосования подготовлены:`, txData);
res.json({
success: true,
data: {
to: dleAddress,
data: txData.data,
value: "0x0",
gasLimit: "0x1e8480", // 2,000,000 gas
message: `Подготовлены данные для голосования ${support ? 'за' : 'против'} предложения ${proposalId}. Отправьте транзакцию через MetaMask.`
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при подготовке голосования:', error);
res.status(500).json({
success: false,
error: 'Ошибка при подготовке голосования: ' + error.message
});
}
});
// Endpoint для отслеживания подтверждения транзакций голосования
router.post('/track-vote-transaction', async (req, res) => {
try {
const { txHash, dleAddress, proposalId, support } = req.body;
if (!txHash || !dleAddress || proposalId === undefined || support === undefined) {
return res.status(400).json({
success: false,
error: 'Необходимы txHash, dleAddress, proposalId и support'
});
}
console.log(`[DLE Proposals] Отслеживание транзакции голосования: ${txHash}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Ждем подтверждения транзакции
const receipt = await provider.waitForTransaction(txHash, 1, 60000); // 60 секунд таймаут
if (receipt && receipt.status === 1) {
console.log(`[DLE Proposals] Транзакция голосования подтверждена: ${txHash}`);
// Отправляем WebSocket уведомление
const wsHub = require('../wsHub');
wsHub.broadcastProposalVoted(dleAddress, proposalId, support, txHash);
res.json({
success: true,
data: {
txHash: txHash,
status: 'confirmed',
receipt: receipt
}
});
} else {
res.json({
success: false,
error: 'Транзакция не подтверждена или провалилась'
});
}
} catch (error) {
console.error('[DLE Proposals] Ошибка при отслеживании транзакции:', error);
res.status(500).json({
success: false,
error: 'Ошибка при отслеживании транзакции: ' + error.message
});
}
});
// Endpoint для отслеживания подтверждения транзакций исполнения
router.post('/track-execution-transaction', async (req, res) => {
try {
const { txHash, dleAddress, proposalId } = req.body;
if (!txHash || !dleAddress || proposalId === undefined) {
return res.status(400).json({
success: false,
error: 'Необходимы txHash, dleAddress и proposalId'
});
}
console.log(`[DLE Proposals] Отслеживание транзакции исполнения: ${txHash}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Ждем подтверждения транзакции
const receipt = await provider.waitForTransaction(txHash, 1, 60000); // 60 секунд таймаут
if (receipt && receipt.status === 1) {
console.log(`[DLE Proposals] Транзакция исполнения подтверждена: ${txHash}`);
// Отправляем WebSocket уведомление
const wsHub = require('../wsHub');
wsHub.broadcastProposalExecuted(dleAddress, proposalId, txHash);
res.json({
success: true,
data: {
txHash: txHash,
status: 'confirmed',
receipt: receipt
}
});
} else {
res.json({
success: false,
error: 'Транзакция не подтверждена или провалилась'
});
}
} catch (error) {
console.error('[DLE Proposals] Ошибка при отслеживании транзакции исполнения:', error);
res.status(500).json({
success: false,
error: 'Ошибка при отслеживании транзакции исполнения: ' + error.message
});
}
});
// Декодировать данные предложения о добавлении модуля
router.post('/decode-proposal-data', async (req, res) => {
try {
const { transactionHash } = req.body;
if (!transactionHash) {
return res.status(400).json({
success: false,
error: 'Хеш транзакции обязателен'
});
}
console.log(`[DLE Proposals] Декодирование данных транзакции: ${transactionHash}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Получаем данные транзакции
const tx = await provider.getTransaction(transactionHash);
if (!tx) {
return res.status(404).json({
success: false,
error: 'Транзакция не найдена'
});
}
// Декодируем данные транзакции
const iface = new ethers.Interface([
"function createAddModuleProposal(string memory _description, uint256 _duration, bytes32 _moduleId, address _moduleAddress, uint256 _chainId) external returns (uint256)"
]);
try {
const decoded = iface.parseTransaction({ data: tx.data });
const proposalData = {
description: decoded.args._description,
duration: Number(decoded.args._duration),
moduleId: decoded.args._moduleId,
moduleAddress: decoded.args._moduleAddress,
chainId: Number(decoded.args._chainId)
};
console.log(`[DLE Proposals] Декодированные данные:`, proposalData);
res.json({
success: true,
data: proposalData
});
} catch (decodeError) {
console.log(`[DLE Proposals] Ошибка декодирования:`, decodeError.message);
res.status(400).json({
success: false,
error: 'Не удалось декодировать данные транзакции: ' + decodeError.message
});
}
} catch (error) {
console.error('[DLE Proposals] Ошибка при декодировании данных предложения:', error);
res.status(500).json({
success: false,
error: 'Ошибка при декодировании данных предложения: ' + error.message
});
}
});
module.exports = router;

View File

@@ -18,10 +18,36 @@ const auth = require('../middleware/auth');
const path = require('path');
const fs = require('fs');
const ethers = require('ethers'); // Added ethers for private key validation
const deploymentTracker = require('../utils/deploymentTracker');
const create2 = require('../utils/create2');
const verificationStore = require('../services/verificationStore');
const etherscanV2 = require('../services/etherscanV2VerificationService');
/**
* Асинхронная функция для выполнения деплоя в фоне
*/
async function executeDeploymentInBackground(deploymentId, dleParams) {
try {
// Отправляем уведомление о начале
deploymentTracker.updateDeployment(deploymentId, {
status: 'in_progress',
stage: 'initializing'
});
deploymentTracker.addLog(deploymentId, '🚀 Начинаем деплой DLE контракта и модулей', 'info');
// Выполняем деплой с передачей deploymentId для WebSocket обновлений
const result = await dleV2Service.createDLE(dleParams, deploymentId);
// Завершаем успешно
deploymentTracker.completeDeployment(deploymentId, result.data);
} catch (error) {
// Завершаем с ошибкой
deploymentTracker.failDeployment(deploymentId, error);
}
}
/**
* @route POST /api/dle-v2
* @desc Создать новое DLE v2 (Digital Legal Entity)
@@ -30,7 +56,7 @@ const etherscanV2 = require('../services/etherscanV2VerificationService');
router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res, next) => {
try {
const dleParams = req.body;
logger.info('Получен запрос на создание DLE v2:', dleParams);
logger.info('🔥 Получен запрос на асинхронный деплой DLE v2');
// Если параметр initialPartners не был передан явно, используем адрес авторизованного пользователя
if (!dleParams.initialPartners || dleParams.initialPartners.length === 0) {
@@ -51,22 +77,26 @@ router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res, next) =>
}
}
// Создаем DLE v2
const result = await dleV2Service.createDLE(dleParams);
// Создаем запись о деплое
const deploymentId = deploymentTracker.createDeployment(dleParams);
logger.info('DLE v2 успешно создано:', result);
// Запускаем деплой в фоне (без await!)
executeDeploymentInBackground(deploymentId, dleParams);
logger.info(`📤 Деплой запущен асинхронно: ${deploymentId}`);
// Сразу возвращаем ответ с ID деплоя
res.json({
success: true,
message: 'DLE v2 успешно создано',
data: result.data
message: 'Деплой запущен в фоновом режиме',
deploymentId: deploymentId
});
} catch (error) {
logger.error('Ошибка при создании DLE v2:', error);
logger.error('Ошибка при запуске асинхронного деплоя:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при создании DLE v2'
message: error.message || 'Произошла ошибка при запуске деплоя'
});
}
});
@@ -94,46 +124,6 @@ router.get('/', async (req, res, next) => {
}
});
/**
* @route POST /api/dle-v2/manual-card
* @desc Ручное сохранение карточки DLE по адресу (если деплой уже был)
* @access Private (admin)
*/
router.post('/manual-card', auth.requireAuth, auth.requireAdmin, async (req, res) => {
try {
const { dleAddress, name, symbol, location, coordinates, jurisdiction, oktmo, okvedCodes, kpp, quorumPercentage, initialPartners, initialAmounts, supportedChainIds, networks } = req.body || {};
if (!dleAddress) {
return res.status(400).json({ success: false, message: 'dleAddress обязателен' });
}
const data = {
name: name || '',
symbol: symbol || '',
location: location || '',
coordinates: coordinates || '',
jurisdiction: jurisdiction ?? 1,
oktmo: oktmo ?? null,
okvedCodes: Array.isArray(okvedCodes) ? okvedCodes : [],
kpp: kpp ?? null,
quorumPercentage: quorumPercentage ?? 51,
initialPartners: Array.isArray(initialPartners) ? initialPartners : [],
initialAmounts: Array.isArray(initialAmounts) ? initialAmounts : [],
governanceSettings: {
quorumPercentage: quorumPercentage ?? 51,
supportedChainIds: Array.isArray(supportedChainIds) ? supportedChainIds : [],
currentChainId: Array.isArray(supportedChainIds) && supportedChainIds.length ? supportedChainIds[0] : 1
},
dleAddress,
version: 'v2',
networks: Array.isArray(networks) ? networks : [],
createdAt: new Date().toISOString()
};
const savedPath = dleV2Service.saveDLEData(data);
return res.json({ success: true, data: { file: savedPath } });
} catch (e) {
logger.error('manual-card error', e);
return res.status(500).json({ success: false, message: e.message });
}
});
/**
* @route GET /api/dle-v2/default-params
@@ -342,35 +332,130 @@ router.post('/validate-private-key', async (req, res, next) => {
}
});
/**
* @route GET /api/dle-v2/deployment-status/:deploymentId
* @desc Получить статус деплоя
* @access Private
*/
router.get('/deployment-status/:deploymentId', auth.requireAuth, auth.requireAdmin, async (req, res) => {
try {
const { deploymentId } = req.params;
const deployment = deploymentTracker.getDeployment(deploymentId);
if (!deployment) {
return res.status(404).json({
success: false,
message: 'Деплой не найден'
});
}
res.json({
success: true,
data: {
id: deployment.id,
status: deployment.status,
stage: deployment.stage,
progress: deployment.progress,
networks: deployment.networks,
startedAt: deployment.startedAt,
updatedAt: deployment.updatedAt,
logs: deployment.logs.slice(-50), // Последние 50 логов
error: deployment.error
}
});
} catch (error) {
logger.error('Ошибка при получении статуса деплоя:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при получении статуса'
});
}
});
/**
* @route GET /api/dle-v2/deployment-result/:deploymentId
* @desc Получить результат завершенного деплоя
* @access Private
*/
router.get('/deployment-result/:deploymentId', auth.requireAuth, auth.requireAdmin, async (req, res) => {
try {
const { deploymentId } = req.params;
const deployment = deploymentTracker.getDeployment(deploymentId);
if (!deployment) {
return res.status(404).json({
success: false,
message: 'Деплой не найден'
});
}
if (deployment.status !== 'completed') {
return res.status(400).json({
success: false,
message: `Деплой не завершен. Текущий статус: ${deployment.status}`,
status: deployment.status
});
}
res.json({
success: true,
data: {
result: deployment.result,
completedAt: deployment.completedAt,
duration: deployment.completedAt ? deployment.completedAt - deployment.startedAt : null
}
});
} catch (error) {
logger.error('Ошибка при получении результата деплоя:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при получении результата'
});
}
});
/**
* @route GET /api/dle-v2/deployment-stats
* @desc Получить статистику деплоев
* @access Private
*/
router.get('/deployment-stats', auth.requireAuth, auth.requireAdmin, async (req, res) => {
try {
const stats = deploymentTracker.getStats();
const activeDeployments = deploymentTracker.getActiveDeployments();
res.json({
success: true,
data: {
stats,
activeDeployments: activeDeployments.map(d => ({
id: d.id,
stage: d.stage,
progress: d.progress,
startedAt: d.startedAt
}))
}
});
} catch (error) {
logger.error('Ошибка при получении статистики деплоев:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при получении статистики'
});
}
});
module.exports = router;
/**
* Дополнительные маршруты (подключаются из app.js)
*/
// Предсказание адресов по выбранным сетям с использованием CREATE2
router.post('/predict-addresses', auth.requireAuth, auth.requireAdmin, async (req, res) => {
try {
const { name, symbol, selectedNetworks } = req.body || {};
if (!selectedNetworks || !Array.isArray(selectedNetworks) || selectedNetworks.length === 0) {
return res.status(400).json({ success: false, message: 'Не переданы сети' });
}
// Используем служебные секреты для фабрики и SALT
// Factory больше не используется - адреса DLE теперь вычисляются через CREATE с выровненным nonce
const result = {};
for (const chainId of selectedNetworks) {
// Адрес DLE будет одинаковым во всех сетях благодаря выравниванию nonce
// Вычисляется в deploy-multichain.js во время деплоя
result[chainId] = 'Вычисляется во время деплоя';
}
return res.json({ success: true, data: result });
} catch (e) {
logger.error('predict-addresses error', e);
return res.status(500).json({ success: false, message: 'Ошибка расчета адресов' });
}
});
// Сохранить GUID верификации (если нужно отдельным вызовом)
router.post('/verify/save-guid', auth.requireAuth, auth.requireAdmin, async (req, res) => {

View File

@@ -1,3 +1,15 @@
/**
* 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
*/
/**
* ENS utilities: resolve avatar URL for a given ENS name
*/

View File

@@ -17,6 +17,32 @@ const logger = require('../utils/logger');
const { ethers } = require('ethers');
const db = require('../db');
const rpcProviderService = require('../services/rpcProviderService');
// Функция для получения информации о сети по chain_id
function getNetworkInfo(chainId) {
const networkInfo = {
1: { name: 'Ethereum Mainnet', description: 'Максимальная безопасность и децентрализация' },
137: { name: 'Polygon', description: 'Низкие комиссии, быстрые транзакции' },
42161: { name: 'Arbitrum One', description: 'Оптимистичные rollups, средние комиссии' },
10: { name: 'Optimism', description: 'Оптимистичные rollups, низкие комиссии' },
56: { name: 'BSC', description: 'Совместимость с экосистемой Binance' },
43114: { name: 'Avalanche', description: 'Высокая пропускная способность' },
11155111: { name: 'Sepolia Testnet', description: 'Тестовая сеть Ethereum' },
80001: { name: 'Mumbai Testnet', description: 'Тестовая сеть Polygon' },
421613: { name: 'Arbitrum Goerli', description: 'Тестовая сеть Arbitrum' },
420: { name: 'Optimism Goerli', description: 'Тестовая сеть Optimism' },
97: { name: 'BSC Testnet', description: 'Тестовая сеть BSC' },
17000: { name: 'Holesky Testnet', description: 'Тестовая сеть Holesky' },
421614: { name: 'Arbitrum Sepolia', description: 'Тестовая сеть Arbitrum Sepolia' },
84532: { name: 'Base Sepolia', description: 'Тестовая сеть Base Sepolia' },
80002: { name: 'Polygon Amoy', description: 'Тестовая сеть Polygon Amoy' }
};
return networkInfo[chainId] || {
name: `Chain ${chainId}`,
description: 'Блокчейн сеть'
};
}
const authTokenService = require('../services/authTokenService');
const aiProviderSettingsService = require('../services/aiProviderSettingsService');
const aiAssistant = require('../services/ai-assistant');
@@ -65,7 +91,15 @@ router.get('/rpc', async (req, res, next) => {
'SELECT id, chain_id, created_at, updated_at, decrypt_text(network_id_encrypted, $1) as network_id, decrypt_text(rpc_url_encrypted, $1) as rpc_url FROM rpc_providers',
[encryptionKey]
);
const rpcConfigs = rpcProvidersResult.rows;
const rpcConfigs = rpcProvidersResult.rows.map(config => {
// Добавляем name и description на основе chain_id
const networkInfo = getNetworkInfo(config.chain_id);
return {
...config,
name: networkInfo.name,
description: networkInfo.description
};
});
if (isAdmin) {
// Для админов возвращаем полные данные

View File

@@ -1,3 +1,15 @@
/**
* 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
*/
/**
* Загрузка файлов (логотипы) через Multer
*/