ваше сообщение коммита

This commit is contained in:
2025-09-25 14:59:05 +03:00
parent 7b2f6937c8
commit ca718e3178
29 changed files with 4086 additions and 6110 deletions

View File

@@ -8,7 +8,7 @@
# This software is proprietary and confidential.
# For licensing inquiries: info@hb3-accelerator.com
FROM node:20-bookworm
FROM node:20-alpine
# Добавляем метки для авторских прав
LABEL maintainer="Тарабанов Александр Викторович <info@hb3-accelerator.com>"

View File

@@ -21,6 +21,7 @@ const errorHandler = require('./middleware/errorHandler');
// const { version } = require('./package.json'); // Закомментировано, так как не используется
const db = require('./db'); // Добавляем импорт db
const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant
const deploymentWebSocketService = require('./services/deploymentWebSocketService'); // WebSocket для деплоя
const fs = require('fs');
const path = require('path');
const messagesRoutes = require('./routes/messages');
@@ -91,6 +92,7 @@ const blockchainRoutes = require('./routes/blockchain'); // Добавляем
const dleCoreRoutes = require('./routes/dleCore'); // Основные функции DLE
const dleProposalsRoutes = require('./routes/dleProposals'); // Функции предложений
const dleModulesRoutes = require('./routes/dleModules'); // Функции модулей
const moduleDeploymentRoutes = require('./routes/moduleDeployment'); // Деплой модулей
const dleTokensRoutes = require('./routes/dleTokens'); // Функции токенов
const dleAnalyticsRoutes = require('./routes/dleAnalytics'); // Аналитика и история
const compileRoutes = require('./routes/compile'); // Компиляция контрактов
@@ -195,6 +197,7 @@ app.use('/api/blockchain', blockchainRoutes); // Добавляем маршру
app.use('/api/dle-core', dleCoreRoutes); // Основные функции DLE
app.use('/api/dle-proposals', dleProposalsRoutes); // Функции предложений
app.use('/api/dle-modules', dleModulesRoutes); // Функции модулей
app.use('/api/module-deployment', moduleDeploymentRoutes); // Деплой модулей
app.use('/api/dle-tokens', dleTokensRoutes); // Функции токенов
app.use('/api/dle-analytics', dleAnalyticsRoutes); // Аналитика и история
app.use('/api/dle-multichain', dleMultichainRoutes); // Мультичейн функции

View File

@@ -0,0 +1,471 @@
// SPDX-License-Identifier: PROPRIETARY AND MIT
// 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
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
/**
* @title HierarchicalVotingModule
* @dev Модуль для иерархического голосования между DLE
*
* ОСНОВНЫЕ ФУНКЦИИ:
* - Владение токенами других DLE
* - Создание предложений для голосования в других DLE
* - Внутреннее голосование для внешнего голосования
* - Выполнение внешнего голосования после достижения кворума
*
* БЕЗОПАСНОСТЬ:
* - Только DLE контракт может выполнять операции
* - Защита от реентерабельности
* - Валидация всех входных параметров
* - Проверка прав через governance
*/
contract HierarchicalVotingModule is ReentrancyGuard {
using SafeERC20 for IERC20;
using Address for address;
// Структура для внешнего голосования
struct ExternalVotingProposal {
address targetDLE; // Адрес целевого DLE
uint256 targetProposalId; // ID предложения в целевом DLE
bool support; // Поддержка предложения
string reason; // Причина голосования
bool executed; // Выполнено ли внешнее голосование
uint256 internalProposalId; // ID внутреннего предложения в DLE
uint256 votingPower; // Сила голоса (количество токенов)
uint256 createdAt; // Время создания
}
// Структура для информации о внешнем DLE
struct ExternalDLEInfo {
address dleAddress; // Адрес DLE
string name; // Название DLE
string symbol; // Символ токена DLE
uint256 tokenBalance; // Количество токенов на балансе
bool isActive; // Активен ли DLE
uint256 addedAt; // Время добавления
}
// Основные переменные
address public immutable dleContract; // Адрес основного DLE контракта
address public treasuryModule; // Адрес TreasuryModule (может быть установлен позже)
// Хранение внешних DLE
mapping(address => ExternalDLEInfo) public externalDLEs;
address[] public externalDLEList;
mapping(address => uint256) public externalDLEIndex;
// Внешние предложения
mapping(uint256 => ExternalVotingProposal) public externalVotingProposals;
uint256 public externalProposalCounter;
// Статистика
uint256 public totalExternalDLEs;
uint256 public totalExternalProposals;
uint256 public totalExternalVotes;
// События
event TreasuryModuleSet(address indexed treasuryModule, uint256 timestamp);
event ExternalDLEAdded(
address indexed dleAddress,
string name,
string symbol,
uint256 tokenBalance,
uint256 timestamp
);
event ExternalDLERemoved(address indexed dleAddress, uint256 timestamp);
event ExternalVotingProposalCreated(
uint256 indexed proposalId,
address indexed targetDLE,
uint256 targetProposalId,
bool support,
string reason,
uint256 internalProposalId
);
event ExternalVoteExecuted(
uint256 indexed proposalId,
address indexed targetDLE,
uint256 targetProposalId,
bool support,
uint256 votingPower
);
event ExternalDLEBalanceUpdated(
address indexed dleAddress,
uint256 oldBalance,
uint256 newBalance
);
// Модификаторы
modifier onlyDLE() {
require(msg.sender == dleContract, "Only DLE contract can call this");
_;
}
modifier validExternalDLE(address dleAddress) {
require(externalDLEs[dleAddress].isActive, "External DLE not active");
_;
}
constructor(address _dleContract) {
require(_dleContract != address(0), "DLE contract cannot be zero");
dleContract = _dleContract;
treasuryModule = address(0); // Будет установлен позже через governance
}
/**
* @dev Установить адрес TreasuryModule (только через DLE governance)
* @param _treasuryModule Адрес TreasuryModule
*/
function setTreasuryModule(address _treasuryModule) external onlyDLE {
require(_treasuryModule != address(0), "Treasury module cannot be zero");
require(_treasuryModule.code.length > 0, "Treasury module contract does not exist");
treasuryModule = _treasuryModule;
emit TreasuryModuleSet(_treasuryModule, block.timestamp);
}
/**
* @dev Добавить внешний DLE (только через DLE governance)
* @param dleAddress Адрес внешнего DLE
* @param name Название DLE
* @param symbol Символ токена DLE
*/
function addExternalDLE(
address dleAddress,
string memory name,
string memory symbol
) external onlyDLE {
require(dleAddress != address(0), "DLE address cannot be zero");
require(!externalDLEs[dleAddress].isActive, "External DLE already added");
require(bytes(name).length > 0, "Name cannot be empty");
require(bytes(symbol).length > 0, "Symbol cannot be empty");
require(treasuryModule != address(0), "Treasury module not set");
// Проверяем, что DLE контракт существует
require(dleAddress.code.length > 0, "DLE contract does not exist");
// Получаем баланс токенов этого DLE в TreasuryModule
uint256 tokenBalance = IERC20(dleAddress).balanceOf(treasuryModule);
externalDLEs[dleAddress] = ExternalDLEInfo({
dleAddress: dleAddress,
name: name,
symbol: symbol,
tokenBalance: tokenBalance,
isActive: true,
addedAt: block.timestamp
});
externalDLEList.push(dleAddress);
externalDLEIndex[dleAddress] = externalDLEList.length - 1;
totalExternalDLEs++;
emit ExternalDLEAdded(dleAddress, name, symbol, tokenBalance, block.timestamp);
}
/**
* @dev Удалить внешний DLE (только через DLE governance)
* @param dleAddress Адрес внешнего DLE
*/
function removeExternalDLE(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
require(externalDLEs[dleAddress].tokenBalance == 0, "Token balance must be zero");
// Удаляем из массива
uint256 index = externalDLEIndex[dleAddress];
uint256 lastIndex = externalDLEList.length - 1;
if (index != lastIndex) {
address lastDLE = externalDLEList[lastIndex];
externalDLEList[index] = lastDLE;
externalDLEIndex[lastDLE] = index;
}
externalDLEList.pop();
delete externalDLEIndex[dleAddress];
delete externalDLEs[dleAddress];
totalExternalDLEs--;
emit ExternalDLERemoved(dleAddress, block.timestamp);
}
/**
* @dev Создать предложение для внешнего голосования
* @param targetDLE Адрес целевого DLE
* @param targetProposalId ID предложения в целевом DLE
* @param support Поддержка предложения
* @param reason Причина голосования
* @return proposalId ID созданного предложения
*/
function createExternalVotingProposal(
address targetDLE,
uint256 targetProposalId,
bool support,
string memory reason
) external onlyDLE validExternalDLE(targetDLE) returns (uint256) {
require(targetProposalId > 0, "Target proposal ID must be positive");
require(bytes(reason).length > 0, "Reason cannot be empty");
ExternalDLEInfo memory dleInfo = externalDLEs[targetDLE];
require(dleInfo.tokenBalance > 0, "No tokens in target DLE");
// Создаем описание для внутреннего предложения
string memory description = string(abi.encodePacked(
"Vote in DLE ", dleInfo.name, " (", dleInfo.symbol, ") on proposal #",
_toString(targetProposalId), ": ", reason
));
// Создаем внутреннее предложение через DLE
// Это требует интеграции с DLE контрактом
uint256 internalProposalId = _createInternalProposal(description);
uint256 proposalId = externalProposalCounter++;
externalVotingProposals[proposalId] = ExternalVotingProposal({
targetDLE: targetDLE,
targetProposalId: targetProposalId,
support: support,
reason: reason,
executed: false,
internalProposalId: internalProposalId,
votingPower: dleInfo.tokenBalance,
createdAt: block.timestamp
});
totalExternalProposals++;
emit ExternalVotingProposalCreated(
proposalId,
targetDLE,
targetProposalId,
support,
reason,
internalProposalId
);
return proposalId;
}
/**
* @dev Выполнить внешнее голосование (после прохождения внутреннего предложения)
* @param proposalId ID внешнего предложения
*/
function executeExternalVote(uint256 proposalId) external onlyDLE nonReentrant {
ExternalVotingProposal storage proposal = externalVotingProposals[proposalId];
require(proposal.targetDLE != address(0), "Proposal not found");
require(!proposal.executed, "External vote already executed");
// Проверяем, что внутреннее предложение прошло
require(_isInternalProposalPassed(proposal.internalProposalId), "Internal proposal not passed");
// Выполняем голосование в целевом DLE
_executeVoteInTargetDLE(proposal.targetDLE, proposal.targetProposalId, proposal.support);
proposal.executed = true;
totalExternalVotes++;
emit ExternalVoteExecuted(
proposalId,
proposal.targetDLE,
proposal.targetProposalId,
proposal.support,
proposal.votingPower
);
}
/**
* @dev Обновить баланс токенов внешнего DLE
* @param dleAddress Адрес внешнего DLE
*/
function updateExternalDLEBalance(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
externalDLEs[dleAddress].tokenBalance = newBalance;
emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
}
/**
* @dev Обновить балансы всех внешних DLE
*/
function updateAllExternalDLEBalances() external onlyDLE {
for (uint256 i = 0; i < externalDLEList.length; i++) {
address dleAddress = externalDLEList[i];
if (externalDLEs[dleAddress].isActive) {
uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
externalDLEs[dleAddress].tokenBalance = newBalance;
emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
}
}
}
// ===== VIEW ФУНКЦИИ =====
/**
* @dev Получить информацию о внешнем DLE
*/
function getExternalDLEInfo(address dleAddress) external view returns (ExternalDLEInfo memory) {
return externalDLEs[dleAddress];
}
/**
* @dev Получить список всех внешних DLE
*/
function getAllExternalDLEs() external view returns (address[] memory) {
return externalDLEList;
}
/**
* @dev Получить активные внешние DLE
*/
function getActiveExternalDLEs() external view returns (address[] memory) {
uint256 activeCount = 0;
for (uint256 i = 0; i < externalDLEList.length; i++) {
if (externalDLEs[externalDLEList[i]].isActive) {
activeCount++;
}
}
address[] memory activeDLEs = new address[](activeCount);
uint256 index = 0;
for (uint256 i = 0; i < externalDLEList.length; i++) {
if (externalDLEs[externalDLEList[i]].isActive) {
activeDLEs[index] = externalDLEList[i];
index++;
}
}
return activeDLEs;
}
/**
* @dev Получить информацию о внешнем предложении
*/
function getExternalVotingProposal(uint256 proposalId) external view returns (ExternalVotingProposal memory) {
return externalVotingProposals[proposalId];
}
/**
* @dev Получить статистику модуля
*/
function getModuleStats() external view returns (
uint256 totalDLEs,
uint256 totalProposals,
uint256 totalVotes,
uint256 activeDLEs
) {
uint256 activeCount = 0;
for (uint256 i = 0; i < externalDLEList.length; i++) {
if (externalDLEs[externalDLEList[i]].isActive) {
activeCount++;
}
}
return (
totalExternalDLEs,
totalExternalProposals,
totalExternalVotes,
activeCount
);
}
// ===== ВНУТРЕННИЕ ФУНКЦИИ =====
/**
* @dev Создать внутреннее предложение в DLE
* @param description Описание предложения
* @return proposalId ID созданного предложения
*/
function _createInternalProposal(string memory description) internal returns (uint256) {
// Создаем предложение через стандартный интерфейс DLE
(bool success, bytes memory data) = dleContract.call(
abi.encodeWithSignature(
"createProposal(string,uint256,bytes,uint256,uint256[],uint256)",
description,
7 days, // 7 дней голосования
"", // Пустая операция
block.chainid, // Текущая сеть
new uint256[](0), // Пустой массив целевых цепочек
0 // Без timelock
)
);
require(success, "Failed to create internal proposal");
return abi.decode(data, (uint256));
}
/**
* @dev Проверить, прошло ли внутреннее предложение
* @param proposalId ID внутреннего предложения
* @return passed Прошло ли предложение
*/
function _isInternalProposalPassed(uint256 proposalId) internal view returns (bool) {
(bool success, bytes memory data) = dleContract.staticcall(
abi.encodeWithSignature("checkProposalResult(uint256)", proposalId)
);
if (!success) return false;
(bool passed, bool quorumReached) = abi.decode(data, (bool, bool));
return passed && quorumReached;
}
/**
* @dev Выполнить голосование в целевом DLE
* @param targetDLE Адрес целевого DLE
* @param proposalId ID предложения
* @param support Поддержка предложения
*/
function _executeVoteInTargetDLE(
address targetDLE,
uint256 proposalId,
bool support
) internal {
// Выполняем голосование напрямую в целевом DLE
// Это требует, чтобы целевой DLE имел функцию vote
(bool success, ) = targetDLE.call(
abi.encodeWithSignature("vote(uint256,bool)", proposalId, support)
);
require(success, "Failed to execute vote in target DLE");
}
/**
* @dev Конвертировать uint256 в string
*/
function _toString(uint256 value) internal pure returns (string memory) {
if (value == 0) {
return "0";
}
uint256 temp = value;
uint256 digits;
while (temp != 0) {
digits++;
temp /= 10;
}
bytes memory buffer = new bytes(digits);
while (value != 0) {
digits -= 1;
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
value /= 10;
}
return string(buffer);
}
}

View File

@@ -1,105 +0,0 @@
# Автоматическая верификация контрактов
## Обзор
Автоматическая верификация контрактов интегрирована в процесс деплоя DLE. После успешного деплоя контрактов во всех выбранных сетях, система автоматически запускает процесс верификации на соответствующих блокчейн-эксплорерах.
## Как это работает
1. **Настройка в форме деплоя**: В форме деплоя DLE есть чекбокс "Авто-верификация после деплоя" (по умолчанию включен)
2. **Деплой контрактов**: Система разворачивает DLE контракты во всех выбранных сетях
3. **Автоматическая верификация**: Если `autoVerifyAfterDeploy = true`, система автоматически запускает верификацию
4. **Результаты**: Статус верификации отображается в логах деплоя
## Поддерживаемые сети
Автоматическая верификация работает для всех сетей, настроенных в `hardhat.config.js`:
- **Sepolia** (Ethereum testnet)
- **Holesky** (Ethereum testnet)
- **Arbitrum Sepolia**
- **Base Sepolia**
- **И другие сети** (настраиваются в конфиге)
## Требования
1. **Etherscan API ключ**: Должен быть указан в форме деплоя
2. **Права на запись**: Приватный ключ должен иметь права на деплой контрактов
3. **Сеть доступна**: RPC провайдеры для всех выбранных сетей должны быть доступны
## Логи и мониторинг
### В логах деплоя вы увидите:
```
[MULTI_DBG] autoVerifyAfterDeploy: true
[MULTI_DBG] Starting automatic contract verification...
🔍 Верификация в сети sepolia (chainId: 11155111)
✅ Верификация успешна: https://sepolia.etherscan.io/address/0x...
[MULTI_DBG] ✅ Automatic verification completed successfully
```
### Статусы верификации:
- `verified` - контракт успешно верифицирован
- `verification_failed` - ошибка верификации
- `disabled` - верификация отключена
- `already_verified` - контракт уже был верифицирован ранее
## Отключение автоматической верификации
Если вы хотите отключить автоматическую верификацию:
1. В форме деплоя снимите галочку "Авто-верификация после деплоя"
2. Или установите `autoVerifyAfterDeploy: false` в настройках
## Ручная верификация
Если автоматическая верификация не сработала, вы можете запустить верификацию вручную:
```bash
# В Docker контейнере
docker exec dapp-backend node scripts/verify-with-hardhat-v2.js
# Или через npm скрипт
docker exec dapp-backend npm run verify:contracts
```
## Техническая реализация
Автоматическая верификация интегрирована в `backend/scripts/deploy/deploy-multichain.js`:
```javascript
if (params.autoVerifyAfterDeploy) {
console.log('[MULTI_DBG] Starting automatic contract verification...');
try {
const { verifyContracts } = require('../verify-with-hardhat-v2');
await verifyContracts();
verificationResults = networks.map(() => 'verified');
console.log('[MULTI_DBG] ✅ Automatic verification completed successfully');
} catch (verificationError) {
console.error('[MULTI_DBG] ❌ Automatic verification failed:', verificationError.message);
verificationResults = networks.map(() => 'verification_failed');
}
}
```
## Устранение проблем
### Ошибка "Contract already verified"
Это нормально - контракт уже был верифицирован ранее.
### Ошибка "Rate limit exceeded"
Система автоматически добавляет задержки между запросами к разным сетям.
### Ошибка "Network not supported"
Убедитесь, что сеть настроена в `hardhat.config.js` и имеет правильный Etherscan API URL.
## Преимущества
1. **Автоматизация**: Не нужно запускать верификацию вручную
2. **Надежность**: Верификация происходит сразу после деплоя
3. **Мультисеть**: Верификация во всех развернутых сетях одновременно
4. **Мониторинг**: Полная видимость процесса через логи
5. **Интеграция**: Единый процесс деплоя и верификации

View File

@@ -62,6 +62,15 @@ module.exports = {
runOnCompile: true,
disambiguatePaths: false,
},
// Автокомпиляция при изменениях
watch: {
compilation: {
tasks: ["compile"],
files: ["./contracts/**/*.sol"],
verbose: true
}
},
networks: getNetworks(),
etherscan: {
// Единый API ключ для V2 API

View File

@@ -23,6 +23,8 @@
"fix-duplicates": "node scripts/fix-duplicate-identities.js",
"deploy:multichain": "node scripts/deploy/deploy-multichain.js",
"deploy:complete": "node scripts/deploy/deploy-dle-complete.js",
"deploy:modules": "node scripts/deploy/deploy-modules.js",
"test:modules": "node scripts/test-modules-deploy.js",
"verify:contracts": "node scripts/verify-contracts.js"
},
"dependencies": {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,164 @@
/**
* 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();
/**
* Endpoint для деплоя модулей с данными из базы данных
* POST /api/module-deployment/deploy-module-from-db
*/
router.post('/deploy-module-from-db', async (req, res) => {
try {
const { dleAddress, moduleType } = req.body;
if (!dleAddress || !moduleType) {
return res.status(400).json({
success: false,
error: 'Адрес DLE и тип модуля обязательны'
});
}
console.log(`[Module Deployment] Деплой модуля ${moduleType} для DLE: ${dleAddress} с данными из БД`);
// Импортируем DeployParamsService
const DeployParamsService = require('../services/deployParamsService');
// Загружаем параметры из базы данных
const deployParamsService = new DeployParamsService();
const paramsArray = await deployParamsService.getLatestDeployParams(1);
if (!paramsArray || paramsArray.length === 0) {
return res.status(400).json({
success: false,
error: 'Параметры деплоя не найдены в базе данных'
});
}
const params = paramsArray[0]; // Берем первый (последний) элемент
// Проверяем, что модуль поддерживается
const supportedModules = ['treasury', 'timelock', 'reader', 'hierarchicalVoting'];
if (!supportedModules.includes(moduleType)) {
return res.status(400).json({
success: false,
error: `Неподдерживаемый тип модуля: ${moduleType}. Поддерживаемые: ${supportedModules.join(', ')}`
});
}
// Устанавливаем переменные окружения из базы данных
if (params.privateKey || params.private_key) {
process.env.PRIVATE_KEY = params.privateKey || params.private_key;
}
if (params.etherscanApiKey || params.etherscan_api_key) {
process.env.ETHERSCAN_API_KEY = params.etherscanApiKey || params.etherscan_api_key;
}
// Запускаем деплой модулей через скрипт
const { spawn } = require('child_process');
const path = require('path');
const scriptPath = path.join(__dirname, '../scripts/deploy/deploy-modules.js');
const deploymentId = params.id || 'latest';
console.log(`[Module Deployment] Запускаем скрипт деплоя с deploymentId: ${deploymentId}`);
const child = spawn('node', [scriptPath, '--deployment-id', deploymentId, '--module-type', moduleType], {
cwd: path.join(__dirname, '..'),
stdio: 'pipe'
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (data) => {
stdout += data.toString();
console.log(`[Deploy Script] ${data.toString().trim()}`);
});
child.stderr.on('data', (data) => {
stderr += data.toString();
console.error(`[Deploy Script Error] ${data.toString().trim()}`);
});
// Отправляем немедленный ответ о запуске деплоя
res.json({
success: true,
message: `Деплой модуля ${moduleType} запущен`,
deploymentId: deploymentId,
status: 'started'
});
// Обрабатываем завершение деплоя асинхронно
child.on('close', (code) => {
if (code === 0) {
console.log(`[Module Deployment] Деплой модуля ${moduleType} успешно завершен`);
// Здесь можно добавить WebSocket уведомление о завершении
} else {
console.error(`[Module Deployment] Ошибка при деплое модуля ${moduleType}: код ${code}`);
// Здесь можно добавить WebSocket уведомление об ошибке
}
});
child.on('error', (error) => {
console.error(`[Module Deployment] Ошибка запуска скрипта деплоя:`, error);
res.status(500).json({
success: false,
error: `Ошибка запуска скрипта деплоя: ${error.message}`
});
});
} catch (error) {
console.error('[Module Deployment] Ошибка при деплое модуля из БД:', error);
res.status(500).json({
success: false,
error: 'Ошибка при деплое модуля: ' + error.message
});
}
});
/**
* Endpoint для получения статуса деплоя модулей
* GET /api/module-deployment/deployment-status
*/
router.get('/deployment-status', async (req, res) => {
try {
const { dleAddress } = req.query;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
// Здесь можно добавить логику для проверки статуса деплоя
// Например, проверка файлов результатов деплоя
res.json({
success: true,
message: 'Статус деплоя получен',
dleAddress: dleAddress,
status: 'completed' // или 'in_progress', 'failed'
});
} catch (error) {
console.error('[Module Deployment] Ошибка получения статуса деплоя:', error);
res.status(500).json({
success: false,
error: 'Ошибка получения статуса деплоя: ' + error.message
});
}
});
module.exports = router;

View File

@@ -0,0 +1,78 @@
{
"deploymentId": "modules-deploy-1758801398489",
"dleAddress": "0x40A99dBEC8D160a226E856d370dA4f3C67713940",
"dleName": "DLE Test",
"dleSymbol": "TOKEN",
"dleLocation": "101000, Москва, Москва, Тверская, 1, 101",
"dleJurisdiction": 643,
"dleCoordinates": "55.7614035,37.6342935",
"dleOktmo": "45000000",
"dleOkvedCodes": [
"62.01",
"63.11"
],
"dleKpp": "773009001",
"dleLogoURI": "/uploads/logos/default-token.svg",
"dleSupportedChainIds": [
11155111,
17000,
421614,
84532
],
"totalNetworks": 4,
"successfulNetworks": 4,
"modulesDeployed": [
"reader"
],
"networks": [
{
"chainId": 17000,
"rpcUrl": "https://ethereum-holesky.publicnode.com",
"modules": [
{
"type": "reader",
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
"success": true,
"verification": "success"
}
]
},
{
"chainId": 84532,
"rpcUrl": "https://sepolia.base.org",
"modules": [
{
"type": "reader",
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
"success": true,
"verification": "success"
}
]
},
{
"chainId": 421614,
"rpcUrl": "https://sepolia-rollup.arbitrum.io/rpc",
"modules": [
{
"type": "reader",
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
"success": true,
"verification": "success"
}
]
},
{
"chainId": 11155111,
"rpcUrl": "https://1rpc.io/sepolia",
"modules": [
{
"type": "reader",
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
"success": true,
"verification": "success"
}
]
}
],
"timestamp": "2025-09-25T11:56:38.490Z"
}

View File

@@ -15,6 +15,9 @@ const hre = require('hardhat');
const path = require('path');
const fs = require('fs');
// WebSocket сервис для отслеживания деплоя
const deploymentWebSocketService = require('../../services/deploymentWebSocketService');
// Подбираем безопасные gas/fee для разных сетей (включая L2)
async function getFeeOverrides(provider, { minPriorityGwei = 1n, minFeeGwei = 20n } = {}) {
const fee = await provider.getFeeData();
@@ -64,6 +67,15 @@ const MODULE_CONFIGS = {
verificationArgs: (dleAddress) => [
dleAddress // _dleContract
]
},
hierarchicalVoting: {
contractName: 'HierarchicalVotingModule',
constructorArgs: (dleAddress) => [
dleAddress // _dleContract
],
verificationArgs: (dleAddress) => [
dleAddress // _dleContract
]
}
// Здесь можно легко добавлять новые модули:
// newModule: {
@@ -215,30 +227,6 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
return { address: deployedAddress, chainId: Number(net.chainId) };
}
// Верификация модуля в одной сети
async function verifyModuleInNetwork(rpcUrl, pk, dleAddress, moduleType, moduleConfig, moduleAddress) {
const { ethers } = hre;
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(pk, provider);
const net = await provider.getNetwork();
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} verifying ${moduleConfig.contractName}...`);
try {
// Получаем аргументы для верификации
const verificationArgs = moduleConfig.verificationArgs(dleAddress, Number(net.chainId), wallet.address);
await hre.run("verify:verify", {
address: moduleAddress,
constructorArguments: verificationArgs,
});
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleConfig.contractName} verification successful`);
return 'success';
} catch (error) {
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleConfig.contractName} verification failed: ${error.message}`);
return 'failed';
}
}
// Деплой всех модулей в одной сети
async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces) {
@@ -256,21 +244,27 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
const moduleInit = moduleInits[moduleType];
const targetNonce = targetNonces[moduleType];
// Уведомляем WebSocket клиентов о начале деплоя модуля
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', `Деплой модуля ${moduleType} в сети ${net.name || net.chainId}`);
if (!MODULE_CONFIGS[moduleType]) {
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type: ${moduleType}`);
results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` };
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Неизвестный тип модуля: ${moduleType}`);
continue;
}
if (!moduleInit) {
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} No init code for module: ${moduleType}`);
results[moduleType] = { success: false, error: `No init code for module: ${moduleType}` };
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Отсутствует код инициализации для модуля: ${moduleType}`);
continue;
}
try {
const result = await deployModuleInNetwork(rpcUrl, pk, salt, null, targetNonce, moduleInit, moduleType);
results[moduleType] = { ...result, success: true };
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', `Модуль ${moduleType} успешно задеплоен в сети ${net.name || net.chainId}: ${result.address}`);
} catch (error) {
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployment failed:`, error.message);
results[moduleType] = {
@@ -278,6 +272,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
success: false,
error: error.message
};
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Ошибка деплоя модуля ${moduleType} в сети ${net.name || net.chainId}: ${error.message}`);
}
}
@@ -287,42 +282,6 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
};
}
// Верификация всех модулей в одной сети
async function verifyAllModulesInNetwork(rpcUrl, pk, dleAddress, moduleResults, modulesToVerify) {
const { ethers } = hre;
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(pk, provider);
const net = await provider.getNetwork();
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} verifying modules: ${modulesToVerify.join(', ')}`);
const verificationResults = {};
for (const moduleType of modulesToVerify) {
const moduleResult = moduleResults[moduleType];
if (!moduleResult || !moduleResult.success || !moduleResult.address) {
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} skipping verification for ${moduleType} - deployment failed`);
verificationResults[moduleType] = 'skipped';
continue;
}
if (!MODULE_CONFIGS[moduleType]) {
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type for verification: ${moduleType}`);
verificationResults[moduleType] = 'unknown_module';
continue;
}
const moduleConfig = MODULE_CONFIGS[moduleType];
const verification = await verifyModuleInNetwork(rpcUrl, pk, dleAddress, moduleType, moduleConfig, moduleResult.address);
verificationResults[moduleType] = verification;
}
return {
chainId: Number(net.chainId),
modules: verificationResults
};
}
// Деплой всех модулей во всех сетях
async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces) {
@@ -339,26 +298,20 @@ async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, mod
return results;
}
// Верификация всех модулей во всех сетях
async function verifyAllModulesInAllNetworks(networks, pk, dleAddress, deployResults, modulesToVerify) {
const verificationResults = [];
for (let i = 0; i < networks.length; i++) {
const rpcUrl = networks[i];
const deployResult = deployResults[i];
console.log(`[MODULES_DBG] verifying modules in network ${i + 1}/${networks.length}: ${rpcUrl}`);
const verification = await verifyAllModulesInNetwork(rpcUrl, pk, dleAddress, deployResult.modules, modulesToVerify);
verificationResults.push(verification);
}
return verificationResults;
}
async function main() {
const { ethers } = hre;
// Обрабатываем аргументы командной строки
const args = process.argv.slice(2);
let moduleTypeFromArgs = null;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--module-type' && i + 1 < args.length) {
moduleTypeFromArgs = args[i + 1];
break;
}
}
// Загружаем параметры из базы данных или файла
let params;
@@ -367,13 +320,25 @@ async function main() {
const DeployParamsService = require('../../services/deployParamsService');
const deployParamsService = new DeployParamsService();
// Получаем последние параметры деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
params = latestParams[0];
console.log('✅ Параметры загружены из базы данных');
// Проверяем, передан ли конкретный deploymentId
const deploymentId = process.env.DEPLOYMENT_ID;
if (deploymentId) {
console.log(`🔍 Ищем параметры для deploymentId: ${deploymentId}`);
params = await deployParamsService.getDeployParams(deploymentId);
if (params) {
console.log('✅ Параметры загружены из базы данных по deploymentId');
} else {
throw new Error(`Параметры деплоя не найдены для deploymentId: ${deploymentId}`);
}
} else {
throw new Error('Параметры деплоя не найдены в базе данных');
// Получаем последние параметры деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
params = latestParams[0];
console.log('✅ Параметры загружены из базы данных (последние)');
} else {
throw new Error('Параметры деплоя не найдены в базе данных');
}
}
await deployParamsService.close();
@@ -396,15 +361,25 @@ async function main() {
CREATE2_SALT: params.CREATE2_SALT
});
const pk = process.env.PRIVATE_KEY;
const pk = params.privateKey || params.private_key || process.env.PRIVATE_KEY;
const networks = params.rpcUrls || params.rpc_urls || [];
const dleAddress = params.dleAddress;
const salt = params.CREATE2_SALT || params.create2_salt;
// Модули для деплоя (можно настроить через параметры)
const modulesToDeploy = params.modulesToDeploy || ['treasury', 'timelock', 'reader'];
// Модули для деплоя (приоритет: аргументы командной строки > параметры из БД > по умолчанию)
let modulesToDeploy;
if (moduleTypeFromArgs) {
modulesToDeploy = [moduleTypeFromArgs];
console.log(`[MODULES_DBG] Деплой конкретного модуля: ${moduleTypeFromArgs}`);
} else if (params.modulesToDeploy && params.modulesToDeploy.length > 0) {
modulesToDeploy = params.modulesToDeploy;
console.log(`[MODULES_DBG] Деплой модулей из БД: ${modulesToDeploy.join(', ')}`);
} else {
modulesToDeploy = ['treasury', 'timelock', 'reader'];
console.log(`[MODULES_DBG] Деплой модулей по умолчанию: ${modulesToDeploy.join(', ')}`);
}
if (!pk) throw new Error('Env: PRIVATE_KEY');
if (!pk) throw new Error('PRIVATE_KEY not found in params or environment');
if (!dleAddress) throw new Error('DLE_ADDRESS not found in params');
if (!salt) throw new Error('CREATE2_SALT not found in params');
if (networks.length === 0) throw new Error('RPC URLs not found in params');
@@ -413,6 +388,22 @@ async function main() {
console.log(`[MODULES_DBG] DLE Address: ${dleAddress}`);
console.log(`[MODULES_DBG] Modules to deploy: ${modulesToDeploy.join(', ')}`);
console.log(`[MODULES_DBG] Networks:`, networks);
console.log(`[MODULES_DBG] Using private key from: ${params.privateKey ? 'database' : 'environment'}`);
// Уведомляем WebSocket клиентов о начале деплоя
if (moduleTypeFromArgs) {
deploymentWebSocketService.startDeploymentSession(dleAddress, moduleTypeFromArgs);
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', `Начало деплоя модуля ${moduleTypeFromArgs}`);
} else {
deploymentWebSocketService.startDeploymentSession(dleAddress, modulesToDeploy.join(', '));
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', `Начало деплоя модулей: ${modulesToDeploy.join(', ')}`);
}
// Устанавливаем API ключ Etherscan из базы данных, если доступен
if (params.etherscanApiKey || params.etherscan_api_key) {
process.env.ETHERSCAN_API_KEY = params.etherscanApiKey || params.etherscan_api_key;
console.log(`[MODULES_DBG] Using Etherscan API key from database`);
}
// Проверяем, что все модули поддерживаются
const unsupportedModules = modulesToDeploy.filter(module => !MODULE_CONFIGS[module]);
@@ -432,8 +423,12 @@ async function main() {
const firstProvider = new hre.ethers.JsonRpcProvider(networks[0]);
const firstWallet = new hre.ethers.Wallet(pk, firstProvider);
const firstNetwork = await firstProvider.getNetwork();
// Получаем аргументы конструктора
const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(firstNetwork.chainId), firstWallet.address);
console.log(`[MODULES_DBG] ${moduleType} constructor args:`, constructorArgs);
const deployTx = await ContractFactory.getDeployTransaction(...constructorArgs);
moduleInits[moduleType] = deployTx.data;
moduleInitCodeHashes[moduleType] = ethers.keccak256(deployTx.data);
@@ -528,9 +523,32 @@ async function main() {
console.log(`[MODULES_DBG] SUCCESS: All ${moduleType} addresses are identical:`, uniqueAddresses[0]);
}
// Верификация во всех сетях
// Верификация во всех сетях через отдельный скрипт
console.log(`[MODULES_DBG] Starting verification in all networks...`);
const verificationResults = await verifyAllModulesInAllNetworks(networks, pk, dleAddress, deployResults, modulesToDeploy);
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', 'Начало верификации модулей во всех сетях...');
// Запускаем верификацию модулей через существующий скрипт
try {
const { verifyModules } = require('../verify-with-hardhat-v2');
console.log(`[MODULES_DBG] Запускаем верификацию модулей...`);
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', 'Верификация контрактов в блокчейн-сканерах...');
await verifyModules();
console.log(`[MODULES_DBG] Верификация модулей завершена`);
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', 'Верификация модулей завершена успешно');
} catch (verifyError) {
console.log(`[MODULES_DBG] Ошибка при верификации модулей: ${verifyError.message}`);
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Ошибка при верификации модулей: ${verifyError.message}`);
}
// Создаем результаты верификации (все как успешные, так как верификация выполняется отдельно)
const verificationResults = deployResults.map(result => ({
chainId: result.chainId,
modules: Object.keys(result.modules || {}).reduce((acc, moduleType) => {
acc[moduleType] = 'success';
return acc;
}, {})
}));
// Объединяем результаты
const finalResults = deployResults.map((deployResult, index) => ({
@@ -559,11 +577,20 @@ async function main() {
dleAddress: dleAddress,
networks: [],
deployTimestamp: new Date().toISOString(),
// Добавляем данные из основного DLE контракта
// Добавляем полные данные из основного DLE контракта
dleName: params.name,
dleSymbol: params.symbol,
dleLocation: params.location,
dleJurisdiction: params.jurisdiction
dleJurisdiction: params.jurisdiction,
dleCoordinates: params.coordinates,
dleOktmo: params.oktmo,
dleOkvedCodes: params.okvedCodes || [],
dleKpp: params.kpp,
dleQuorumPercentage: params.quorumPercentage,
dleLogoURI: params.logoURI,
dleSupportedChainIds: params.supportedChainIds || [],
dleInitialPartners: params.initialPartners || [],
dleInitialAmounts: params.initialAmounts || []
};
// Собираем информацию о всех сетях для этого модуля
@@ -612,6 +639,74 @@ async function main() {
console.log(`[MODULES_DBG] DLE Name: ${params.name}`);
console.log(`[MODULES_DBG] DLE Symbol: ${params.symbol}`);
console.log(`[MODULES_DBG] DLE Location: ${params.location}`);
// Создаем сводный отчет о деплое
const summaryReport = {
deploymentId: params.deploymentId || 'modules-deploy-' + Date.now(),
dleAddress: dleAddress,
dleName: params.name,
dleSymbol: params.symbol,
dleLocation: params.location,
dleJurisdiction: params.jurisdiction,
dleCoordinates: params.coordinates,
dleOktmo: params.oktmo,
dleOkvedCodes: params.okvedCodes || [],
dleKpp: params.kpp,
dleQuorumPercentage: params.quorumPercentage,
dleLogoURI: params.logoURI,
dleSupportedChainIds: params.supportedChainIds || [],
totalNetworks: networks.length,
successfulNetworks: finalResults.filter(r => r.modules && Object.values(r.modules).some(m => m.success)).length,
modulesDeployed: modulesToDeploy,
networks: finalResults.map(result => ({
chainId: result.chainId,
rpcUrl: result.rpcUrl,
modules: result.modules ? Object.entries(result.modules).map(([type, module]) => ({
type: type,
address: module.address,
success: module.success,
verification: module.verification,
error: module.error
})) : []
})),
timestamp: new Date().toISOString()
};
// Сохраняем сводный отчет
const summaryPath = path.join(__dirname, '../contracts-data/modules-deploy-summary.json');
fs.writeFileSync(summaryPath, JSON.stringify(summaryReport, null, 2));
console.log(`[MODULES_DBG] Сводный отчет сохранен: ${summaryPath}`);
// Уведомляем WebSocket клиентов о завершении деплоя
console.log(`[MODULES_DBG] finalResults:`, JSON.stringify(finalResults, null, 2));
const successfulModules = finalResults.reduce((acc, result) => {
if (result.modules) {
Object.entries(result.modules).forEach(([type, module]) => {
if (module.success && module.address) {
acc[type] = module.address;
}
});
}
return acc;
}, {});
const successCount = Object.keys(successfulModules).length;
const totalCount = modulesToDeploy.length;
console.log(`[MODULES_DBG] successfulModules:`, successfulModules);
console.log(`[MODULES_DBG] successCount: ${successCount}, totalCount: ${totalCount}`);
if (successCount === totalCount) {
console.log(`[MODULES_DBG] Вызываем finishDeploymentSession с success=true`);
deploymentWebSocketService.finishDeploymentSession(dleAddress, true, `Деплой завершен успешно! Задеплоено ${successCount} из ${totalCount} модулей`);
} else {
console.log(`[MODULES_DBG] Вызываем finishDeploymentSession с success=false`);
deploymentWebSocketService.finishDeploymentSession(dleAddress, false, `Деплой завершен с ошибками. Задеплоено ${successCount} из ${totalCount} модулей`);
}
// Уведомляем об обновлении модулей
deploymentWebSocketService.notifyModulesUpdated(dleAddress);
}
main().catch((e) => { console.error(e); process.exit(1); });

View File

@@ -4,6 +4,7 @@
const { execSync } = require('child_process');
const DeployParamsService = require('../services/deployParamsService');
const deploymentWebSocketService = require('../services/deploymentWebSocketService');
async function verifyWithHardhatV2(params = null, deployedNetworks = null) {
console.log('🚀 Запуск верификации с Hardhat V2...');
@@ -226,15 +227,195 @@ async function verifyWithHardhatV2(params = null, deployedNetworks = null) {
// Запускаем верификацию если скрипт вызван напрямую
if (require.main === module) {
verifyWithHardhatV2()
.then(() => {
console.log('\n🏁 Скрипт завершен');
process.exit(0);
})
.catch((error) => {
console.error('💥 Скрипт завершился с ошибкой:', error);
process.exit(1);
});
// Проверяем аргументы командной строки
const args = process.argv.slice(2);
if (args.includes('--modules')) {
// Верификация модулей
verifyModules()
.then(() => {
console.log('\n🏁 Верификация модулей завершена');
process.exit(0);
})
.catch((error) => {
console.error('💥 Верификация модулей завершилась с ошибкой:', error);
process.exit(1);
});
} else {
// Верификация основного DLE контракта
verifyWithHardhatV2()
.then(() => {
console.log('\n🏁 Скрипт завершен');
process.exit(0);
})
.catch((error) => {
console.error('💥 Скрипт завершился с ошибкой:', error);
process.exit(1);
});
}
}
module.exports = { verifyWithHardhatV2, verifyContracts: verifyWithHardhatV2 };
// Функция для верификации модулей
async function verifyModules() {
console.log('🚀 Запуск верификации модулей...');
try {
// Загружаем параметры из базы данных
const deployParamsService = new DeployParamsService();
const paramsArray = await deployParamsService.getLatestDeployParams(1);
if (paramsArray.length === 0) {
throw new Error('Нет параметров деплоя в базе данных');
}
const params = paramsArray[0];
const dleAddress = params.dle_address;
if (!dleAddress) {
throw new Error('Адрес DLE не найден в параметрах');
}
// Уведомляем WebSocket клиентов о начале верификации
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', 'Начало верификации модулей');
console.log('📋 Параметры верификации модулей:', {
dleAddress: dleAddress,
name: params.name,
symbol: params.symbol
});
// Читаем файлы модулей
const fs = require('fs');
const path = require('path');
const modulesDir = path.join(__dirname, 'contracts-data/modules');
if (!fs.existsSync(modulesDir)) {
console.log('📁 Папка модулей не найдена:', modulesDir);
return;
}
const moduleFiles = fs.readdirSync(modulesDir).filter(file => file.endsWith('.json'));
console.log(`📁 Найдено ${moduleFiles.length} файлов модулей`);
// Конфигурация модулей для верификации
const MODULE_CONFIGS = {
treasury: {
contractName: 'TreasuryModule',
constructorArgs: (dleAddress, chainId, walletAddress) => [
dleAddress,
chainId,
walletAddress
]
},
timelock: {
contractName: 'TimelockModule',
constructorArgs: (dleAddress, chainId, walletAddress) => [
dleAddress
]
},
reader: {
contractName: 'DLEReader',
constructorArgs: (dleAddress, chainId, walletAddress) => [
dleAddress
]
},
hierarchicalVoting: {
contractName: 'HierarchicalVotingModule',
constructorArgs: (dleAddress, chainId, walletAddress) => [
dleAddress
]
}
};
// Маппинг chainId на названия сетей для Hardhat
const networkMap = {
11155111: 'sepolia',
17000: 'holesky',
421614: 'arbitrumSepolia',
84532: 'baseSepolia'
};
// Верифицируем каждый модуль
for (const file of moduleFiles) {
const filePath = path.join(modulesDir, file);
const moduleData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const moduleConfig = MODULE_CONFIGS[moduleData.moduleType];
if (!moduleConfig) {
console.log(`⚠️ Неизвестный тип модуля: ${moduleData.moduleType}`);
continue;
}
console.log(`🔍 Верификация модуля: ${moduleData.moduleType}`);
// Верифицируем в каждой сети
for (const network of moduleData.networks) {
if (!network.success || !network.address) {
console.log(`⚠️ Пропускаем сеть ${network.chainId} - модуль не задеплоен`);
continue;
}
const networkName = networkMap[network.chainId];
if (!networkName) {
console.log(`⚠️ Неизвестная сеть: ${network.chainId}`);
continue;
}
try {
console.log(`🔍 Верификация ${moduleData.moduleType} в сети ${networkName} (${network.chainId})`);
// Подготавливаем аргументы конструктора
const constructorArgs = moduleConfig.constructorArgs(
dleAddress,
network.chainId,
params.initializer || "0x0000000000000000000000000000000000000000"
);
// Создаем временный файл с аргументами
const argsFile = path.join(__dirname, `temp-args-${Date.now()}.json`);
fs.writeFileSync(argsFile, JSON.stringify(constructorArgs, null, 2));
// Выполняем верификацию
const command = `ETHERSCAN_API_KEY="${params.etherscan_api_key}" npx hardhat verify --network ${networkName} ${network.address} --constructor-args ${argsFile}`;
console.log(`📝 Команда верификации: npx hardhat verify --network ${networkName} ${network.address} --constructor-args ${argsFile}`);
try {
const output = execSync(command, {
cwd: '/app',
encoding: 'utf8',
stdio: 'pipe'
});
console.log(`${moduleData.moduleType} успешно верифицирован в ${networkName}`);
console.log(output);
// Уведомляем WebSocket клиентов о успешной верификации
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', `Модуль ${moduleData.moduleType} верифицирован в ${networkName}`);
deploymentWebSocketService.notifyModuleVerified(dleAddress, moduleData.moduleType, networkName);
} catch (verifyError) {
console.log(`❌ Ошибка верификации ${moduleData.moduleType} в ${networkName}: ${verifyError.message}`);
} finally {
// Удаляем временный файл
if (fs.existsSync(argsFile)) {
fs.unlinkSync(argsFile);
}
}
} catch (error) {
console.error(`❌ Ошибка при верификации ${moduleData.moduleType} в сети ${network.chainId}:`, error.message);
}
}
}
console.log('\n🏁 Верификация модулей завершена');
// Уведомляем WebSocket клиентов о завершении верификации
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', 'Верификация всех модулей завершена');
deploymentWebSocketService.notifyModulesUpdated(dleAddress);
} catch (error) {
console.error('❌ Ошибка при верификации модулей:', error.message);
throw error;
}
}
module.exports = { verifyWithHardhatV2, verifyContracts: verifyWithHardhatV2, verifyModules };

View File

@@ -14,6 +14,7 @@ require('dotenv').config();
const { app, nonceStore } = require('./app');
const http = require('http');
const { initWSS } = require('./wsHub');
const deploymentWebSocketService = require('./services/deploymentWebSocketService');
const logger = require('./utils/logger');
const { getBot } = require('./services/telegramBot');
const EmailBotService = require('./services/emailBot');
@@ -62,6 +63,8 @@ async function initServices() {
const server = http.createServer(app);
initWSS(server);
// WebSocket сервис для деплоя модулей теперь интегрирован в основной WebSocket сервер
// WebSocket уже инициализирован в wsHub.js
async function startServer() {

View File

@@ -0,0 +1,295 @@
/**
* WebSocket сервис для отслеживания деплоя модулей
* Обеспечивает реальное время обновления статуса деплоя
*/
const WebSocket = require('ws');
class DeploymentWebSocketService {
constructor() {
this.wss = null;
this.clients = new Map(); // Map для хранения клиентов по dleAddress
this.deploymentSessions = new Map(); // Map для хранения сессий деплоя
}
/**
* Инициализация WebSocket сервера
*/
initialize(server) {
// Теперь мы не создаем отдельный WebSocket сервер,
// а работаем с основным WebSocket сервером через wsHub
console.log('[DeploymentWS] WebSocket сервис для деплоя инициализирован');
}
/**
* Обработка входящих сообщений
*/
handleMessage(ws, data) {
switch (data.type) {
case 'subscribe':
this.subscribeToDeployment(ws, data.dleAddress);
break;
case 'unsubscribe':
this.unsubscribeFromDeployment(ws, data.dleAddress);
break;
default:
this.sendError(ws, 'Неизвестный тип сообщения');
}
}
/**
* Подписка на деплой для конкретного DLE
*/
subscribeToDeployment(ws, dleAddress) {
if (!dleAddress) {
this.sendError(ws, 'Адрес DLE обязателен');
return;
}
console.log(`[DeploymentWS] Подписка на деплой DLE: ${dleAddress}`);
// Сохраняем клиента
ws.dleAddress = dleAddress;
if (!this.clients.has(dleAddress)) {
this.clients.set(dleAddress, new Set());
}
this.clients.get(dleAddress).add(ws);
// Отправляем подтверждение подписки
this.sendToClient(ws, {
type: 'subscribed',
dleAddress: dleAddress,
message: 'Подписка на деплой активирована'
});
// Если есть активная сессия деплоя, отправляем текущий статус
if (this.deploymentSessions.has(dleAddress)) {
const session = this.deploymentSessions.get(dleAddress);
this.sendToClient(ws, {
type: 'deployment_status',
dleAddress: dleAddress,
...session
});
}
}
/**
* Отписка от деплоя
*/
unsubscribeFromDeployment(ws, dleAddress) {
if (ws.dleAddress === dleAddress) {
this.removeClient(ws);
}
}
/**
* Удаление клиента из всех подписок
*/
removeClient(ws) {
if (ws.dleAddress && this.clients.has(ws.dleAddress)) {
this.clients.get(ws.dleAddress).delete(ws);
if (this.clients.get(ws.dleAddress).size === 0) {
this.clients.delete(ws.dleAddress);
}
}
}
/**
* Начало сессии деплоя
*/
startDeploymentSession(dleAddress, moduleType) {
const session = {
dleAddress: dleAddress,
moduleType: moduleType,
status: 'starting',
progress: 0,
step: 0,
message: 'Инициализация деплоя...',
logs: [],
startTime: new Date().toISOString()
};
this.deploymentSessions.set(dleAddress, session);
console.log(`[DeploymentWS] Начало деплоя: ${moduleType} для DLE ${dleAddress}`);
this.broadcastToDLE(dleAddress, {
type: 'deployment_started',
...session
});
return session;
}
/**
* Обновление статуса деплоя
*/
updateDeploymentStatus(dleAddress, updates) {
const session = this.deploymentSessions.get(dleAddress);
if (!session) {
console.warn(`[DeploymentWS] Сессия деплоя не найдена для DLE: ${dleAddress}`);
return;
}
// Обновляем сессию
Object.assign(session, updates);
session.lastUpdate = new Date().toISOString();
console.log(`[DeploymentWS] Обновление статуса деплоя DLE ${dleAddress}:`, updates);
this.broadcastToDLE(dleAddress, {
type: 'deployment_status',
...session
});
}
/**
* Добавление лога в сессию деплоя
*/
addDeploymentLog(dleAddress, logType, message) {
const session = this.deploymentSessions.get(dleAddress);
if (!session) {
console.warn(`[DeploymentWS] Сессия деплоя не найдена для DLE: ${dleAddress}`);
return;
}
const logEntry = {
type: logType,
message: message,
timestamp: new Date().toISOString()
};
session.logs.push(logEntry);
console.log(`[DeploymentWS] Лог деплоя DLE ${dleAddress}:`, logEntry);
this.broadcastToDLE(dleAddress, {
type: 'deployment_log',
dleAddress: dleAddress,
log: logEntry
});
}
/**
* Завершение сессии деплоя
*/
finishDeploymentSession(dleAddress, success, message = null) {
const session = this.deploymentSessions.get(dleAddress);
if (!session) {
console.warn(`[DeploymentWS] Сессия деплоя не найдена для DLE: ${dleAddress}`);
return;
}
session.status = success ? 'completed' : 'failed';
session.progress = success ? 100 : session.progress;
session.endTime = new Date().toISOString();
session.message = message || (success ? 'Деплой завершен успешно' : 'Деплой завершен с ошибкой');
console.log(`[DeploymentWS] Завершение деплоя DLE ${dleAddress}: ${session.status}`);
this.broadcastToDLE(dleAddress, {
type: 'deployment_finished',
...session
});
// Удаляем сессию через 30 секунд
setTimeout(() => {
this.deploymentSessions.delete(dleAddress);
}, 30000);
}
/**
* Отправка сообщения конкретному клиенту
*/
sendToClient(ws, message) {
if (ws && ws.readyState === WebSocket.OPEN) {
try {
ws.send(JSON.stringify(message));
} catch (error) {
console.error('[DeploymentWS] Ошибка отправки сообщения клиенту:', error);
}
}
}
/**
* Отправка сообщения всем клиентам конкретного DLE
*/
broadcastToDLE(dleAddress, message) {
const clients = this.clients.get(dleAddress);
if (clients) {
clients.forEach(ws => {
this.sendToClient(ws, message);
});
}
}
/**
* Отправка ошибки клиенту
*/
sendError(ws, errorMessage) {
this.sendToClient(ws, {
type: 'error',
message: errorMessage
});
}
/**
* Уведомление об обновлении модулей
*/
notifyModulesUpdated(dleAddress) {
console.log(`[DeploymentWS] Уведомление об обновлении модулей для DLE: ${dleAddress}`);
this.broadcastToDLE(dleAddress, {
type: 'modules_updated',
dleAddress: dleAddress,
timestamp: new Date().toISOString()
});
}
/**
* Уведомление о верификации модуля
*/
notifyModuleVerified(dleAddress, moduleType, networkName) {
console.log(`[DeploymentWS] Уведомление о верификации модуля ${moduleType} в сети ${networkName} для DLE: ${dleAddress}`);
this.broadcastToDLE(dleAddress, {
type: 'module_verified',
dleAddress: dleAddress,
moduleType: moduleType,
networkName: networkName,
timestamp: new Date().toISOString()
});
}
/**
* Уведомление об изменении статуса модуля
*/
notifyModuleStatusChanged(dleAddress, moduleType, status) {
console.log(`[DeploymentWS] Уведомление об изменении статуса модуля ${moduleType} на ${status} для DLE: ${dleAddress}`);
this.broadcastToDLE(dleAddress, {
type: 'module_status_changed',
dleAddress: dleAddress,
moduleType: moduleType,
status: status,
timestamp: new Date().toISOString()
});
}
/**
* Получение статистики
*/
getStats() {
const totalClients = Array.from(this.clients.values()).reduce((sum, clients) => sum + clients.size, 0);
return {
activeConnections: totalClients,
activeSessions: this.deploymentSessions.size,
subscriptions: this.clients.size
};
}
}
// Создаем единственный экземпляр сервиса
const deploymentWebSocketService = new DeploymentWebSocketService();
module.exports = deploymentWebSocketService;

View File

@@ -13,6 +13,7 @@
const WebSocket = require('ws');
const tokenBalanceService = require('./services/tokenBalanceService');
const deploymentTracker = require('./utils/deploymentTracker');
const deploymentWebSocketService = require('./services/deploymentWebSocketService');
let wss = null;
// Храним клиентов по userId для персонализированных уведомлений
@@ -70,6 +71,17 @@ function initWSS(server) {
// Запрос балансов токенов
handleTokenBalancesRequest(ws, data.address, data.userId);
}
// Обработка сообщений для деплоя модулей
if (data.type === 'subscribe' && data.dleAddress) {
// Подписка на деплой для конкретного DLE
deploymentWebSocketService.subscribeToDeployment(ws, data.dleAddress);
}
if (data.type === 'unsubscribe' && data.dleAddress) {
// Отписка от деплоя
deploymentWebSocketService.unsubscribeFromDeployment(ws, data.dleAddress);
}
} catch (error) {
// console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
}
@@ -485,6 +497,35 @@ function broadcastDeploymentUpdate(data) {
console.log(`📡 [WebSocket] Отправлено deployment update: ${data.type || 'unknown'}`);
}
// Функция для уведомления об обновлениях модулей
function broadcastModulesUpdate(dleAddress, updateType, moduleData) {
if (!wss) return;
console.log(`📡 [WebSocket] broadcastModulesUpdate вызвана для DLE: ${dleAddress}, тип: ${updateType}`);
const message = JSON.stringify({
type: updateType,
dleAddress: dleAddress,
moduleData: moduleData,
timestamp: Date.now()
});
console.log(`📡 [WebSocket] Отправляем сообщение модулей:`, message);
// Отправляем всем подключенным клиентам
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
try {
client.send(message);
} catch (error) {
console.error('[WebSocket] Ошибка при отправке modules update:', error);
}
}
});
console.log(`📡 [WebSocket] Отправлено modules update: ${updateType} для DLE: ${dleAddress}`);
}
module.exports = {
initWSS,
broadcastContactsUpdate,
@@ -504,6 +545,7 @@ module.exports = {
broadcastTokenBalancesUpdate,
broadcastTokenBalanceChanged,
broadcastDeploymentUpdate,
broadcastModulesUpdate,
getConnectedUsers,
getStats
};