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

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

View File

@@ -21,6 +21,7 @@ const errorHandler = require('./middleware/errorHandler');
// const { version } = require('./package.json'); // Закомментировано, так как не используется // const { version } = require('./package.json'); // Закомментировано, так как не используется
const db = require('./db'); // Добавляем импорт db const db = require('./db'); // Добавляем импорт db
const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant
const deploymentWebSocketService = require('./services/deploymentWebSocketService'); // WebSocket для деплоя
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const messagesRoutes = require('./routes/messages'); const messagesRoutes = require('./routes/messages');
@@ -91,6 +92,7 @@ const blockchainRoutes = require('./routes/blockchain'); // Добавляем
const dleCoreRoutes = require('./routes/dleCore'); // Основные функции DLE const dleCoreRoutes = require('./routes/dleCore'); // Основные функции DLE
const dleProposalsRoutes = require('./routes/dleProposals'); // Функции предложений const dleProposalsRoutes = require('./routes/dleProposals'); // Функции предложений
const dleModulesRoutes = require('./routes/dleModules'); // Функции модулей const dleModulesRoutes = require('./routes/dleModules'); // Функции модулей
const moduleDeploymentRoutes = require('./routes/moduleDeployment'); // Деплой модулей
const dleTokensRoutes = require('./routes/dleTokens'); // Функции токенов const dleTokensRoutes = require('./routes/dleTokens'); // Функции токенов
const dleAnalyticsRoutes = require('./routes/dleAnalytics'); // Аналитика и история const dleAnalyticsRoutes = require('./routes/dleAnalytics'); // Аналитика и история
const compileRoutes = require('./routes/compile'); // Компиляция контрактов 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-core', dleCoreRoutes); // Основные функции DLE
app.use('/api/dle-proposals', dleProposalsRoutes); // Функции предложений app.use('/api/dle-proposals', dleProposalsRoutes); // Функции предложений
app.use('/api/dle-modules', dleModulesRoutes); // Функции модулей app.use('/api/dle-modules', dleModulesRoutes); // Функции модулей
app.use('/api/module-deployment', moduleDeploymentRoutes); // Деплой модулей
app.use('/api/dle-tokens', dleTokensRoutes); // Функции токенов app.use('/api/dle-tokens', dleTokensRoutes); // Функции токенов
app.use('/api/dle-analytics', dleAnalyticsRoutes); // Аналитика и история app.use('/api/dle-analytics', dleAnalyticsRoutes); // Аналитика и история
app.use('/api/dle-multichain', dleMultichainRoutes); // Мультичейн функции 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, runOnCompile: true,
disambiguatePaths: false, disambiguatePaths: false,
}, },
// Автокомпиляция при изменениях
watch: {
compilation: {
tasks: ["compile"],
files: ["./contracts/**/*.sol"],
verbose: true
}
},
networks: getNetworks(), networks: getNetworks(),
etherscan: { etherscan: {
// Единый API ключ для V2 API // Единый API ключ для V2 API

View File

@@ -23,6 +23,8 @@
"fix-duplicates": "node scripts/fix-duplicate-identities.js", "fix-duplicates": "node scripts/fix-duplicate-identities.js",
"deploy:multichain": "node scripts/deploy/deploy-multichain.js", "deploy:multichain": "node scripts/deploy/deploy-multichain.js",
"deploy:complete": "node scripts/deploy/deploy-dle-complete.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" "verify:contracts": "node scripts/verify-contracts.js"
}, },
"dependencies": { "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 path = require('path');
const fs = require('fs'); const fs = require('fs');
// WebSocket сервис для отслеживания деплоя
const deploymentWebSocketService = require('../../services/deploymentWebSocketService');
// Подбираем безопасные gas/fee для разных сетей (включая L2) // Подбираем безопасные gas/fee для разных сетей (включая L2)
async function getFeeOverrides(provider, { minPriorityGwei = 1n, minFeeGwei = 20n } = {}) { async function getFeeOverrides(provider, { minPriorityGwei = 1n, minFeeGwei = 20n } = {}) {
const fee = await provider.getFeeData(); const fee = await provider.getFeeData();
@@ -64,6 +67,15 @@ const MODULE_CONFIGS = {
verificationArgs: (dleAddress) => [ verificationArgs: (dleAddress) => [
dleAddress // _dleContract dleAddress // _dleContract
] ]
},
hierarchicalVoting: {
contractName: 'HierarchicalVotingModule',
constructorArgs: (dleAddress) => [
dleAddress // _dleContract
],
verificationArgs: (dleAddress) => [
dleAddress // _dleContract
]
} }
// Здесь можно легко добавлять новые модули: // Здесь можно легко добавлять новые модули:
// newModule: { // newModule: {
@@ -215,30 +227,6 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
return { address: deployedAddress, chainId: Number(net.chainId) }; 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) { 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 moduleInit = moduleInits[moduleType];
const targetNonce = targetNonces[moduleType]; const targetNonce = targetNonces[moduleType];
// Уведомляем WebSocket клиентов о начале деплоя модуля
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', `Деплой модуля ${moduleType} в сети ${net.name || net.chainId}`);
if (!MODULE_CONFIGS[moduleType]) { if (!MODULE_CONFIGS[moduleType]) {
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type: ${moduleType}`); console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type: ${moduleType}`);
results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` }; results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` };
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Неизвестный тип модуля: ${moduleType}`);
continue; continue;
} }
if (!moduleInit) { if (!moduleInit) {
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} No init code for module: ${moduleType}`); 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}` }; results[moduleType] = { success: false, error: `No init code for module: ${moduleType}` };
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Отсутствует код инициализации для модуля: ${moduleType}`);
continue; continue;
} }
try { try {
const result = await deployModuleInNetwork(rpcUrl, pk, salt, null, targetNonce, moduleInit, moduleType); const result = await deployModuleInNetwork(rpcUrl, pk, salt, null, targetNonce, moduleInit, moduleType);
results[moduleType] = { ...result, success: true }; results[moduleType] = { ...result, success: true };
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', `Модуль ${moduleType} успешно задеплоен в сети ${net.name || net.chainId}: ${result.address}`);
} catch (error) { } catch (error) {
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployment failed:`, error.message); console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployment failed:`, error.message);
results[moduleType] = { results[moduleType] = {
@@ -278,6 +272,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
success: false, success: false,
error: error.message 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) { async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces) {
@@ -339,26 +298,20 @@ async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, mod
return results; 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() { async function main() {
const { ethers } = hre; 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; let params;
@@ -367,14 +320,26 @@ async function main() {
const DeployParamsService = require('../../services/deployParamsService'); const DeployParamsService = require('../../services/deployParamsService');
const deployParamsService = new DeployParamsService(); const deployParamsService = new DeployParamsService();
// Проверяем, передан ли конкретный 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 {
// Получаем последние параметры деплоя // Получаем последние параметры деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1); const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) { if (latestParams.length > 0) {
params = latestParams[0]; params = latestParams[0];
console.log('✅ Параметры загружены из базы данных'); console.log('✅ Параметры загружены из базы данных (последние)');
} else { } else {
throw new Error('Параметры деплоя не найдены в базе данных'); throw new Error('Параметры деплоя не найдены в базе данных');
} }
}
await deployParamsService.close(); await deployParamsService.close();
} catch (dbError) { } catch (dbError) {
@@ -396,15 +361,25 @@ async function main() {
CREATE2_SALT: params.CREATE2_SALT 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 networks = params.rpcUrls || params.rpc_urls || [];
const dleAddress = params.dleAddress; const dleAddress = params.dleAddress;
const salt = params.CREATE2_SALT || params.create2_salt; 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 (!dleAddress) throw new Error('DLE_ADDRESS not found in params');
if (!salt) throw new Error('CREATE2_SALT 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'); 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] DLE Address: ${dleAddress}`);
console.log(`[MODULES_DBG] Modules to deploy: ${modulesToDeploy.join(', ')}`); console.log(`[MODULES_DBG] Modules to deploy: ${modulesToDeploy.join(', ')}`);
console.log(`[MODULES_DBG] Networks:`, networks); 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]); const unsupportedModules = modulesToDeploy.filter(module => !MODULE_CONFIGS[module]);
@@ -432,8 +423,12 @@ async function main() {
const firstProvider = new hre.ethers.JsonRpcProvider(networks[0]); const firstProvider = new hre.ethers.JsonRpcProvider(networks[0]);
const firstWallet = new hre.ethers.Wallet(pk, firstProvider); const firstWallet = new hre.ethers.Wallet(pk, firstProvider);
const firstNetwork = await firstProvider.getNetwork(); const firstNetwork = await firstProvider.getNetwork();
// Получаем аргументы конструктора
const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(firstNetwork.chainId), firstWallet.address); const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(firstNetwork.chainId), firstWallet.address);
console.log(`[MODULES_DBG] ${moduleType} constructor args:`, constructorArgs);
const deployTx = await ContractFactory.getDeployTransaction(...constructorArgs); const deployTx = await ContractFactory.getDeployTransaction(...constructorArgs);
moduleInits[moduleType] = deployTx.data; moduleInits[moduleType] = deployTx.data;
moduleInitCodeHashes[moduleType] = ethers.keccak256(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] SUCCESS: All ${moduleType} addresses are identical:`, uniqueAddresses[0]);
} }
// Верификация во всех сетях // Верификация во всех сетях через отдельный скрипт
console.log(`[MODULES_DBG] Starting verification in all networks...`); 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) => ({ const finalResults = deployResults.map((deployResult, index) => ({
@@ -559,11 +577,20 @@ async function main() {
dleAddress: dleAddress, dleAddress: dleAddress,
networks: [], networks: [],
deployTimestamp: new Date().toISOString(), deployTimestamp: new Date().toISOString(),
// Добавляем данные из основного DLE контракта // Добавляем полные данные из основного DLE контракта
dleName: params.name, dleName: params.name,
dleSymbol: params.symbol, dleSymbol: params.symbol,
dleLocation: params.location, 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 Name: ${params.name}`);
console.log(`[MODULES_DBG] DLE Symbol: ${params.symbol}`); console.log(`[MODULES_DBG] DLE Symbol: ${params.symbol}`);
console.log(`[MODULES_DBG] DLE Location: ${params.location}`); 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); }); main().catch((e) => { console.error(e); process.exit(1); });

View File

@@ -4,6 +4,7 @@
const { execSync } = require('child_process'); const { execSync } = require('child_process');
const DeployParamsService = require('../services/deployParamsService'); const DeployParamsService = require('../services/deployParamsService');
const deploymentWebSocketService = require('../services/deploymentWebSocketService');
async function verifyWithHardhatV2(params = null, deployedNetworks = null) { async function verifyWithHardhatV2(params = null, deployedNetworks = null) {
console.log('🚀 Запуск верификации с Hardhat V2...'); console.log('🚀 Запуск верификации с Hardhat V2...');
@@ -226,6 +227,22 @@ async function verifyWithHardhatV2(params = null, deployedNetworks = null) {
// Запускаем верификацию если скрипт вызван напрямую // Запускаем верификацию если скрипт вызван напрямую
if (require.main === module) { if (require.main === module) {
// Проверяем аргументы командной строки
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() verifyWithHardhatV2()
.then(() => { .then(() => {
console.log('\n🏁 Скрипт завершен'); console.log('\n🏁 Скрипт завершен');
@@ -235,6 +252,170 @@ if (require.main === module) {
console.error('💥 Скрипт завершился с ошибкой:', error); console.error('💥 Скрипт завершился с ошибкой:', error);
process.exit(1); 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 { app, nonceStore } = require('./app');
const http = require('http'); const http = require('http');
const { initWSS } = require('./wsHub'); const { initWSS } = require('./wsHub');
const deploymentWebSocketService = require('./services/deploymentWebSocketService');
const logger = require('./utils/logger'); const logger = require('./utils/logger');
const { getBot } = require('./services/telegramBot'); const { getBot } = require('./services/telegramBot');
const EmailBotService = require('./services/emailBot'); const EmailBotService = require('./services/emailBot');
@@ -62,6 +63,8 @@ async function initServices() {
const server = http.createServer(app); const server = http.createServer(app);
initWSS(server); initWSS(server);
// WebSocket сервис для деплоя модулей теперь интегрирован в основной WebSocket сервер
// WebSocket уже инициализирован в wsHub.js // WebSocket уже инициализирован в wsHub.js
async function startServer() { 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 WebSocket = require('ws');
const tokenBalanceService = require('./services/tokenBalanceService'); const tokenBalanceService = require('./services/tokenBalanceService');
const deploymentTracker = require('./utils/deploymentTracker'); const deploymentTracker = require('./utils/deploymentTracker');
const deploymentWebSocketService = require('./services/deploymentWebSocketService');
let wss = null; let wss = null;
// Храним клиентов по userId для персонализированных уведомлений // Храним клиентов по userId для персонализированных уведомлений
@@ -70,6 +71,17 @@ function initWSS(server) {
// Запрос балансов токенов // Запрос балансов токенов
handleTokenBalancesRequest(ws, data.address, data.userId); 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) { } catch (error) {
// console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error); // console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
} }
@@ -485,6 +497,35 @@ function broadcastDeploymentUpdate(data) {
console.log(`📡 [WebSocket] Отправлено deployment update: ${data.type || 'unknown'}`); 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 = { module.exports = {
initWSS, initWSS,
broadcastContactsUpdate, broadcastContactsUpdate,
@@ -504,6 +545,7 @@ module.exports = {
broadcastTokenBalancesUpdate, broadcastTokenBalancesUpdate,
broadcastTokenBalanceChanged, broadcastTokenBalanceChanged,
broadcastDeploymentUpdate, broadcastDeploymentUpdate,
broadcastModulesUpdate,
getConnectedUsers, getConnectedUsers,
getStats getStats
}; };

View File

@@ -0,0 +1,151 @@
# Интеграция динамических операций модулей в CreateProposalView
## Обзор
Реализована система автоматического отображения блоков с предложениями операций модулей в реальном времени на странице создания предложений (`/management/create-proposal`). После деплоя модуля карточки с операциями автоматически появляются без необходимости обновления страницы.
## Архитектура решения
### 1. Backend API Endpoints
Добавлены новые API endpoints в `/backend/routes/dleModules.js`:
- `POST /dle-modules/get-module-operations` - получение всех доступных операций модулей для DLE
- `POST /dle-modules/get-module-specific-operations` - получение операций конкретного модуля
- `POST /dle-modules/get-module-interface` - получение ABI и интерфейса модуля
- `POST /dle-modules/get-module-available-functions` - получение доступных функций модуля
- `POST /dle-modules/get-module-function-parameters` - получение параметров функции модуля
- `POST /dle-modules/create-module-operation-proposal` - создание предложения для операции модуля
- `POST /dle-modules/validate-module-operation` - валидация операции модуля
### 2. Frontend Services
Создан новый сервис `/frontend/src/services/moduleOperationsService.js` с функциями:
- `getModuleOperations(dleAddress)` - получение операций модулей
- `getModuleSpecificOperations(dleAddress, moduleType, moduleAddress, chainId)` - операции конкретного модуля
- `createModuleOperationProposal(dleAddress, operationData)` - создание предложения
- `getModuleInterface(moduleType, moduleAddress, chainId)` - получение интерфейса
- `validateModuleOperation(dleAddress, operationData)` - валидация операции
### 3. WebSocket Integration
#### Backend (wsHub.js)
- Добавлена функция `broadcastModulesUpdate(dleAddress, updateType, moduleData)` для отправки уведомлений об обновлениях модулей
- Уведомления отправляются при обнаружении новых модулей в файлах деплоя
#### Frontend (CreateProposalView.vue)
- Подключение к WebSocket при монтировании компонента
- Обработка сообщений: `modules_updated`, `module_verified`, `module_status_changed`
- Автоматическое обновление списка операций при получении уведомлений
### 4. Динамическое отображение
#### Состояние компонента
```javascript
const moduleOperations = ref([]);
const isLoadingModuleOperations = ref(false);
const modulesWebSocket = ref(null);
const isModulesWSConnected = ref(false);
```
#### Шаблон
- Условное отображение индикатора загрузки
- Динамическое создание блоков операций для каждого модуля
- Анимация появления новых блоков
## Поддерживаемые типы модулей и операции
### 1. Treasury Module (💰)
- **Депозит средств** - пополнение казначейства DLE
- **Вывод средств** - вывод средств из казначейства
- **Распределение дивидендов** - распределение дивидендов между держателями токенов
### 2. Timelock Module (⏰)
- **Установить задержку** - установить время задержки для операций
- **Поставить операцию в очередь** - добавить операцию в очередь для выполнения
### 3. Reader Module (📖)
- **Обновить данные** - обновить информацию о DLE
### 4. Hierarchical Voting Module (🗳️)
- **Голосование во внешнем DLE** - использовать токены для голосования в другом DLE
## Реальный процесс работы
1. **Деплой модуля** → Backend сохраняет информацию в файлы деплоя
2. **Обнаружение модуля**`getDeployedModulesInfo()` читает файлы и находит новые модули
3. **WebSocket уведомление**`broadcastModulesUpdate()` отправляет уведомление клиентам
4. **Обновление UI** → Frontend получает уведомление и автоматически загружает новые операции
5. **Отображение блоков** → Новые блоки операций появляются с анимацией
## Стили и UX
### Визуальные особенности
- Отдельные стили для блоков операций модулей (`.module-operation-block`)
- Цветовая схема с зеленым акцентом для модулей
- Теги категорий операций
- Анимация появления (`fadeInUp`)
- Индикатор загрузки с анимацией
### Адаптивность
- Responsive grid для блоков операций
- Поддержка мобильных устройств
- Адаптивные размеры и отступы
## Технические детали
### WebSocket Events
```javascript
// Подписка на обновления
{
type: 'subscribe',
dleAddress: '0x...'
}
// Уведомления
{
type: 'modules_updated',
dleAddress: '0x...',
moduleData: { modulesCount: 3, moduleTypes: ['treasury', 'timelock'] },
timestamp: 1234567890
}
```
### Структура данных операций
```javascript
{
id: 'deposit',
name: 'Депозит средств',
description: 'Пополнение казначейства DLE',
icon: '💰',
functionName: 'deposit',
parameters: [
{ name: 'amount', type: 'uint256', label: 'Сумма', required: true }
],
category: 'Финансы'
}
```
## Будущие улучшения
1. **Полные формы создания предложений** - замена alert на полноценные модальные окна
2. **Валидация параметров** - клиентская валидация перед отправкой
3. **Предпросмотр операций** - отображение calldata перед созданием предложения
4. **История операций** - показ выполненных операций модулей
5. **Расширенная фильтрация** - фильтры по типам операций и модулей
## Файлы изменений
### Новые файлы
- `frontend/src/services/moduleOperationsService.js`
- `docs/MODULE_OPERATIONS_INTEGRATION.md`
### Измененные файлы
- `frontend/src/views/smartcontracts/CreateProposalView.vue`
- `backend/routes/dleModules.js`
- `backend/wsHub.js`
## Заключение
Реализована полноценная система динамического отображения операций модулей с реальным временем обновлений через WebSocket. После деплоя модуля пользователи сразу видят доступные операции без необходимости обновления страницы, что значительно улучшает пользовательский опыт.

View File

@@ -242,56 +242,6 @@ const routes = [
name: 'management-modules', name: 'management-modules',
component: () => import('../views/smartcontracts/ModulesView.vue') component: () => import('../views/smartcontracts/ModulesView.vue')
}, },
{
path: '/management/modules/deploy/treasury',
name: 'module-deploy-treasury',
component: () => import('../views/smartcontracts/modules/TreasuryModuleDeployView.vue')
},
{
path: '/management/modules/deploy/timelock',
name: 'module-deploy-timelock',
component: () => import('../views/smartcontracts/modules/TimelockModuleDeployView.vue')
},
{
path: '/management/modules/deploy/reader',
name: 'module-deploy-reader',
component: () => import('../views/smartcontracts/modules/DLEReaderDeployView.vue')
},
{
path: '/management/modules/deploy/communication',
name: 'module-deploy-communication',
component: () => import('../views/smartcontracts/modules/CommunicationModuleDeployView.vue')
},
{
path: '/management/modules/deploy/application',
name: 'module-deploy-application',
component: () => import('../views/smartcontracts/modules/ApplicationModuleDeployView.vue')
},
{
path: '/management/modules/deploy/mint',
name: 'module-deploy-mint',
component: () => import('../views/smartcontracts/modules/MintModuleDeploy.vue')
},
{
path: '/management/modules/deploy/burn',
name: 'module-deploy-burn',
component: () => import('../views/smartcontracts/modules/BurnModuleDeploy.vue')
},
{
path: '/management/modules/deploy/oracle',
name: 'module-deploy-oracle',
component: () => import('../views/smartcontracts/modules/OracleModuleDeploy.vue')
},
{
path: '/management/modules/deploy/custom',
name: 'module-deploy-custom',
component: () => import('../views/smartcontracts/modules/ModuleDeployFormView.vue')
},
{
path: '/management/modules/deploy/inheritance',
name: 'module-deploy-inheritance',
component: () => import('../views/smartcontracts/modules/InheritanceModuleDeploy.vue')
},
// { // {
// path: '/management/multisig', // path: '/management/multisig',
// name: 'management-multisig', // name: 'management-multisig',

View File

@@ -0,0 +1,195 @@
/**
* 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
*/
// Сервис для работы с операциями модулей DLE
import api from '@/api/axios';
/**
* Получает доступные операции для модулей DLE
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Доступные операции
*/
export const getModuleOperations = async (dleAddress) => {
try {
const response = await api.post('/dle-modules/get-module-operations', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении операций модулей:', error);
throw error;
}
};
/**
* Получает операции для конкретного модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleType - Тип модуля
* @param {string} moduleAddress - Адрес модуля
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Операции модуля
*/
export const getModuleSpecificOperations = async (dleAddress, moduleType, moduleAddress, chainId) => {
try {
const response = await api.post('/dle-modules/get-module-specific-operations', {
dleAddress,
moduleType,
moduleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении операций конкретного модуля:', error);
throw error;
}
};
/**
* Создает предложение для выполнения операции модуля
* @param {string} dleAddress - Адрес DLE
* @param {Object} operationData - Данные операции
* @returns {Promise<Object>} - Результат создания предложения
*/
export const createModuleOperationProposal = async (dleAddress, operationData) => {
try {
const response = await api.post('/dle-modules/create-module-operation-proposal', {
dleAddress,
...operationData
});
return response.data;
} catch (error) {
console.error('Ошибка при создании предложения для операции модуля:', error);
throw error;
}
};
/**
* Получает ABI и интерфейс модуля
* @param {string} moduleType - Тип модуля
* @param {string} moduleAddress - Адрес модуля
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - ABI и интерфейс модуля
*/
export const getModuleInterface = async (moduleType, moduleAddress, chainId) => {
try {
const response = await api.post('/dle-modules/get-module-interface', {
moduleType,
moduleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении интерфейса модуля:', error);
throw error;
}
};
/**
* Получает доступные функции модуля для создания предложений
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleType - Тип модуля
* @param {string} moduleAddress - Адрес модуля
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Доступные функции
*/
export const getModuleAvailableFunctions = async (dleAddress, moduleType, moduleAddress, chainId) => {
try {
const response = await api.post('/dle-modules/get-module-available-functions', {
dleAddress,
moduleType,
moduleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении доступных функций модуля:', error);
throw error;
}
};
/**
* Получает параметры функции модуля
* @param {string} moduleType - Тип модуля
* @param {string} functionName - Имя функции
* @returns {Promise<Object>} - Параметры функции
*/
export const getModuleFunctionParameters = async (moduleType, functionName) => {
try {
const response = await api.post('/dle-modules/get-module-function-parameters', {
moduleType,
functionName
});
return response.data;
} catch (error) {
console.error('Ошибка при получении параметров функции модуля:', error);
throw error;
}
};
/**
* Валидирует параметры операции модуля
* @param {string} dleAddress - Адрес DLE
* @param {Object} operationData - Данные операции
* @returns {Promise<Object>} - Результат валидации
*/
export const validateModuleOperation = async (dleAddress, operationData) => {
try {
const response = await api.post('/dle-modules/validate-module-operation', {
dleAddress,
...operationData
});
return response.data;
} catch (error) {
console.error('Ошибка при валидации операции модуля:', error);
throw error;
}
};
/**
* Получает историю операций модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleType - Тип модуля
* @param {Object} filters - Фильтры
* @returns {Promise<Object>} - История операций
*/
export const getModuleOperationsHistory = async (dleAddress, moduleType, filters = {}) => {
try {
const response = await api.post('/dle-modules/get-module-operations-history', {
dleAddress,
moduleType,
...filters
});
return response.data;
} catch (error) {
console.error('Ошибка при получении истории операций модуля:', error);
throw error;
}
};
/**
* Получает статус выполнения операции модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} operationId - ID операции
* @returns {Promise<Object>} - Статус операции
*/
export const getModuleOperationStatus = async (dleAddress, operationId) => {
try {
const response = await api.post('/dle-modules/get-module-operation-status', {
dleAddress,
operationId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статуса операции модуля:', error);
throw error;
}
};

View File

@@ -148,6 +148,40 @@
</div> </div>
</div> </div>
<!-- Операции модулей (динамические) -->
<div v-if="isLoadingModuleOperations" class="loading-modules">
Загрузка операций модулей...
</div>
<div
v-for="moduleOperation in moduleOperations"
:key="moduleOperation.moduleType"
class="operation-category"
>
<h5>{{ getModuleIcon(moduleOperation.moduleType) }} {{ moduleOperation.moduleName }}</h5>
<p class="module-description">{{ moduleOperation.moduleDescription }}</p>
<div class="operation-blocks">
<div
v-for="operation in moduleOperation.operations"
:key="operation.id"
class="operation-block module-operation-block"
>
<div class="operation-icon">{{ operation.icon }}</div>
<h6>{{ operation.name }}</h6>
<p>{{ operation.description }}</p>
<div class="operation-category-tag">{{ operation.category }}</div>
<button
class="create-btn"
@click="openModuleOperationForm(moduleOperation.moduleType, operation)"
:disabled="!props.isAuthenticated || isLoadingModuleOperations"
>
<span v-if="isLoadingModuleOperations">Загрузка...</span>
<span v-else>Создать</span>
</button>
</div>
</div>
</div>
<!-- Оффчейн операции --> <!-- Оффчейн операции -->
<div class="operation-category"> <div class="operation-category">
<h5>📋 Оффчейн операции</h5> <h5>📋 Оффчейн операции</h5>
@@ -169,12 +203,13 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, defineProps, defineEmits, inject } from 'vue'; import { ref, computed, onMounted, onUnmounted, defineProps, defineEmits, inject } from 'vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
import { useAuthContext } from '../../composables/useAuth'; import { useAuthContext } from '../../composables/useAuth';
import BaseLayout from '../../components/BaseLayout.vue'; import BaseLayout from '../../components/BaseLayout.vue';
import { getDLEInfo, getSupportedChains } from '../../services/dleV2Service.js'; import { getDLEInfo, getSupportedChains } from '../../services/dleV2Service.js';
import { createProposal as createProposalAPI } from '../../services/proposalsService.js'; import { createProposal as createProposalAPI } from '../../services/proposalsService.js';
import { getModuleOperations } from '../../services/moduleOperationsService.js';
import api from '../../api/axios'; import api from '../../api/axios';
import wsClient from '../../utils/websocket.js'; import wsClient from '../../utils/websocket.js';
import { ethers } from 'ethers'; import { ethers } from 'ethers';
@@ -221,6 +256,12 @@ const isLoadingDle = ref(false);
// Доступные цепочки (загружаются из конфигурации) // Доступные цепочки (загружаются из конфигурации)
const availableChains = ref([]); const availableChains = ref([]);
// Состояние модулей и их операций
const moduleOperations = ref([]);
const isLoadingModuleOperations = ref(false);
const modulesWebSocket = ref(null);
const isModulesWSConnected = ref(false);
// Функции для открытия отдельных форм операций // Функции для открытия отдельных форм операций
function openTransferForm() { function openTransferForm() {
// TODO: Открыть форму для передачи токенов // TODO: Открыть форму для передачи токенов
@@ -273,6 +314,26 @@ function openOffchainActionForm() {
alert('Форма оффчейн действий будет реализована'); alert('Форма оффчейн действий будет реализована');
} }
// Функция для создания предложения операции модуля
function openModuleOperationForm(moduleType, operation) {
console.log('[CreateProposalView] Открытие формы для операции модуля:', { moduleType, operation });
// TODO: Открыть форму для создания предложения операции модуля
// Пока показываем alert с информацией об операции
alert(`Создание предложения для операции "${operation.name}" модуля ${moduleType}.\n\nОписание: ${operation.description}\ункция: ${operation.functionName}\nКатегория: ${operation.category}`);
}
// Получить иконку для типа модуля
function getModuleIcon(moduleType) {
const icons = {
treasury: '💰',
timelock: '⏰',
reader: '📖',
hierarchicalVoting: '🗳️'
};
return icons[moduleType] || '🔧';
}
// Функции // Функции
async function loadDleData() { async function loadDleData() {
console.log('loadDleData вызвана с адресом:', dleAddress.value); console.log('loadDleData вызвана с адресом:', dleAddress.value);
@@ -300,6 +361,9 @@ async function loadDleData() {
const chainsResponse = await getSupportedChains(dleAddress.value); const chainsResponse = await getSupportedChains(dleAddress.value);
availableChains.value = chainsResponse.data?.chains || []; availableChains.value = chainsResponse.data?.chains || [];
// Загружаем операции модулей
await loadModuleOperations();
} catch (error) { } catch (error) {
console.error('Ошибка загрузки данных DLE из блокчейна:', error); console.error('Ошибка загрузки данных DLE из блокчейна:', error);
} finally { } finally {
@@ -307,6 +371,113 @@ async function loadDleData() {
} }
} }
// Загрузка операций модулей
async function loadModuleOperations() {
if (!dleAddress.value) {
console.warn('Адрес DLE не указан для загрузки операций модулей');
return;
}
isLoadingModuleOperations.value = true;
try {
console.log('[CreateProposalView] Загрузка операций модулей для DLE:', dleAddress.value);
const response = await getModuleOperations(dleAddress.value);
if (response.success) {
moduleOperations.value = response.data.moduleOperations || [];
console.log('[CreateProposalView] Загружены операции модулей:', moduleOperations.value);
} else {
console.error('[CreateProposalView] Ошибка загрузки операций модулей:', response.error);
moduleOperations.value = [];
}
} catch (error) {
console.error('[CreateProposalView] Ошибка загрузки операций модулей:', error);
moduleOperations.value = [];
} finally {
isLoadingModuleOperations.value = false;
}
}
// WebSocket функции для модулей
function connectModulesWebSocket() {
if (modulesWebSocket.value && modulesWebSocket.value.readyState === WebSocket.OPEN) {
return;
}
const wsUrl = `ws://localhost:8000/ws/deployment`;
modulesWebSocket.value = new WebSocket(wsUrl);
modulesWebSocket.value.onopen = () => {
console.log('[CreateProposalView] WebSocket модулей соединение установлено');
isModulesWSConnected.value = true;
// Подписываемся на обновления модулей для текущего DLE
if (dleAddress.value) {
modulesWebSocket.value.send(JSON.stringify({
type: 'subscribe',
dleAddress: dleAddress.value
}));
}
};
modulesWebSocket.value.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
handleModulesWebSocketMessage(data);
} catch (error) {
console.error('[CreateProposalView] Ошибка парсинга WebSocket сообщения модулей:', error);
}
};
modulesWebSocket.value.onclose = () => {
console.log('[CreateProposalView] WebSocket модулей соединение закрыто');
isModulesWSConnected.value = false;
// Переподключаемся через 5 секунд
setTimeout(() => {
connectModulesWebSocket();
}, 5000);
};
modulesWebSocket.value.onerror = (error) => {
console.error('[CreateProposalView] Ошибка WebSocket модулей:', error);
isModulesWSConnected.value = false;
};
}
function handleModulesWebSocketMessage(data) {
console.log('[CreateProposalView] WebSocket модулей сообщение:', data);
switch (data.type) {
case 'modules_updated':
// Автоматически обновляем список операций модулей
console.log('[CreateProposalView] Получено уведомление об обновлении модулей');
loadModuleOperations();
break;
case 'module_verified':
// Обновляем операции модуля
console.log(`[CreateProposalView] Модуль ${data.moduleType} верифицирован`);
loadModuleOperations();
break;
case 'module_status_changed':
// Обновляем операции модуля
console.log(`[CreateProposalView] Статус модуля ${data.moduleType} изменен`);
loadModuleOperations();
break;
}
}
function disconnectModulesWebSocket() {
if (modulesWebSocket.value) {
modulesWebSocket.value.close();
modulesWebSocket.value = null;
isModulesWSConnected.value = false;
}
}
onMounted(async () => { onMounted(async () => {
// Принудительно загружаем токены, если пользователь аутентифицирован // Принудительно загружаем токены, если пользователь аутентифицирован
if (isAuthenticated.value && address.value) { if (isAuthenticated.value && address.value) {
@@ -318,6 +489,14 @@ onMounted(async () => {
if (dleAddress.value) { if (dleAddress.value) {
loadDleData(); loadDleData();
} }
// Подключаемся к WebSocket для получения обновлений модулей
connectModulesWebSocket();
});
// Отключаем WebSocket при размонтировании компонента
onUnmounted(() => {
disconnectModulesWebSocket();
}); });
</script> </script>
@@ -562,6 +741,91 @@ onMounted(async () => {
display: none; display: none;
} }
/* Стили для модулей */
.module-description {
color: #666;
font-size: 0.9rem;
margin: 0.5rem 0 1rem 0;
font-style: italic;
}
.module-operation-block {
position: relative;
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 2px solid #e9ecef;
}
.module-operation-block::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #28a745, #20c997);
transform: scaleX(0);
transition: transform 0.3s ease;
}
.module-operation-block:hover::before {
transform: scaleX(1);
}
.operation-category-tag {
display: inline-block;
background: linear-gradient(135deg, #28a745, #20c997);
color: white;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
margin: 0.5rem 0;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Анимация появления модулей */
.operation-category {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Индикатор загрузки модулей */
.loading-modules {
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
color: #666;
font-style: italic;
}
.loading-modules::before {
content: '';
width: 20px;
height: 20px;
border: 2px solid #f3f3f3;
border-top: 2px solid var(--color-primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 0.5rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Адаптивность */ /* Адаптивность */
@media (max-width: 768px) { @media (max-width: 768px) {
.operations-blocks { .operations-blocks {

File diff suppressed because it is too large Load Diff

View File

@@ -1,329 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="application-module-deploy">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Деплой ApplicationModule</h1>
<p>Управление вызовом функций приложения через предложения и голосование</p>
<p v-if="dleAddress" class="dle-address">
<strong>DLE:</strong> {{ dleAddress }}
</p>
</div>
<button class="close-btn" @click="router.push('/management/modules')">×</button>
</div>
<!-- Информация о модуле -->
<div class="module-info">
<div class="info-card">
<h3>🖥 ApplicationModule</h3>
<div class="info-grid">
<div class="info-item">
<strong>Назначение:</strong> Управление функциями приложения через DLE
</div>
<div class="info-item">
<strong>Функции:</strong> Создание предложений для вызова API, голосование за операции
</div>
<div class="info-item">
<strong>Безопасность:</strong> Все операции приложения через кворум токен-холдеров
</div>
<div class="info-item">
<strong>Примеры:</strong> Удаление пользователей, изменение настроек, обновление данных
</div>
</div>
</div>
</div>
<!-- Детальное описание -->
<div class="module-details">
<div class="details-card">
<h3>📋 Как работает ApplicationModule</h3>
<div class="details-content">
<div class="detail-step">
<div class="step-number">1</div>
<div class="step-content">
<h4>Создание предложения</h4>
<p>Токен-холдер создает предложение для выполнения операции в приложении (например, удаление пользователя, изменение настроек)</p>
</div>
</div>
<div class="detail-step">
<div class="step-number">2</div>
<div class="step-content">
<h4>Голосование</h4>
<p>Все токен-холдеры голосуют за или против предложения в течение установленного времени</p>
</div>
</div>
<div class="detail-step">
<div class="step-number">3</div>
<div class="step-content">
<h4>Исполнение</h4>
<p>При достижении кворума предложение исполняется - вызывается соответствующая функция приложения</p>
</div>
</div>
<div class="detail-step">
<div class="step-number">4</div>
<div class="step-content">
<h4>Аудит</h4>
<p>Все операции логируются в блокчейне для полной прозрачности и подотчетности</p>
</div>
</div>
</div>
</div>
</div>
<!-- Форма деплоя будет добавлена позже -->
<div class="deploy-form-placeholder">
<div class="placeholder-content">
<h3>🚧 Форма деплоя в разработке</h3>
<p>Здесь будет форма для деплоя ApplicationModule</p>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
// Определяем props
const props = defineProps({
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false }
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Состояние
const isLoading = ref(false);
const dleAddress = ref(route.query.address || null);
// Инициализация
onMounted(() => {
console.log('[ApplicationModuleDeployView] Страница загружена');
});
</script>
<style scoped>
.application-module-deploy {
padding: 20px;
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-top: 20px;
margin-bottom: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2rem;
margin: 0;
}
.page-header p {
margin: 10px 0 0 0;
color: #666;
}
.dle-address {
margin-top: 10px !important;
font-family: monospace;
background: #f8f9fa;
padding: 8px 12px;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Информация о модуле */
.module-info {
margin-bottom: 30px;
}
.info-card {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
}
.info-card h3 {
margin: 0 0 15px 0;
color: var(--color-primary);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.info-item strong {
color: var(--color-primary);
}
/* Детальное описание */
.module-details {
margin-bottom: 30px;
}
.details-card {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
}
.details-card h3 {
margin: 0 0 20px 0;
color: var(--color-primary);
}
.details-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.detail-step {
display: flex;
align-items: flex-start;
gap: 15px;
padding: 15px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.step-number {
background: var(--color-primary);
color: white;
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 14px;
flex-shrink: 0;
}
.step-content h4 {
margin: 0 0 8px 0;
color: var(--color-primary);
font-size: 16px;
}
.step-content p {
margin: 0;
color: #666;
line-height: 1.5;
}
/* Плейсхолдер для формы */
.deploy-form-placeholder {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 40px;
text-align: center;
border: 2px dashed #dee2e6;
}
.placeholder-content h3 {
color: var(--color-primary);
margin-bottom: 10px;
}
.placeholder-content p {
color: #666;
margin: 0;
}
/* Адаптивность */
@media (max-width: 768px) {
.info-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.detail-step {
flex-direction: column;
text-align: center;
}
.step-number {
align-self: center;
}
}
</style>

View File

@@ -1,515 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="module-deploy-page">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>🔥 Деплой BurnModule</h1>
<p>Модуль для сжигания токенов DLE через governance</p>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
</div>
</div>
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
</div>
<!-- Описание модуля -->
<div class="module-description">
<div class="description-card">
<h3>📋 Описание BurnModule</h3>
<div class="description-content">
<p><strong>BurnModule</strong> - это модуль для управления сжиганием токенов DLE через систему governance.</p>
<h4>🔧 Функциональность:</h4>
<ul>
<li><strong>Сжигание токенов:</strong> Уменьшение общего предложения токенов DLE</li>
<li><strong>Governance:</strong> Все операции требуют голосования и кворума</li>
<li><strong>Безопасность:</strong> Контролируемое сжигание через коллективные решения</li>
<li><strong>Прозрачность:</strong> Все операции записываются в блокчейн</li>
</ul>
<h4> Важные особенности:</h4>
<ul>
<li>Сжигание токенов возможно только через предложения и голосование</li>
<li>Можно сжигать токены из казны DLE или от имени участников</li>
<li>Все операции требуют достижения кворума</li>
<li>История всех сжиганий сохраняется в блокчейне</li>
<li>Сжигание необратимо - токены уничтожаются навсегда</li>
</ul>
</div>
</div>
</div>
<!-- Форма деплоя -->
<div class="deploy-form-section">
<div class="form-header">
<h3> Настройки деплоя</h3>
<p>Настройте параметры для деплоя BurnModule</p>
</div>
<form @submit.prevent="deployModule" class="deploy-form">
<div class="form-row">
<div class="form-group">
<label for="moduleName">Название модуля:</label>
<input
id="moduleName"
v-model="deployData.moduleName"
type="text"
placeholder="BurnModule"
required
/>
</div>
<div class="form-group">
<label for="moduleVersion">Версия модуля:</label>
<input
id="moduleVersion"
v-model="deployData.moduleVersion"
type="text"
placeholder="1.0.0"
required
/>
</div>
</div>
<div class="form-group">
<label for="moduleDescription">Описание модуля:</label>
<textarea
id="moduleDescription"
v-model="deployData.moduleDescription"
placeholder="Модуль для сжигания токенов DLE через governance..."
rows="3"
required
></textarea>
</div>
<div class="form-group">
<label for="maxBurnPerProposal">Максимальное сжигание за одно предложение:</label>
<input
id="maxBurnPerProposal"
v-model="deployData.maxBurnPerProposal"
type="number"
min="1"
step="1"
placeholder="1000000"
required
/>
<small class="form-help">Максимальное количество токенов, которое можно сжечь за одно предложение</small>
</div>
<div class="form-group">
<label for="burnCooldown">Кулдаун между сжиганиями (часы):</label>
<input
id="burnCooldown"
v-model="deployData.burnCooldown"
type="number"
min="0"
step="1"
placeholder="24"
required
/>
<small class="form-help">Минимальное время между успешными сжиганиями токенов</small>
</div>
<div class="form-group">
<label for="allowTreasuryBurn">Разрешить сжигание из казны:</label>
<select
id="allowTreasuryBurn"
v-model="deployData.allowTreasuryBurn"
required
>
<option value="true">Да</option>
<option value="false">Нет</option>
</select>
<small class="form-help">Разрешить сжигание токенов из казны DLE</small>
</div>
<div class="form-group">
<label for="allowUserBurn">Разрешить сжигание от участников:</label>
<select
id="allowUserBurn"
v-model="deployData.allowUserBurn"
required
>
<option value="true">Да</option>
<option value="false">Нет</option>
</select>
<small class="form-help">Разрешить сжигание токенов от имени участников DLE</small>
</div>
<div class="form-group">
<label for="deployDescription">Описание предложения для деплоя:</label>
<textarea
id="deployDescription"
v-model="deployData.deployDescription"
placeholder="Предложение о деплое BurnModule для управления сжиганием токенов DLE..."
rows="3"
required
></textarea>
</div>
<div class="form-group">
<label for="votingDuration">Длительность голосования (часы):</label>
<input
id="votingDuration"
v-model="deployData.votingDuration"
type="number"
min="1"
max="168"
placeholder="24"
required
/>
<small class="form-help">Время для голосования по предложению деплоя (1-168 часов)</small>
</div>
<button type="submit" class="btn-primary" :disabled="isDeploying">
{{ isDeploying ? 'Деплой...' : 'Деплой BurnModule' }}
</button>
<!-- Статус деплоя -->
<div v-if="deployStatus" class="deploy-status">
<p class="status-message">{{ deployStatus }}</p>
</div>
</form>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import api from '../../../api/axios';
// Props
const props = defineProps({
isAuthenticated: {
type: Boolean,
default: false
},
identities: {
type: Array,
default: () => []
},
tokenBalances: {
type: Object,
default: () => ({})
},
isLoadingTokens: {
type: Boolean,
default: false
}
});
// Emits
const emit = defineEmits(['auth-action-completed']);
// Router
const route = useRoute();
const router = useRouter();
// Состояние
const isDeploying = ref(false);
const deployStatus = ref('');
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Данные для деплоя
const deployData = ref({
moduleName: 'BurnModule',
moduleVersion: '1.0.0',
moduleDescription: 'Модуль для сжигания токенов DLE через governance',
maxBurnPerProposal: 1000000,
burnCooldown: 24,
allowTreasuryBurn: 'true',
allowUserBurn: 'true',
deployDescription: 'Предложение о деплое BurnModule для управления сжиганием токенов DLE',
votingDuration: 24
});
// Получаем адрес DLE из URL
const dleAddress = computed(() => route.query.address);
// Загрузка данных DLE
const loadDleData = async () => {
if (!dleAddress.value) return;
try {
isLoadingDle.value = true;
const response = await api.post('/blockchain/read-dle-info', {
dleAddress: dleAddress.value
});
if (response.data.success) {
selectedDle.value = response.data.data;
}
} catch (error) {
console.error('Ошибка загрузки данных DLE:', error);
} finally {
isLoadingDle.value = false;
}
};
// Функция деплоя модуля
const deployModule = async () => {
if (isDeploying.value) return;
try {
isDeploying.value = true;
deployStatus.value = 'Подготовка к деплою...';
// Здесь будет логика деплоя модуля
console.log('Деплой BurnModule:', deployData.value);
// Временная заглушка
await new Promise(resolve => setTimeout(resolve, 2000));
deployStatus.value = 'Модуль успешно развернут!';
// Очищаем статус через 3 секунды
setTimeout(() => {
deployStatus.value = '';
}, 3000);
alert('BurnModule успешно развернут!');
} catch (error) {
console.error('Ошибка деплоя модуля:', error);
deployStatus.value = 'Ошибка деплоя модуля';
setTimeout(() => {
deployStatus.value = '';
}, 3000);
alert('Ошибка при деплое модуля');
} finally {
isDeploying.value = false;
}
};
// Загружаем данные при монтировании
onMounted(() => {
loadDleData();
});
</script>
<style scoped>
.module-deploy-page {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.header-content h1 {
color: var(--color-primary);
font-size: 2.5rem;
margin: 0 0 10px 0;
}
.header-content p {
color: var(--color-grey-dark);
font-size: 1.1rem;
margin: 0 0 15px 0;
}
.dle-info {
display: flex;
gap: 15px;
align-items: center;
}
.dle-name {
font-weight: 600;
color: var(--color-primary);
}
.dle-address {
font-family: monospace;
color: var(--color-grey-dark);
font-size: 0.9rem;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
color: var(--color-grey-dark);
cursor: pointer;
padding: 5px;
}
.close-btn:hover {
color: var(--color-primary);
}
.module-description {
margin-bottom: 30px;
}
.description-card {
background: #f8f9fa;
padding: 25px;
border-radius: var(--radius-lg);
border: 1px solid #e9ecef;
}
.description-card h3 {
color: var(--color-primary);
margin: 0 0 20px 0;
}
.description-content h4 {
color: var(--color-grey-dark);
margin: 20px 0 10px 0;
}
.description-content ul {
margin: 10px 0;
padding-left: 20px;
}
.description-content li {
margin: 5px 0;
line-height: 1.5;
}
.deploy-form-section {
background: white;
padding: 30px;
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.form-header {
margin-bottom: 25px;
}
.form-header h3 {
color: var(--color-primary);
margin: 0 0 10px 0;
}
.form-header p {
color: var(--color-grey-dark);
margin: 0;
}
.deploy-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-weight: 600;
color: var(--color-grey-dark);
}
.form-group input,
.form-group select,
.form-group textarea {
padding: 12px;
border: 1px solid #ddd;
border-radius: var(--radius-sm);
font-size: 1rem;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.form-help {
font-size: 0.85rem;
color: var(--color-grey-dark);
font-style: italic;
}
.btn-primary {
background: var(--color-primary);
color: white;
border: none;
padding: 15px 30px;
border-radius: var(--radius-sm);
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-primary:hover:not(:disabled) {
background: var(--color-primary-dark);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.deploy-status {
margin-top: 20px;
padding: 15px;
background: #e8f5e8;
border-radius: var(--radius-sm);
border-left: 4px solid #28a745;
}
.status-message {
margin: 0;
font-weight: 600;
color: #28a745;
}
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.dle-info {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
}
</style>

View File

@@ -1,218 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="communication-module-deploy">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Деплой CommunicationModule</h1>
<p>Коммуникации - сообщения, звонки, история общения между участниками</p>
<p v-if="dleAddress" class="dle-address">
<strong>DLE:</strong> {{ dleAddress }}
</p>
</div>
<button class="close-btn" @click="router.push('/management/modules')">×</button>
</div>
<!-- Информация о модуле -->
<div class="module-info">
<div class="info-card">
<h3>💬 CommunicationModule</h3>
<div class="info-grid">
<div class="info-item">
<strong>Назначение:</strong> Коммуникации между участниками DLE
</div>
<div class="info-item">
<strong>Функции:</strong> Сообщения, аудио/видео звонки, история общения
</div>
<div class="info-item">
<strong>Безопасность:</strong> Кворум для коммуникационных операций
</div>
</div>
</div>
</div>
<!-- Форма деплоя будет добавлена позже -->
<div class="deploy-form-placeholder">
<div class="placeholder-content">
<h3>🚧 Форма деплоя в разработке</h3>
<p>Здесь будет форма для деплоя CommunicationModule</p>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
// Определяем props
const props = defineProps({
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false }
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Состояние
const isLoading = ref(false);
const dleAddress = ref(route.query.address || null);
// Инициализация
onMounted(() => {
console.log('[CommunicationModuleDeployView] Страница загружена');
});
</script>
<style scoped>
.communication-module-deploy {
padding: 20px;
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-top: 20px;
margin-bottom: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2rem;
margin: 0;
}
.page-header p {
margin: 10px 0 0 0;
color: #666;
}
.dle-address {
margin-top: 10px !important;
font-family: monospace;
background: #f8f9fa;
padding: 8px 12px;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Информация о модуле */
.module-info {
margin-bottom: 30px;
}
.info-card {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
}
.info-card h3 {
margin: 0 0 15px 0;
color: var(--color-primary);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.info-item strong {
color: var(--color-primary);
}
/* Плейсхолдер для формы */
.deploy-form-placeholder {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 40px;
text-align: center;
border: 2px dashed #dee2e6;
}
.placeholder-content h3 {
color: var(--color-primary);
margin-bottom: 10px;
}
.placeholder-content p {
color: #666;
margin: 0;
}
/* Адаптивность */
@media (max-width: 768px) {
.info-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
}
</style>

View File

@@ -1,707 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="reader-module-deploy">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Деплой DLEReader</h1>
<p>API для чтения данных DLE - получение информации о контракте и предложениях</p>
<p v-if="dleAddress" class="dle-address">
<strong>DLE:</strong> {{ dleAddress }}
</p>
</div>
<button class="close-btn" @click="router.push('/management/modules')">×</button>
</div>
<!-- Информация о модуле -->
<div class="module-info">
<div class="info-card">
<h3>📊 DLEReader</h3>
<div class="info-grid">
<div class="info-item">
<strong>Назначение:</strong> Чтение данных DLE контракта
</div>
<div class="info-item">
<strong>Функции:</strong> API для предложений, голосования, статистики
</div>
<div class="info-item">
<strong>Безопасность:</strong> Только чтение, не изменяет состояние
</div>
</div>
</div>
</div>
<!-- Форма деплоя модуля администратором -->
<div v-if="canManageSettings" class="deploy-form">
<div class="form-header">
<h3>🔧 Деплой DLEReader администратором</h3>
<p>Администратор деплоит модуль, затем создает предложение для добавления в DLE</p>
</div>
<div class="form-content">
<!-- Информация о сетях -->
<div class="networks-info">
<h4>📡 Сети для деплоя:</h4>
<div class="networks-list">
<div class="network-item">
<span class="network-name">Sepolia</span>
<span class="network-chain-id">Chain ID: 11155111</span>
</div>
<div class="network-item">
<span class="network-name">Holesky</span>
<span class="network-chain-id">Chain ID: 17000</span>
</div>
<div class="network-item">
<span class="network-name">Arbitrum Sepolia</span>
<span class="network-chain-id">Chain ID: 421614</span>
</div>
<div class="network-item">
<span class="network-name">Base Sepolia</span>
<span class="network-chain-id">Chain ID: 84532</span>
</div>
</div>
</div>
<!-- Настройки модуля -->
<div class="module-settings">
<h4> Настройки DLEReader:</h4>
<div class="settings-form">
<!-- Поля администратора -->
<div class="admin-section">
<h5>🔐 Настройки администратора:</h5>
<div class="form-row">
<div class="form-group">
<label for="adminPrivateKey">Приватный ключ администратора:</label>
<input
type="password"
id="adminPrivateKey"
v-model="moduleSettings.adminPrivateKey"
class="form-control"
placeholder="0x..."
required
>
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
</div>
<div class="form-group">
<label for="etherscanApiKey">Etherscan API ключ:</label>
<input
type="text"
id="etherscanApiKey"
v-model="moduleSettings.etherscanApiKey"
class="form-control"
placeholder="YourAPIKey..."
>
<small class="form-help">API ключ для автоматической верификации контрактов</small>
</div>
</div>
</div>
<div class="simple-info">
<h5>📋 Информация о DLEReader:</h5>
<div class="info-text">
<p><strong>DLEReader</strong> - это простой read-only модуль, который:</p>
<ul>
<li> Только читает данные из DLE контракта</li>
<li> Не изменяет состояние блокчейна</li>
<li> Предоставляет API для получения информации</li>
<li> Безопасен для обновления</li>
</ul>
<p><strong>Конструктор принимает только один параметр:</strong> адрес DLE контракта</p>
</div>
</div>
</div>
</div>
<!-- Кнопка деплоя -->
<div class="deploy-actions">
<button
class="btn btn-primary btn-large deploy-module"
@click="deployDLEReader"
:disabled="isDeploying || !dleAddress || !isFormValid"
>
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
{{ isDeploying ? 'Деплой модуля...' : 'Деплой DLEReader' }}
</button>
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
<i class="fas fa-exclamation-triangle"></i>
<span>Заполните приватный ключ и API ключ для деплоя</span>
</div>
<div v-if="deploymentProgress" class="deployment-progress">
<div class="progress-info">
<span>{{ deploymentProgress.message }}</span>
<span class="progress-percentage">{{ deploymentProgress.percentage }}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: deploymentProgress.percentage + '%' }"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Сообщение для пользователей без прав доступа -->
<div v-if="!canManageSettings" class="no-access-message">
<div class="message-content">
<h3>🔒 Нет прав доступа</h3>
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
<button class="btn btn-secondary" @click="router.push('/management/modules')">
Вернуться к модулям
</button>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import { usePermissions } from '@/composables/usePermissions';
// Props
const props = defineProps({
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
const { canEdit, canManageSettings } = usePermissions();
// Состояние
const isLoading = ref(false);
const dleAddress = ref(route.query.address || null);
const isDeploying = ref(false);
const deploymentProgress = ref(null);
// Настройки модуля
const moduleSettings = ref({
// Поля администратора
adminPrivateKey: '',
etherscanApiKey: ''
});
// Проверка валидности формы
const isFormValid = computed(() => {
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
});
// Функция деплоя DLEReader
async function deployDLEReader() {
if (!canManageSettings.value) {
alert('У вас нет прав для деплоя смарт-контрактов');
return;
}
try {
isDeploying.value = true;
deploymentProgress.value = {
message: 'Инициализация деплоя...',
percentage: 0
};
console.log('[DLEReaderDeployView] Начинаем деплой DLEReader для DLE:', dleAddress.value);
// Вызываем API для деплоя модуля администратором
const response = await fetch('/api/dle-modules/deploy-reader-admin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
dleAddress: dleAddress.value,
moduleType: 'reader',
adminPrivateKey: moduleSettings.value.adminPrivateKey,
etherscanApiKey: moduleSettings.value.etherscanApiKey,
settings: {
// Используем настройки по умолчанию
useDefaultSettings: true
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
console.log('[DLEReaderDeployView] Деплой успешно запущен:', result);
// Обновляем прогресс
deploymentProgress.value = {
message: 'Деплой запущен успешно! Проверьте логи для отслеживания прогресса.',
percentage: 100
};
// Показываем детальную информацию о деплое
const deployInfo = result.data || {};
const deployedAddresses = deployInfo.addresses || [];
let successMessage = '✅ DLEReader успешно задеплоен!\n\n';
successMessage += `📊 Детали деплоя:\n`;
successMessage += `• DLE: ${dleAddress.value}\n`;
successMessage += `• Тип модуля: DLEReader\n`;
successMessage += `• Адрес модуля: ${deployInfo.moduleAddress || 'Не указан'}\n`;
if (deployedAddresses.length > 0) {
successMessage += `\n🌐 Задеплоенные адреса:\n`;
deployedAddresses.forEach((addr, index) => {
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
});
}
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
alert(successMessage);
// Перенаправляем обратно к модулям
setTimeout(() => {
router.push(`/management/modules?address=${dleAddress.value}`);
}, 3000);
} else {
throw new Error(result.error || 'Неизвестная ошибка');
}
} catch (error) {
console.error('[DLEReaderDeployView] Ошибка деплоя:', error);
alert('❌ Ошибка деплоя: ' + error.message);
deploymentProgress.value = {
message: 'Ошибка деплоя: ' + error.message,
percentage: 0
};
} finally {
isDeploying.value = false;
}
}
// Инициализация
onMounted(() => {
console.log('[DLEReaderDeployView] Страница загружена');
});
</script>
<style scoped>
.reader-module-deploy {
padding: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: var(--radius-lg);
color: white;
}
.header-content h1 {
margin: 0 0 10px 0;
font-size: 2rem;
font-weight: 700;
}
.header-content p {
margin: 0 0 5px 0;
opacity: 0.9;
font-size: 1.1rem;
}
.dle-address {
font-family: 'Courier New', monospace;
background: rgba(255, 255, 255, 0.1);
padding: 8px 12px;
border-radius: var(--radius-sm);
margin-top: 10px;
}
.close-btn {
background: rgba(255, 255, 255, 0.2);
border: none;
color: white;
font-size: 24px;
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
}
.close-btn:hover {
background: rgba(255, 255, 255, 0.3);
color: white;
}
/* Информация о модуле */
.module-info {
margin-bottom: 30px;
}
.info-card {
background: white;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.info-card h3 {
margin: 0 0 15px 0;
color: var(--color-primary);
font-size: 1.5rem;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border-left: 4px solid var(--color-primary);
}
.info-item strong {
color: var(--color-primary);
}
/* Форма деплоя */
.deploy-form {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
margin-bottom: 30px;
border: 1px solid #e9ecef;
}
.form-header h3 {
margin: 0 0 10px 0;
color: var(--color-primary);
}
.form-header p {
margin: 0 0 20px 0;
color: #666;
}
.networks-info,
.module-settings {
margin-bottom: 20px;
padding: 15px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.settings-form {
margin-top: 15px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: var(--radius-sm);
font-size: 14px;
transition: border-color 0.2s;
}
.form-control:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.1);
}
.form-help {
display: block;
margin-top: 5px;
font-size: 12px;
color: #666;
line-height: 1.4;
}
/* Настройки отображения данных */
.data-display-settings {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.data-display-settings h5 {
margin: 0 0 15px 0;
color: var(--color-primary);
font-size: 1.1rem;
font-weight: 600;
}
.data-display-settings .form-row {
margin-bottom: 15px;
}
.data-display-settings .form-group {
margin-bottom: 15px;
}
.data-display-settings .form-group:last-child {
margin-bottom: 0;
}
/* Секция администратора */
.admin-section {
margin-bottom: 20px;
padding: 20px;
background: #fff3cd;
border-radius: var(--radius-sm);
border: 1px solid #ffeaa7;
}
.admin-section h5 {
margin: 0 0 15px 0;
color: #856404;
font-size: 1.1rem;
font-weight: 600;
}
/* Простая информация */
.simple-info {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.simple-info h5 {
margin: 0 0 15px 0;
color: var(--color-primary);
font-size: 1.1rem;
font-weight: 600;
}
.info-text {
color: #666;
line-height: 1.6;
}
.info-text p {
margin: 0 0 10px 0;
}
.info-text ul {
margin: 10px 0;
padding-left: 20px;
}
.info-text li {
margin: 5px 0;
color: #555;
}
.info-text strong {
color: var(--color-primary);
}
.deploy-actions {
text-align: center;
margin-top: 20px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-large {
padding: 16px 32px;
font-size: 18px;
}
.deployment-progress {
margin-top: 20px;
padding: 15px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.progress-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.progress-percentage {
font-weight: 600;
color: var(--color-primary);
}
.progress-bar {
width: 100%;
height: 8px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-dark));
transition: width 0.3s ease;
}
/* Сети */
.networks-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
margin-top: 10px;
}
.network-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.network-name {
font-weight: 600;
color: var(--color-primary);
}
.network-chain-id {
font-size: 12px;
color: #666;
font-family: 'Courier New', monospace;
}
/* Сообщение об отсутствии прав доступа */
.no-access-message {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-md);
padding: 30px;
margin: 20px 0;
text-align: center;
}
.message-content h3 {
color: #856404;
margin-bottom: 15px;
font-size: 1.4em;
}
.message-content p {
color: #856404;
margin-bottom: 20px;
font-size: 1.1em;
line-height: 1.5;
}
.message-content .btn {
background: #6c757d;
color: white;
border: none;
border-radius: var(--radius-sm);
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.message-content .btn:hover {
background: #5a6268;
}
</style>

View File

@@ -1,663 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="module-deploy-page">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>🏛 Деплой InheritanceModule</h1>
<p>Модуль наследования токенов DLE - защита активов и автоматическая передача наследникам</p>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
</div>
</div>
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
</div>
<!-- Описание модуля -->
<div class="module-description">
<div class="description-card">
<h3>📋 Описание InheritanceModule</h3>
<div class="description-content">
<p><strong>InheritanceModule</strong> - это модуль для автоматической передачи токенов DLE наследникам в случае смерти или недееспособности токенхолдера.</p>
<h4>🔧 Основная функциональность:</h4>
<ul>
<li><strong>Назначение наследников:</strong> Токенхолдеры могут указать один или несколько наследников</li>
<li><strong>Распределение долей:</strong> Настройка процентного распределения токенов между наследниками</li>
<li><strong>Условия активации:</strong> Настройка условий для передачи токенов (смерть, недееспособность)</li>
<li><strong>Временные ограничения:</strong> Установка минимального периода владения токенами</li>
<li><strong>Множественные наследники:</strong> Поддержка сложных схем наследования</li>
<li><strong>Отзыв и изменение:</strong> Возможность изменения наследников в любое время</li>
</ul>
<h4>🏛 Юридические аспекты:</h4>
<ul>
<li><strong>Соответствие законам:</strong> Интеграция с юридическими системами наследования</li>
<li><strong>Документооборот:</strong> Автоматическое создание юридических документов</li>
<li><strong>Подтверждение смерти:</strong> Интеграция с государственными реестрами</li>
<li><strong>Споры и оспаривание:</strong> Механизмы разрешения споров о наследстве</li>
<li><strong>Налоговые обязательства:</strong> Автоматический расчет налогов на наследство</li>
</ul>
<h4>🔐 Безопасность и контроль:</h4>
<ul>
<li>Все изменения наследников требуют подтверждения через governance</li>
<li>Криптографическая защита данных о наследниках</li>
<li>Аудит всех операций наследования</li>
<li>Возможность экстренной блокировки в случае споров</li>
<li>Интеграция с системой идентификации для подтверждения личности</li>
</ul>
</div>
</div>
</div>
<!-- Архитектура модуля -->
<div class="module-architecture">
<div class="architecture-card">
<h3>🏗 Архитектура InheritanceModule</h3>
<div class="architecture-content">
<div class="architecture-diagram">
<div class="diagram-row">
<div class="diagram-item tokenholder">
<h5>👤 Токенхолдер</h5>
<ul>
<li>Назначает наследников</li>
<li>Устанавливает доли</li>
<li>Управляет условиями</li>
<li>Может отозвать</li>
</ul>
</div>
<div class="diagram-arrow"></div>
<div class="diagram-item inheritance">
<h5>🏛 InheritanceModule</h5>
<ul>
<li>Хранит данные наследников</li>
<li>Проверяет условия</li>
<li>Выполняет передачу</li>
<li>Ведет аудит</li>
</ul>
</div>
<div class="diagram-arrow"></div>
<div class="diagram-item heirs">
<h5>👥 Наследники</h5>
<ul>
<li>Получают токены</li>
<li>Подтверждают получение</li>
<li>Управляют наследством</li>
<li>Планируют налоги</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Типы наследования -->
<div class="inheritance-types">
<div class="types-card">
<h3>📊 Типы наследования</h3>
<div class="types-grid">
<div class="type-item">
<h4>👨👩👧👦 Семейное наследование</h4>
<p>Передача токенов членам семьи согласно традиционным схемам</p>
<ul>
<li>Супруг/супруга (50%)</li>
<li>Дети (равные доли)</li>
<li>Родители (при отсутствии детей)</li>
<li>Братья/сестры (при отсутствии родителей)</li>
</ul>
</div>
<div class="type-item">
<h4>🏢 Корпоративное наследование</h4>
<p>Передача токенов в рамках бизнес-структур и организаций</p>
<ul>
<li>Партнеры по бизнесу</li>
<li>Ключевые сотрудники</li>
<li>Дочерние компании</li>
<li>Благотворительные фонды</li>
</ul>
</div>
<div class="type-item">
<h4>🎯 Целевое наследование</h4>
<p>Передача токенов для достижения конкретных целей</p>
<ul>
<li>Образовательные учреждения</li>
<li>Исследовательские проекты</li>
<li>Экологические инициативы</li>
<li>Социальные программы</li>
</ul>
</div>
<div class="type-item">
<h4> Условное наследование</h4>
<p>Передача токенов при выполнении определенных условий</p>
<ul>
<li>Достижение определенного возраста</li>
<li>Завершение образования</li>
<li>Создание семьи</li>
<li>Достижение карьерных целей</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Примеры использования -->
<div class="usage-examples">
<div class="examples-card">
<h3>💡 Примеры использования</h3>
<div class="examples-content">
<div class="example-item">
<h4>👨👩👧👦 Семейное планирование</h4>
<div class="example-code">
<pre><code>// Назначение наследников для семьи
function setFamilyInheritance() {
setHeir(spouse, 50); // Супруг 50%
setHeir(son, 25); // Сын 25%
setHeir(daughter, 25); // Дочь 25%
setActivationCondition("death");
}</code></pre>
</div>
</div>
<div class="example-item">
<h4>🏢 Бизнес-преемственность</h4>
<div class="example-code">
<pre><code>// Передача бизнеса партнеру
function setBusinessInheritance() {
setHeir(businessPartner, 100); // Партнер 100%
setActivationCondition("death");
setTimeLock(365 days); // Минимум 1 год владения
}</code></pre>
</div>
</div>
<div class="example-item">
<h4>🎯 Благотворительное наследование</h4>
<div class="example-code">
<pre><code>// Передача в благотворительный фонд
function setCharityInheritance() {
setHeir(environmentalFund, 70); // Экологический фонд 70%
setHeir(educationFund, 30); // Образовательный фонд 30%
setActivationCondition("death");
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- Юридические аспекты -->
<div class="legal-aspects">
<div class="legal-card">
<h3> Юридические аспекты</h3>
<div class="legal-content">
<div class="legal-section">
<h4>📜 Соответствие законодательству</h4>
<ul>
<li><strong>Гражданский кодекс:</strong> Соответствие нормам наследования</li>
<li><strong>Налоговый кодекс:</strong> Правильный расчет налогов на наследство</li>
<li><strong>Семейный кодекс:</strong> Учет семейных обязательств</li>
<li><strong>Международное право:</strong> Наследование в разных юрисдикциях</li>
</ul>
</div>
<div class="legal-section">
<h4>🔍 Процедура подтверждения</h4>
<ul>
<li><strong>Свидетельство о смерти:</strong> Официальное подтверждение</li>
<li><strong>Медицинское заключение:</strong> При недееспособности</li>
<li><strong>Судебное решение:</strong> При спорах о наследстве</li>
<li><strong>Нотариальное заверение:</strong> Документов о наследниках</li>
</ul>
</div>
<div class="legal-section">
<h4>💰 Налоговые обязательства</h4>
<ul>
<li><strong>Налог на наследство:</strong> Автоматический расчет</li>
<li><strong>НДФЛ:</strong> При получении токенов</li>
<li><strong>Отчетность:</strong> Автоматическая подача деклараций</li>
<li><strong>Льготы:</strong> Учет налоговых льгот для наследников</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Статус разработки -->
<div class="development-status">
<div class="status-card">
<h3>🚧 Статус разработки</h3>
<div class="status-content">
<p><strong>InheritanceModule находится в стадии планирования.</strong></p>
<p>Модуль будет включать:</p>
<ul>
<li> Систему назначения наследников</li>
<li> Управление долями и условиями</li>
<li> Интеграцию с юридическими системами</li>
<li> Автоматическую передачу токенов</li>
<li> Налоговые расчеты</li>
<li> Аудит и мониторинг</li>
<li> Разрешение споров</li>
</ul>
<p><em>Модуль будет доступен в следующих обновлениях DLE.</em></p>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import api from '../../../api/axios';
// Props
const props = defineProps({
isAuthenticated: {
type: Boolean,
default: false
},
identities: {
type: Array,
default: () => []
},
tokenBalances: {
type: Object,
default: () => ({})
},
isLoadingTokens: {
type: Boolean,
default: false
}
});
// Emits
const emit = defineEmits(['auth-action-completed']);
// Router
const route = useRoute();
const router = useRouter();
// Состояние
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Получаем адрес DLE из URL
const dleAddress = computed(() => route.query.address);
// Загрузка данных DLE
const loadDleData = async () => {
if (!dleAddress.value) return;
try {
isLoadingDle.value = true;
const response = await api.post('/blockchain/read-dle-info', {
dleAddress: dleAddress.value
});
if (response.data.success) {
selectedDle.value = response.data.data;
}
} catch (error) {
console.error('Ошибка загрузки данных DLE:', error);
} finally {
isLoadingDle.value = false;
}
};
// Загружаем данные при монтировании
onMounted(() => {
loadDleData();
});
</script>
<style scoped>
.module-deploy-page {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.header-content h1 {
color: var(--color-primary);
font-size: 2.5rem;
margin: 0 0 10px 0;
}
.header-content p {
color: var(--color-grey-dark);
font-size: 1.1rem;
margin: 0 0 15px 0;
}
.dle-info {
display: flex;
gap: 15px;
align-items: center;
}
.dle-name {
font-weight: 600;
color: var(--color-primary);
}
.dle-address {
font-family: monospace;
color: var(--color-grey-dark);
font-size: 0.9rem;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
color: var(--color-grey-dark);
cursor: pointer;
padding: 5px;
}
.close-btn:hover {
color: var(--color-primary);
}
.module-description,
.module-architecture,
.inheritance-types,
.usage-examples,
.legal-aspects,
.development-status {
margin-bottom: 30px;
}
.description-card,
.architecture-card,
.types-card,
.examples-card,
.legal-card,
.status-card {
background: #f8f9fa;
padding: 25px;
border-radius: var(--radius-lg);
border: 1px solid #e9ecef;
}
.description-card h3,
.architecture-card h3,
.types-card h3,
.examples-card h3,
.legal-card h3,
.status-card h3 {
color: var(--color-primary);
margin: 0 0 20px 0;
}
.description-content h4 {
color: var(--color-grey-dark);
margin: 20px 0 10px 0;
}
.description-content ul {
margin: 10px 0;
padding-left: 20px;
}
.description-content li {
margin: 5px 0;
line-height: 1.5;
}
/* Архитектурная диаграмма */
.architecture-diagram {
margin: 20px 0;
}
.diagram-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 20px;
}
.diagram-item {
flex: 1;
padding: 20px;
border-radius: var(--radius-sm);
text-align: center;
min-height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
}
.diagram-item.tokenholder {
background: #e8f5e8;
border: 2px solid #4caf50;
}
.diagram-item.inheritance {
background: #fff3e0;
border: 2px solid #ff9800;
}
.diagram-item.heirs {
background: #f3e5f5;
border: 2px solid #9c27b0;
}
.diagram-item h5 {
margin: 0 0 15px 0;
font-weight: 600;
}
.diagram-item ul {
margin: 0;
padding: 0;
list-style: none;
font-size: 0.9rem;
}
.diagram-item li {
margin: 5px 0;
}
.diagram-arrow {
font-size: 2rem;
color: var(--color-primary);
font-weight: bold;
}
/* Типы наследования */
.types-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-top: 20px;
}
.type-item {
background: white;
padding: 20px;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.type-item h4 {
color: var(--color-primary);
margin: 0 0 10px 0;
}
.type-item p {
color: var(--color-grey-dark);
margin: 0 0 15px 0;
font-size: 0.9rem;
}
.type-item ul {
margin: 0;
padding-left: 20px;
font-size: 0.9rem;
}
.type-item li {
margin: 5px 0;
color: var(--color-grey-dark);
}
/* Примеры использования */
.examples-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.example-item {
background: white;
padding: 20px;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.example-item h4 {
color: var(--color-primary);
margin: 0 0 15px 0;
}
.example-code {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: var(--radius-sm);
padding: 15px;
overflow-x: auto;
}
.example-code pre {
margin: 0;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
color: #333;
}
.example-code code {
background: none;
padding: 0;
}
/* Юридические аспекты */
.legal-content {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
.legal-section {
background: white;
padding: 20px;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.legal-section h4 {
color: var(--color-primary);
margin: 0 0 15px 0;
}
.legal-section ul {
margin: 0;
padding-left: 20px;
}
.legal-section li {
margin: 8px 0;
line-height: 1.5;
color: var(--color-grey-dark);
}
/* Статус разработки */
.status-content {
background: white;
padding: 20px;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.status-content p {
margin: 0 0 15px 0;
line-height: 1.6;
}
.status-content ul {
margin: 15px 0;
padding-left: 20px;
}
.status-content li {
margin: 5px 0;
color: var(--color-grey-dark);
}
.status-content em {
color: var(--color-primary);
font-style: italic;
}
@media (max-width: 768px) {
.diagram-row {
flex-direction: column;
gap: 15px;
}
.diagram-arrow {
transform: rotate(90deg);
}
.types-grid {
grid-template-columns: 1fr;
}
.legal-content {
grid-template-columns: 1fr;
}
.dle-info {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
}
</style>

View File

@@ -1,485 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="module-deploy-page">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>🚀 Деплой MintModule</h1>
<p>Модуль для выпуска новых токенов DLE через governance</p>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
</div>
</div>
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
</div>
<!-- Описание модуля -->
<div class="module-description">
<div class="description-card">
<h3>📋 Описание MintModule</h3>
<div class="description-content">
<p><strong>MintModule</strong> - это модуль для управления выпуском новых токенов DLE через систему governance.</p>
<h4>🔧 Функциональность:</h4>
<ul>
<li><strong>Выпуск токенов:</strong> Создание дополнительных токенов DLE</li>
<li><strong>Governance:</strong> Все операции требуют голосования и кворума</li>
<li><strong>Безопасность:</strong> Контролируемый выпуск через коллективные решения</li>
<li><strong>Прозрачность:</strong> Все операции записываются в блокчейн</li>
</ul>
<h4> Важные особенности:</h4>
<ul>
<li>Выпуск токенов возможен только через предложения и голосование</li>
<li>Новые токены могут быть распределены между участниками или добавлены в казну DLE</li>
<li>Все операции требуют достижения кворума</li>
<li>История всех выпусков сохраняется в блокчейне</li>
</ul>
</div>
</div>
</div>
<!-- Форма деплоя -->
<div class="deploy-form-section">
<div class="form-header">
<h3> Настройки деплоя</h3>
<p>Настройте параметры для деплоя MintModule</p>
</div>
<form @submit.prevent="deployModule" class="deploy-form">
<div class="form-row">
<div class="form-group">
<label for="moduleName">Название модуля:</label>
<input
id="moduleName"
v-model="deployData.moduleName"
type="text"
placeholder="MintModule"
required
/>
</div>
<div class="form-group">
<label for="moduleVersion">Версия модуля:</label>
<input
id="moduleVersion"
v-model="deployData.moduleVersion"
type="text"
placeholder="1.0.0"
required
/>
</div>
</div>
<div class="form-group">
<label for="moduleDescription">Описание модуля:</label>
<textarea
id="moduleDescription"
v-model="deployData.moduleDescription"
placeholder="Модуль для выпуска новых токенов DLE через governance..."
rows="3"
required
></textarea>
</div>
<div class="form-group">
<label for="maxMintPerProposal">Максимальный выпуск за одно предложение:</label>
<input
id="maxMintPerProposal"
v-model="deployData.maxMintPerProposal"
type="number"
min="1"
step="1"
placeholder="1000000"
required
/>
<small class="form-help">Максимальное количество токенов, которое можно выпустить за одно предложение</small>
</div>
<div class="form-group">
<label for="mintCooldown">Кулдаун между выпусками (часы):</label>
<input
id="mintCooldown"
v-model="deployData.mintCooldown"
type="number"
min="0"
step="1"
placeholder="24"
required
/>
<small class="form-help">Минимальное время между успешными выпусками токенов</small>
</div>
<div class="form-group">
<label for="deployDescription">Описание предложения для деплоя:</label>
<textarea
id="deployDescription"
v-model="deployData.deployDescription"
placeholder="Предложение о деплое MintModule для управления выпуском токенов DLE..."
rows="3"
required
></textarea>
</div>
<div class="form-group">
<label for="votingDuration">Длительность голосования (часы):</label>
<input
id="votingDuration"
v-model="deployData.votingDuration"
type="number"
min="1"
max="168"
placeholder="24"
required
/>
<small class="form-help">Время для голосования по предложению деплоя (1-168 часов)</small>
</div>
<button type="submit" class="btn-primary" :disabled="isDeploying">
{{ isDeploying ? 'Деплой...' : 'Деплой MintModule' }}
</button>
<!-- Статус деплоя -->
<div v-if="deployStatus" class="deploy-status">
<p class="status-message">{{ deployStatus }}</p>
</div>
</form>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import api from '../../../api/axios';
// Props
const props = defineProps({
isAuthenticated: {
type: Boolean,
default: false
},
identities: {
type: Array,
default: () => []
},
tokenBalances: {
type: Object,
default: () => ({})
},
isLoadingTokens: {
type: Boolean,
default: false
}
});
// Emits
const emit = defineEmits(['auth-action-completed']);
// Router
const route = useRoute();
const router = useRouter();
// Состояние
const isDeploying = ref(false);
const deployStatus = ref('');
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Данные для деплоя
const deployData = ref({
moduleName: 'MintModule',
moduleVersion: '1.0.0',
moduleDescription: 'Модуль для выпуска новых токенов DLE через governance',
maxMintPerProposal: 1000000,
mintCooldown: 24,
deployDescription: 'Предложение о деплое MintModule для управления выпуском токенов DLE',
votingDuration: 24
});
// Получаем адрес DLE из URL
const dleAddress = computed(() => route.query.address);
// Загрузка данных DLE
const loadDleData = async () => {
if (!dleAddress.value) return;
try {
isLoadingDle.value = true;
const response = await api.post('/blockchain/read-dle-info', {
dleAddress: dleAddress.value
});
if (response.data.success) {
selectedDle.value = response.data.data;
}
} catch (error) {
console.error('Ошибка загрузки данных DLE:', error);
} finally {
isLoadingDle.value = false;
}
};
// Функция деплоя модуля
const deployModule = async () => {
if (isDeploying.value) return;
try {
isDeploying.value = true;
deployStatus.value = 'Подготовка к деплою...';
// Здесь будет логика деплоя модуля
console.log('Деплой MintModule:', deployData.value);
// Временная заглушка
await new Promise(resolve => setTimeout(resolve, 2000));
deployStatus.value = 'Модуль успешно развернут!';
// Очищаем статус через 3 секунды
setTimeout(() => {
deployStatus.value = '';
}, 3000);
alert('MintModule успешно развернут!');
} catch (error) {
console.error('Ошибка деплоя модуля:', error);
deployStatus.value = 'Ошибка деплоя модуля';
setTimeout(() => {
deployStatus.value = '';
}, 3000);
alert('Ошибка при деплое модуля');
} finally {
isDeploying.value = false;
}
};
// Загружаем данные при монтировании
onMounted(() => {
loadDleData();
});
</script>
<style scoped>
.module-deploy-page {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.header-content h1 {
color: var(--color-primary);
font-size: 2.5rem;
margin: 0 0 10px 0;
}
.header-content p {
color: var(--color-grey-dark);
font-size: 1.1rem;
margin: 0 0 15px 0;
}
.dle-info {
display: flex;
gap: 15px;
align-items: center;
}
.dle-name {
font-weight: 600;
color: var(--color-primary);
}
.dle-address {
font-family: monospace;
color: var(--color-grey-dark);
font-size: 0.9rem;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
color: var(--color-grey-dark);
cursor: pointer;
padding: 5px;
}
.close-btn:hover {
color: var(--color-primary);
}
.module-description {
margin-bottom: 30px;
}
.description-card {
background: #f8f9fa;
padding: 25px;
border-radius: var(--radius-lg);
border: 1px solid #e9ecef;
}
.description-card h3 {
color: var(--color-primary);
margin: 0 0 20px 0;
}
.description-content h4 {
color: var(--color-grey-dark);
margin: 20px 0 10px 0;
}
.description-content ul {
margin: 10px 0;
padding-left: 20px;
}
.description-content li {
margin: 5px 0;
line-height: 1.5;
}
.deploy-form-section {
background: white;
padding: 30px;
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.form-header {
margin-bottom: 25px;
}
.form-header h3 {
color: var(--color-primary);
margin: 0 0 10px 0;
}
.form-header p {
color: var(--color-grey-dark);
margin: 0;
}
.deploy-form {
display: flex;
flex-direction: column;
gap: 20px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.form-group label {
font-weight: 600;
color: var(--color-grey-dark);
}
.form-group input,
.form-group textarea {
padding: 12px;
border: 1px solid #ddd;
border-radius: var(--radius-sm);
font-size: 1rem;
}
.form-group textarea {
resize: vertical;
min-height: 80px;
}
.form-help {
font-size: 0.85rem;
color: var(--color-grey-dark);
font-style: italic;
}
.btn-primary {
background: var(--color-primary);
color: white;
border: none;
padding: 15px 30px;
border-radius: var(--radius-sm);
font-size: 1.1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s;
}
.btn-primary:hover:not(:disabled) {
background: var(--color-primary-dark);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.deploy-status {
margin-top: 20px;
padding: 15px;
background: #e8f5e8;
border-radius: var(--radius-sm);
border-left: 4px solid #28a745;
}
.status-message {
margin: 0;
font-weight: 600;
color: #28a745;
}
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.dle-info {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
}
</style>

View File

@@ -1,205 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="module-deploy-form">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Деплой пользовательского модуля</h1>
<p>Создание и деплой кастомного модуля для DLE</p>
</div>
<button class="close-btn" @click="router.push('/management/modules')">×</button>
</div>
<!-- Информация о пользовательских модулях -->
<div class="module-info">
<div class="info-card">
<h3>🔧 Пользовательские модули</h3>
<div class="info-grid">
<div class="info-item">
<strong>Назначение:</strong> Расширение функциональности DLE
</div>
<div class="info-item">
<strong>Возможности:</strong> Любая кастомная логика, интеграции, API
</div>
<div class="info-item">
<strong>Безопасность:</strong> Проверка через голосование токен-холдеров
</div>
</div>
</div>
</div>
<!-- Форма деплоя будет добавлена позже -->
<div class="deploy-form-placeholder">
<div class="placeholder-content">
<h3>🚧 Форма деплоя в разработке</h3>
<p>Здесь будет универсальная форма для деплоя пользовательских модулей</p>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
// Определяем props
const props = defineProps({
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false }
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Состояние
const isLoading = ref(false);
// Инициализация
onMounted(() => {
console.log('[ModuleDeployFormView] Страница загружена');
});
</script>
<style scoped>
.module-deploy-form {
padding: 20px;
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-top: 20px;
margin-bottom: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2rem;
margin: 0;
}
.page-header p {
margin: 10px 0 0 0;
color: #666;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Информация о модуле */
.module-info {
margin-bottom: 30px;
}
.info-card {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
}
.info-card h3 {
margin: 0 0 15px 0;
color: var(--color-primary);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.info-item strong {
color: var(--color-primary);
}
/* Плейсхолдер для формы */
.deploy-form-placeholder {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 40px;
text-align: center;
border: 2px dashed #dee2e6;
}
.placeholder-content h3 {
color: var(--color-primary);
margin-bottom: 10px;
}
.placeholder-content p {
color: #666;
margin: 0;
}
/* Адаптивность */
@media (max-width: 768px) {
.info-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
}
</style>

View File

@@ -1,584 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="module-deploy-page">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>🔗 Деплой OracleModule</h1>
<p>Модуль для интеграции с внешними данными и автоматизации DLE</p>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
</div>
</div>
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
</div>
<!-- Описание модуля -->
<div class="module-description">
<div class="description-card">
<h3>📋 Описание OracleModule</h3>
<div class="description-content">
<p><strong>OracleModule</strong> - это модуль для интеграции DLE с внешними источниками данных и автоматизации процессов на основе реальных событий.</p>
<h4>🔧 Основная функциональность:</h4>
<ul>
<li><strong>Интеграция с IoT:</strong> Получение данных от датчиков, производственных линий, оборудования</li>
<li><strong>API интеграция:</strong> Подключение к внешним системам, ERP, CRM, аналитическим платформам</li>
<li><strong>Автоматические триггеры:</strong> Создание предложений на основе внешних событий</li>
<li><strong>Валидация данных:</strong> Проверка достоверности полученной информации</li>
<li><strong>Множественные оракулы:</strong> Подтверждение данных от нескольких источников</li>
</ul>
<h4>🏭 Примеры применения:</h4>
<ul>
<li><strong>Производственные токены:</strong> Автоматический выпуск токенов за завершение партий продукции</li>
<li><strong>Качественные бонусы:</strong> Токены за высокое качество продукции на основе данных датчиков</li>
<li><strong>Экологические токены:</strong> Вознаграждения за снижение энергопотребления и выбросов</li>
<li><strong>Инновационные токены:</strong> Токены за внедрение новых технологий и процессов</li>
<li><strong>Рыночные корректировки:</strong> Автоматическая адаптация стратегии на основе рыночных данных</li>
</ul>
<h4>🔐 Безопасность и контроль:</h4>
<ul>
<li>Все оракулы должны быть авторизованы через governance</li>
<li>Данные валидируются перед обработкой</li>
<li>Критические решения требуют множественного подтверждения</li>
<li>История всех оракульных данных сохраняется в блокчейне</li>
<li>Возможность отключения оракулов в экстренных случаях</li>
</ul>
</div>
</div>
</div>
<!-- Архитектура модуля -->
<div class="module-architecture">
<div class="architecture-card">
<h3>🏗 Архитектура OracleModule</h3>
<div class="architecture-content">
<div class="architecture-diagram">
<div class="diagram-row">
<div class="diagram-item external">
<h5>🌐 Внешние источники</h5>
<ul>
<li>IoT датчики</li>
<li>Производственные линии</li>
<li>ERP/CRM системы</li>
<li>Аналитические платформы</li>
<li>Рыночные данные</li>
</ul>
</div>
<div class="diagram-arrow"></div>
<div class="diagram-item oracle">
<h5>🔗 OracleModule</h5>
<ul>
<li>Получение данных</li>
<li>Валидация</li>
<li>Обработка</li>
<li>Создание предложений</li>
</ul>
</div>
<div class="diagram-arrow"></div>
<div class="diagram-item dle">
<h5>🏛 DLE Governance</h5>
<ul>
<li>Предложения</li>
<li>Голосование</li>
<li>Исполнение</li>
<li>Выпуск токенов</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Типы оракулов -->
<div class="oracle-types">
<div class="types-card">
<h3>📊 Типы оракулов</h3>
<div class="types-grid">
<div class="type-item">
<h4>🏭 Производственные оракулы</h4>
<p>Данные от производственных линий, оборудования, датчиков качества</p>
<ul>
<li>Завершение партий продукции</li>
<li>Показатели качества</li>
<li>Энергопотребление</li>
<li>Время простоя</li>
</ul>
</div>
<div class="type-item">
<h4>📈 Рыночные оракулы</h4>
<p>Рыночные данные, цены, спрос, конкуренция</p>
<ul>
<li>Цены на сырье</li>
<li>Спрос на продукцию</li>
<li>Конкурентные данные</li>
<li>Экономические индикаторы</li>
</ul>
</div>
<div class="type-item">
<h4>🌱 Экологические оракулы</h4>
<p>Данные об экологическом воздействии и устойчивости</p>
<ul>
<li>Выбросы CO2</li>
<li>Потребление энергии</li>
<li>Переработка отходов</li>
<li>Использование возобновляемых ресурсов</li>
</ul>
</div>
<div class="type-item">
<h4>💼 Бизнес-оракулы</h4>
<p>Бизнес-метрики, продажи, удовлетворенность клиентов</p>
<ul>
<li>Объемы продаж</li>
<li>Удовлетворенность клиентов</li>
<li>Эффективность процессов</li>
<li>Финансовые показатели</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Примеры использования -->
<div class="usage-examples">
<div class="examples-card">
<h3>💡 Примеры использования</h3>
<div class="examples-content">
<div class="example-item">
<h4>🏭 Автоматические производственные токены</h4>
<div class="example-code">
<pre><code>// При завершении партии продукции
function onBatchCompleted(uint256 quantity, uint256 quality) {
uint256 tokens = quantity * quality * 10;
createMintProposal(tokens, "Production reward");
}</code></pre>
</div>
</div>
<div class="example-item">
<h4>🌱 Экологические бонусы</h4>
<div class="example-code">
<pre><code>// При снижении энергопотребления
function onEnergyReduction(uint256 savedEnergy) {
uint256 tokens = savedEnergy * 5;
createMintProposal(tokens, "Energy efficiency bonus");
}</code></pre>
</div>
</div>
<div class="example-item">
<h4>📈 Рыночная адаптация</h4>
<div class="example-code">
<pre><code>// При изменении рыночных условий
function onMarketChange(uint256 priceChange) {
if (priceChange > 10) {
createStrategyProposal("Increase production");
}
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- Статус разработки -->
<div class="development-status">
<div class="status-card">
<h3>🚧 Статус разработки</h3>
<div class="status-content">
<p><strong>OracleModule находится в стадии планирования.</strong></p>
<p>Модуль будет включать:</p>
<ul>
<li> Систему авторизации оракулов</li>
<li> Валидацию и обработку данных</li>
<li> Интеграцию с IoT и API</li>
<li> Автоматические триггеры</li>
<li> Множественное подтверждение данных</li>
<li> Аудит и мониторинг</li>
</ul>
<p><em>Модуль будет доступен в следующих обновлениях DLE.</em></p>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import api from '../../../api/axios';
// Props
const props = defineProps({
isAuthenticated: {
type: Boolean,
default: false
},
identities: {
type: Array,
default: () => []
},
tokenBalances: {
type: Object,
default: () => ({})
},
isLoadingTokens: {
type: Boolean,
default: false
}
});
// Emits
const emit = defineEmits(['auth-action-completed']);
// Router
const route = useRoute();
const router = useRouter();
// Состояние
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Получаем адрес DLE из URL
const dleAddress = computed(() => route.query.address);
// Загрузка данных DLE
const loadDleData = async () => {
if (!dleAddress.value) return;
try {
isLoadingDle.value = true;
const response = await api.post('/blockchain/read-dle-info', {
dleAddress: dleAddress.value
});
if (response.data.success) {
selectedDle.value = response.data.data;
}
} catch (error) {
console.error('Ошибка загрузки данных DLE:', error);
} finally {
isLoadingDle.value = false;
}
};
// Загружаем данные при монтировании
onMounted(() => {
loadDleData();
});
</script>
<style scoped>
.module-deploy-page {
padding: 20px;
max-width: 1200px;
margin: 0 auto;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.header-content h1 {
color: var(--color-primary);
font-size: 2.5rem;
margin: 0 0 10px 0;
}
.header-content p {
color: var(--color-grey-dark);
font-size: 1.1rem;
margin: 0 0 15px 0;
}
.dle-info {
display: flex;
gap: 15px;
align-items: center;
}
.dle-name {
font-weight: 600;
color: var(--color-primary);
}
.dle-address {
font-family: monospace;
color: var(--color-grey-dark);
font-size: 0.9rem;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
color: var(--color-grey-dark);
cursor: pointer;
padding: 5px;
}
.close-btn:hover {
color: var(--color-primary);
}
.module-description,
.module-architecture,
.oracle-types,
.usage-examples,
.development-status {
margin-bottom: 30px;
}
.description-card,
.architecture-card,
.types-card,
.examples-card,
.status-card {
background: #f8f9fa;
padding: 25px;
border-radius: var(--radius-lg);
border: 1px solid #e9ecef;
}
.description-card h3,
.architecture-card h3,
.types-card h3,
.examples-card h3,
.status-card h3 {
color: var(--color-primary);
margin: 0 0 20px 0;
}
.description-content h4 {
color: var(--color-grey-dark);
margin: 20px 0 10px 0;
}
.description-content ul {
margin: 10px 0;
padding-left: 20px;
}
.description-content li {
margin: 5px 0;
line-height: 1.5;
}
/* Архитектурная диаграмма */
.architecture-diagram {
margin: 20px 0;
}
.diagram-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
margin-bottom: 20px;
}
.diagram-item {
flex: 1;
padding: 20px;
border-radius: var(--radius-sm);
text-align: center;
min-height: 150px;
display: flex;
flex-direction: column;
justify-content: center;
}
.diagram-item.external {
background: #e3f2fd;
border: 2px solid #2196f3;
}
.diagram-item.oracle {
background: #f3e5f5;
border: 2px solid #9c27b0;
}
.diagram-item.dle {
background: #e8f5e8;
border: 2px solid #4caf50;
}
.diagram-item h5 {
margin: 0 0 15px 0;
font-weight: 600;
}
.diagram-item ul {
margin: 0;
padding: 0;
list-style: none;
font-size: 0.9rem;
}
.diagram-item li {
margin: 5px 0;
}
.diagram-arrow {
font-size: 2rem;
color: var(--color-primary);
font-weight: bold;
}
/* Типы оракулов */
.types-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-top: 20px;
}
.type-item {
background: white;
padding: 20px;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.type-item h4 {
color: var(--color-primary);
margin: 0 0 10px 0;
}
.type-item p {
color: var(--color-grey-dark);
margin: 0 0 15px 0;
font-size: 0.9rem;
}
.type-item ul {
margin: 0;
padding-left: 20px;
font-size: 0.9rem;
}
.type-item li {
margin: 5px 0;
color: var(--color-grey-dark);
}
/* Примеры использования */
.examples-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.example-item {
background: white;
padding: 20px;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.example-item h4 {
color: var(--color-primary);
margin: 0 0 15px 0;
}
.example-code {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: var(--radius-sm);
padding: 15px;
overflow-x: auto;
}
.example-code pre {
margin: 0;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
color: #333;
}
.example-code code {
background: none;
padding: 0;
}
/* Статус разработки */
.status-content {
background: white;
padding: 20px;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.status-content p {
margin: 0 0 15px 0;
line-height: 1.6;
}
.status-content ul {
margin: 15px 0;
padding-left: 20px;
}
.status-content li {
margin: 5px 0;
color: var(--color-grey-dark);
}
.status-content em {
color: var(--color-primary);
font-style: italic;
}
@media (max-width: 768px) {
.diagram-row {
flex-direction: column;
gap: 15px;
}
.diagram-arrow {
transform: rotate(90deg);
}
.types-grid {
grid-template-columns: 1fr;
}
.dle-info {
flex-direction: column;
align-items: flex-start;
gap: 5px;
}
}
</style>

View File

@@ -1,673 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="timelock-module-deploy">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Деплой TimelockModule</h1>
<p>Задержки исполнения - безопасность критических операций через таймлоки</p>
<p v-if="dleAddress" class="dle-address">
<strong>DLE:</strong> {{ dleAddress }}
</p>
</div>
<button class="close-btn" @click="router.push('/management/modules')">×</button>
</div>
<!-- Информация о модуле -->
<div class="module-info">
<div class="info-card">
<h3> TimelockModule</h3>
<div class="info-grid">
<div class="info-item">
<strong>Назначение:</strong> Безопасность критических операций
</div>
<div class="info-item">
<strong>Функции:</strong> Настраиваемые таймлоки, отмена предложений, аудит
</div>
<div class="info-item">
<strong>Безопасность:</strong> Задержки исполнения для защиты от атак
</div>
</div>
</div>
</div>
<!-- Форма деплоя модуля администратором -->
<div v-if="canManageSettings" class="deploy-form">
<div class="form-header">
<h3>🔧 Деплой TimelockModule администратором</h3>
<p>Администратор деплоит модуль, затем создает предложение для добавления в DLE</p>
</div>
<div class="form-content">
<!-- Информация о сетях -->
<div class="networks-info">
<h4>📡 Сети для деплоя:</h4>
<div class="networks-list">
<div class="network-item">
<span class="network-name">Sepolia</span>
<span class="network-chain-id">Chain ID: 11155111</span>
</div>
<div class="network-item">
<span class="network-name">Holesky</span>
<span class="network-chain-id">Chain ID: 17000</span>
</div>
<div class="network-item">
<span class="network-name">Arbitrum Sepolia</span>
<span class="network-chain-id">Chain ID: 421614</span>
</div>
<div class="network-item">
<span class="network-name">Base Sepolia</span>
<span class="network-chain-id">Chain ID: 84532</span>
</div>
</div>
</div>
<!-- Настройки модуля -->
<div class="module-settings">
<h4> Настройки TimelockModule:</h4>
<div class="settings-form">
<!-- Поля администратора -->
<div class="admin-section">
<h5>🔐 Настройки администратора:</h5>
<div class="form-row">
<div class="form-group">
<label for="adminPrivateKey">Приватный ключ администратора:</label>
<input
type="password"
id="adminPrivateKey"
v-model="moduleSettings.adminPrivateKey"
class="form-control"
placeholder="0x..."
required
>
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
</div>
<div class="form-group">
<label for="etherscanApiKey">Etherscan API ключ:</label>
<input
type="text"
id="etherscanApiKey"
v-model="moduleSettings.etherscanApiKey"
class="form-control"
placeholder="YourAPIKey..."
>
<small class="form-help">API ключ для автоматической верификации контрактов</small>
</div>
</div>
</div>
<div class="simple-info">
<h5>📋 Информация о TimelockModule:</h5>
<div class="info-text">
<p><strong>TimelockModule</strong> будет задеплоен с настройками по умолчанию:</p>
<ul>
<li> Стандартная задержка: 2 дня</li>
<li> Экстренная задержка: 30 минут</li>
<li> Автоматическое исполнение операций</li>
<li> Готовые настройки безопасности</li>
</ul>
<p><em>После деплоя настройки можно будет изменить через governance.</em></p>
</div>
</div>
</div>
</div>
<!-- Кнопка деплоя -->
<div class="deploy-actions">
<button
class="btn btn-primary btn-large deploy-module"
@click="deployTimelockModule"
:disabled="isDeploying || !dleAddress || !isFormValid"
>
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
{{ isDeploying ? 'Деплой модуля...' : 'Деплой TimelockModule' }}
</button>
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
<i class="fas fa-exclamation-triangle"></i>
<span>Заполните приватный ключ и API ключ для деплоя</span>
</div>
<div v-if="deploymentProgress" class="deployment-progress">
<div class="progress-info">
<span>{{ deploymentProgress.message }}</span>
<span class="progress-percentage">{{ deploymentProgress.percentage }}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: deploymentProgress.percentage + '%' }"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Сообщение для пользователей без прав доступа -->
<div v-if="!canManageSettings" class="no-access-message">
<div class="message-content">
<h3>🔒 Нет прав доступа</h3>
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
<button class="btn btn-secondary" @click="router.push('/management/modules')">
Вернуться к модулям
</button>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import { usePermissions } from '@/composables/usePermissions';
// Определяем props
const props = defineProps({
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false }
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
const { canEdit, canManageSettings } = usePermissions();
// Состояние
const isLoading = ref(false);
const dleAddress = ref(route.query.address || null);
const isDeploying = ref(false);
const deploymentProgress = ref(null);
// Настройки модуля
const moduleSettings = ref({
// Поля администратора
adminPrivateKey: '',
etherscanApiKey: ''
});
// Проверка валидности формы
const isFormValid = computed(() => {
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
});
// Функция деплоя TimelockModule
async function deployTimelockModule() {
if (!canManageSettings.value) {
alert('У вас нет прав для деплоя смарт-контрактов');
return;
}
try {
isDeploying.value = true;
deploymentProgress.value = {
message: 'Инициализация деплоя...',
percentage: 0
};
console.log('[TimelockModuleDeployView] Начинаем деплой TimelockModule для DLE:', dleAddress.value);
// Вызываем API для деплоя модуля администратором
const response = await fetch('/api/dle-modules/deploy-timelock-admin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
dleAddress: dleAddress.value,
moduleType: 'timelock',
adminPrivateKey: moduleSettings.value.adminPrivateKey,
etherscanApiKey: moduleSettings.value.etherscanApiKey,
settings: {
// Используем настройки по умолчанию
useDefaultSettings: true
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
console.log('[TimelockModuleDeployView] Деплой успешно запущен:', result);
// Обновляем прогресс
deploymentProgress.value = {
message: 'Деплой запущен успешно! Проверьте логи для отслеживания прогресса.',
percentage: 100
};
// Показываем детальную информацию о деплое
const deployInfo = result.data || {};
const deployedAddresses = deployInfo.addresses || [];
let successMessage = '✅ TimelockModule успешно задеплоен!\n\n';
successMessage += `📊 Детали деплоя:\n`;
successMessage += `• DLE: ${dleAddress.value}\n`;
successMessage += `• Тип модуля: TimelockModule\n`;
successMessage += `• Адрес модуля: ${deployInfo.moduleAddress || 'Не указан'}\n`;
if (deployedAddresses.length > 0) {
successMessage += `\n🌐 Задеплоенные адреса:\n`;
deployedAddresses.forEach((addr, index) => {
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
});
}
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
alert(successMessage);
// Перенаправляем обратно к модулям
setTimeout(() => {
router.push(`/management/modules?address=${dleAddress.value}`);
}, 3000);
} else {
throw new Error(result.error || 'Неизвестная ошибка');
}
} catch (error) {
console.error('[TimelockModuleDeployView] Ошибка деплоя:', error);
alert('❌ Ошибка деплоя: ' + error.message);
deploymentProgress.value = {
message: 'Ошибка деплоя: ' + error.message,
percentage: 0
};
} finally {
isDeploying.value = false;
}
}
// Инициализация
onMounted(() => {
console.log('[TimelockModuleDeployView] Страница загружена');
});
</script>
<style scoped>
.timelock-module-deploy {
padding: 20px;
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-top: 20px;
margin-bottom: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2rem;
margin: 0;
}
.page-header p {
margin: 10px 0 0 0;
color: #666;
}
.dle-address {
margin-top: 10px !important;
font-family: monospace;
background: #f8f9fa;
padding: 8px 12px;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Форма деплоя */
.deploy-form {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
margin-bottom: 30px;
border: 1px solid #e9ecef;
}
.form-header h3 {
margin: 0 0 10px 0;
color: var(--color-primary);
}
.form-header p {
margin: 0 0 20px 0;
color: #666;
}
.networks-info,
.module-settings {
margin-bottom: 20px;
padding: 15px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.settings-form {
margin-top: 15px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: var(--radius-sm);
font-size: 14px;
transition: border-color 0.2s;
}
.form-control:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.1);
}
.form-help {
display: block;
margin-top: 5px;
font-size: 12px;
color: #666;
line-height: 1.4;
}
/* Секция администратора */
.admin-section {
margin-bottom: 20px;
padding: 20px;
background: #fff3cd;
border-radius: var(--radius-sm);
border: 1px solid #ffeaa7;
}
.admin-section h5 {
margin: 0 0 15px 0;
color: #856404;
font-size: 1.1rem;
font-weight: 600;
}
/* Дополнительные настройки */
.advanced-settings {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.advanced-settings h5 {
margin: 0 0 15px 0;
color: var(--color-primary);
font-size: 1.1rem;
font-weight: 600;
}
.advanced-settings .form-row {
margin-bottom: 15px;
}
.advanced-settings .form-group {
margin-bottom: 15px;
}
.advanced-settings .form-group:last-child {
margin-bottom: 0;
}
.deploy-actions {
text-align: center;
margin-top: 20px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-large {
padding: 16px 32px;
font-size: 18px;
}
.deployment-progress {
margin-top: 20px;
padding: 15px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.progress-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.progress-percentage {
font-weight: 600;
color: var(--color-primary);
}
.progress-bar {
width: 100%;
height: 8px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-dark));
transition: width 0.3s ease;
}
/* Информация о модуле */
.module-info {
margin-bottom: 30px;
}
.info-card {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
}
.info-card h3 {
margin: 0 0 15px 0;
color: var(--color-primary);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.info-item strong {
color: var(--color-primary);
}
/* Плейсхолдер для формы */
.deploy-form-placeholder {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 40px;
text-align: center;
border: 2px dashed #dee2e6;
}
.placeholder-content h3 {
color: var(--color-primary);
margin-bottom: 10px;
}
.placeholder-content p {
color: #666;
margin: 0;
}
/* Сообщение об отсутствии прав доступа */
.no-access-message {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-md);
padding: 30px;
margin: 20px 0;
text-align: center;
}
.message-content h3 {
color: #856404;
margin-bottom: 15px;
font-size: 1.4em;
}
.message-content p {
color: #856404;
margin-bottom: 20px;
font-size: 1.1em;
line-height: 1.5;
}
.message-content .btn {
background: #6c757d;
color: white;
border: none;
border-radius: var(--radius-sm);
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.message-content .btn:hover {
background: #5a6268;
}
/* Адаптивность */
@media (max-width: 768px) {
.info-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
}
</style>

View File

@@ -1,886 +0,0 @@
<!--
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
-->
<template>
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="treasury-module-deploy">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Деплой TreasuryModule</h1>
<p>Казначейство DLE - управление финансами, депозиты, выводы, дивиденды</p>
<p v-if="dleAddress" class="dle-address">
<strong>DLE:</strong> {{ dleAddress }}
</p>
</div>
<button class="close-btn" @click="router.push('/management/modules')">×</button>
</div>
<!-- Информация о модуле -->
<div class="module-info">
<div class="info-card">
<h3>💰 TreasuryModule</h3>
<div class="info-grid">
<div class="info-item">
<strong>Назначение:</strong> Управление финансами DLE
</div>
<div class="info-item">
<strong>Функции:</strong> Депозиты, выводы, дивиденды, бюджетирование
</div>
<div class="info-item">
<strong>Безопасность:</strong> Все операции через голосование
</div>
</div>
</div>
</div>
<!-- Форма деплоя модуля администратором -->
<div v-if="canManageSettings" class="deploy-form">
<div class="form-header">
<h3>🌐 Деплой TreasuryModule во всех сетях</h3>
<p>Администратор деплоит модуль во всех 4 сетях одновременно, затем создает предложение для добавления в DLE</p>
</div>
<div class="form-content">
<!-- Информация о сетях -->
<div class="networks-info">
<h4>📡 Сети для деплоя:</h4>
<div class="networks-list">
<div class="network-item">
<span class="network-name">Sepolia</span>
<span class="network-chain-id">Chain ID: 11155111</span>
</div>
<div class="network-item">
<span class="network-name">Holesky</span>
<span class="network-chain-id">Chain ID: 17000</span>
</div>
<div class="network-item">
<span class="network-name">Arbitrum Sepolia</span>
<span class="network-chain-id">Chain ID: 421614</span>
</div>
<div class="network-item">
<span class="network-name">Base Sepolia</span>
<span class="network-chain-id">Chain ID: 84532</span>
</div>
</div>
</div>
<!-- Настройки модуля -->
<div class="module-settings">
<h4> Настройки TreasuryModule:</h4>
<div class="settings-form">
<!-- Поля администратора -->
<div class="admin-section">
<h5>🔐 Настройки администратора:</h5>
<div class="form-row">
<div class="form-group">
<label for="adminPrivateKey">Приватный ключ администратора:</label>
<input
type="password"
id="adminPrivateKey"
v-model="moduleSettings.adminPrivateKey"
class="form-control"
placeholder="0x..."
required
>
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
</div>
<div class="form-group">
<label for="etherscanApiKey">Etherscan API ключ:</label>
<input
type="text"
id="etherscanApiKey"
v-model="moduleSettings.etherscanApiKey"
class="form-control"
placeholder="YourAPIKey..."
>
<small class="form-help">API ключ для автоматической верификации контрактов</small>
</div>
</div>
</div>
<div class="simple-info">
<h5>📋 Информация о TreasuryModule:</h5>
<div class="info-text">
<p><strong>TreasuryModule</strong> будет задеплоен с настройками по умолчанию:</p>
<ul>
<li> Поддержка ETH и основных ERC20 токенов</li>
<li> Стандартные задержки для безопасности</li>
<li> Автоматическая настройка для всех сетей</li>
<li> Готовые настройки безопасности</li>
</ul>
<p><em>После деплоя настройки можно будет изменить через governance.</em></p>
</div>
</div>
</div>
</div>
<!-- Кнопка деплоя -->
<div class="deploy-actions">
<button
class="btn btn-primary btn-large deploy-module"
@click="deployTreasuryModule"
:disabled="isDeploying || !dleAddress || !isFormValid"
>
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
{{ isDeploying ? 'Деплой во всех сетях...' : 'Деплой TreasuryModule во всех сетях' }}
</button>
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
<i class="fas fa-exclamation-triangle"></i>
<span>Заполните приватный ключ и API ключ для деплоя</span>
</div>
<div v-if="deploymentProgress" class="deployment-progress">
<div class="progress-info">
<span>{{ deploymentProgress.message }}</span>
<span class="progress-percentage">{{ deploymentProgress.percentage }}%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" :style="{ width: deploymentProgress.percentage + '%' }"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Сообщение для пользователей без прав доступа -->
<div v-if="!canManageSettings" class="no-access-message">
<div class="message-content">
<h3>🔒 Нет прав доступа</h3>
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
<button class="btn btn-secondary" @click="router.push('/management/modules')">
Вернуться к модулям
</button>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import { usePermissions } from '@/composables/usePermissions';
// Определяем props
const props = defineProps({
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false }
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
const { canEdit, canManageSettings } = usePermissions();
// Состояние
const isLoading = ref(false);
const dleAddress = ref(route.query.address || null);
const isDeploying = ref(false);
const deploymentProgress = ref(null);
// Настройки модуля
const moduleSettings = ref({
// Поля администратора
adminPrivateKey: '',
etherscanApiKey: ''
});
// Проверка валидности формы
const isFormValid = computed(() => {
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
});
// Функция деплоя TreasuryModule
async function deployTreasuryModule() {
if (!canManageSettings.value) {
alert('У вас нет прав для деплоя смарт-контрактов');
return;
}
try {
isDeploying.value = true;
deploymentProgress.value = {
message: 'Инициализация деплоя...',
percentage: 0
};
console.log('[TreasuryModuleDeployView] Начинаем деплой TreasuryModule для DLE:', dleAddress.value);
// Вызываем API для деплоя модуля администратором
const response = await fetch('/api/dle-modules/deploy-treasury-admin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
dleAddress: dleAddress.value,
moduleType: 'treasury',
adminPrivateKey: moduleSettings.value.adminPrivateKey,
etherscanApiKey: moduleSettings.value.etherscanApiKey,
settings: {
// Используем настройки по умолчанию
useDefaultSettings: true
}
})
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
console.log('[TreasuryModuleDeployView] Деплой успешно запущен:', result);
// Обновляем прогресс
deploymentProgress.value = {
message: 'Деплой запущен успешно! Проверьте логи для отслеживания прогресса.',
percentage: 100
};
// Показываем детальную информацию о деплое
const deployInfo = result.data || {};
const deployedAddresses = deployInfo.addresses || [];
let successMessage = '✅ TreasuryModule успешно задеплоен во всех сетях!\n\n';
successMessage += `📊 Детали деплоя:\n`;
successMessage += `• DLE: ${dleAddress.value}\n`;
successMessage += `• Тип модуля: TreasuryModule\n`;
successMessage += `• Сети: Sepolia, Holesky, Arbitrum Sepolia, Base Sepolia\n`;
if (deployedAddresses.length > 0) {
successMessage += `\n🌐 Задеплоенные адреса:\n`;
deployedAddresses.forEach((addr, index) => {
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
});
}
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
alert(successMessage);
// Перенаправляем обратно к модулям
setTimeout(() => {
router.push(`/management/modules?address=${dleAddress.value}`);
}, 3000);
} else {
throw new Error(result.error || 'Неизвестная ошибка');
}
} catch (error) {
console.error('[TreasuryModuleDeployView] Ошибка деплоя:', error);
alert('❌ Ошибка деплоя: ' + error.message);
deploymentProgress.value = {
message: 'Ошибка деплоя: ' + error.message,
percentage: 0
};
} finally {
isDeploying.value = false;
}
}
// Инициализация
onMounted(() => {
console.log('[TreasuryModuleDeployView] Страница загружена');
});
</script>
<style scoped>
.treasury-module-deploy {
padding: 20px;
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-top: 20px;
margin-bottom: 20px;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2rem;
margin: 0;
}
.page-header p {
margin: 10px 0 0 0;
color: #666;
}
.dle-address {
margin-top: 10px !important;
font-family: monospace;
background: #f8f9fa;
padding: 8px 12px;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Форма деплоя */
.deploy-form {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
margin-bottom: 30px;
border: 1px solid #e9ecef;
}
.form-header h3 {
margin: 0 0 10px 0;
color: var(--color-primary);
}
.form-header p {
margin: 0 0 20px 0;
color: #666;
}
.networks-info,
.module-settings {
margin-bottom: 20px;
padding: 15px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.settings-form {
margin-top: 15px;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.form-control {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: var(--radius-sm);
font-size: 14px;
transition: border-color 0.2s;
}
.form-control:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.1);
}
.form-help {
display: block;
margin-top: 5px;
font-size: 12px;
color: #666;
line-height: 1.4;
}
/* Секция администратора */
.admin-section {
margin-bottom: 20px;
padding: 20px;
background: #fff3cd;
border-radius: var(--radius-sm);
border: 1px solid #ffeaa7;
}
.admin-section h5 {
margin: 0 0 15px 0;
color: #856404;
font-size: 1.1rem;
font-weight: 600;
}
/* Простая информация */
.simple-info {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.simple-info h5 {
margin: 0 0 15px 0;
color: var(--color-primary);
font-size: 1.1rem;
font-weight: 600;
}
.info-text {
color: #666;
line-height: 1.6;
}
.info-text ul {
margin: 10px 0;
padding-left: 20px;
}
.info-text li {
margin-bottom: 5px;
}
.token-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.token-input-wrapper {
position: relative;
flex: 1;
}
.token-input {
width: 100%;
padding-right: 40px;
}
.token-input.is-valid {
border-color: #28a745;
box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.1);
}
.token-input.is-invalid {
border-color: #dc3545;
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.1);
}
.validation-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 10px;
}
.validation-icon.valid {
background: #28a745;
color: white;
}
.validation-icon.invalid {
background: #dc3545;
color: white;
}
.validation-icon.loading {
background: #6c757d;
color: white;
}
.remove-token {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #dc3545;
border: none;
color: white;
font-size: 12px;
transition: all 0.2s ease;
flex-shrink: 0;
}
.remove-token:hover {
background: #c82333;
transform: scale(1.05);
}
.remove-token:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.add-token {
margin-top: 15px;
width: 100%;
padding: 12px 20px;
border: 2px dashed #28a745;
background: #f8f9fa;
color: #28a745;
border-radius: var(--radius-sm);
font-weight: 500;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.add-token:hover {
background: #28a745;
color: white;
border-color: #28a745;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(40, 167, 69, 0.2);
}
/* Сообщение валидации */
.form-validation-info {
display: flex;
align-items: center;
gap: 8px;
margin-top: 15px;
padding: 10px 15px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-sm);
color: #856404;
font-size: 14px;
}
.form-validation-info i {
color: #f39c12;
}
/* Дополнительные настройки */
.advanced-settings {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.advanced-settings h5 {
margin: 0 0 15px 0;
color: var(--color-primary);
font-size: 1.1rem;
font-weight: 600;
}
.advanced-settings .form-row {
margin-bottom: 15px;
}
.advanced-settings .form-group {
margin-bottom: 15px;
}
.advanced-settings .form-group:last-child {
margin-bottom: 0;
}
.networks-info h4,
.deploy-parameters h4 {
margin: 0 0 15px 0;
color: var(--color-primary);
}
.networks-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 10px;
}
.network-item {
display: flex;
flex-direction: column;
padding: 10px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.network-name {
font-weight: 600;
color: var(--color-primary);
}
.network-chain-id {
font-size: 12px;
color: #666;
font-family: monospace;
}
.parameter-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.parameter-item:last-child {
border-bottom: none;
}
.parameter-item label {
font-weight: 500;
color: #333;
}
.parameter-value {
font-family: monospace;
color: var(--color-primary);
background: #f8f9fa;
padding: 4px 8px;
border-radius: var(--radius-sm);
font-size: 14px;
}
.deploy-actions {
text-align: center;
margin-top: 20px;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: all 0.3s;
display: inline-flex;
align-items: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.btn-primary:hover:not(:disabled) {
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transform: translateY(-1px);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-large {
padding: 16px 32px;
font-size: 18px;
}
.deployment-progress {
margin-top: 20px;
padding: 15px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.progress-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.progress-percentage {
font-weight: 600;
color: var(--color-primary);
}
.progress-bar {
width: 100%;
height: 8px;
background: #f0f0f0;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-dark));
transition: width 0.3s ease;
}
/* Информация о модуле */
.module-info {
margin-bottom: 30px;
}
.info-card {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
}
.info-card h3 {
margin: 0 0 15px 0;
color: var(--color-primary);
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
.info-item strong {
color: var(--color-primary);
}
/* Плейсхолдер для формы */
.deploy-form-placeholder {
background: #f8f9fa;
border-radius: var(--radius-md);
padding: 40px;
text-align: center;
border: 2px dashed #dee2e6;
}
.placeholder-content h3 {
color: var(--color-primary);
margin-bottom: 10px;
}
.placeholder-content p {
color: #666;
margin: 0;
}
/* Сообщение об отсутствии прав доступа */
.no-access-message {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-md);
padding: 30px;
margin: 20px 0;
text-align: center;
}
.message-content h3 {
color: #856404;
margin-bottom: 15px;
font-size: 1.4em;
}
.message-content p {
color: #856404;
margin-bottom: 20px;
font-size: 1.1em;
line-height: 1.5;
}
.message-content .btn {
background: #6c757d;
color: white;
border: none;
border-radius: var(--radius-sm);
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.message-content .btn:hover {
background: #5a6268;
}
/* Адаптивность */
@media (max-width: 768px) {
.info-grid {
grid-template-columns: 1fr;
}
.page-header {
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
}
</style>