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

This commit is contained in:
2025-07-29 18:07:21 +03:00
parent ce42899afc
commit 0f2270a08a
58 changed files with 5367 additions and 5931 deletions

View File

@@ -77,7 +77,7 @@ const tokensRouter = require('./routes/tokens');
const isicRoutes = require('./routes/isic'); // Добавленный импорт
const kppRoutes = require('./routes/kpp'); // Добавленный импорт КПП кодов
const geocodingRoutes = require('./routes/geocoding'); // Добавленный импорт
const dleRoutes = require('./routes/dle'); // Добавляем импорт DLE маршрутов
const dleV2Routes = require('./routes/dleV2'); // Добавляем импорт DLE v2 маршрутов
const settingsRoutes = require('./routes/settings'); // Добавляем импорт маршрутов настроек
const tablesRoutes = require('./routes/tables'); // Добавляем импорт таблиц
@@ -200,7 +200,7 @@ app.use('/api/tokens', tokensRouter);
app.use('/api/isic', isicRoutes); // Добавленное использование роута
app.use('/api/kpp', kppRoutes); // Добавленное использование роута КПП кодов
app.use('/api/geocoding', geocodingRoutes); // Добавленное использование роута
app.use('/api/dle', dleRoutes); // Добавляем маршрут DLE
app.use('/api/dle-v2', dleV2Routes); // Добавляем маршрут DLE v2
app.use('/api/settings', settingsRoutes); // Добавляем маршрут настроек
app.use('/api/countries', countriesRoutes); // Добавляем маршрут стран

View File

@@ -0,0 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../../../build-info/e497981aa6f6963ae97f832d3d617a54.json"
}

View File

@@ -0,0 +1,16 @@
{
"_format": "hh-sol-artifact-1",
"contractName": "ReentrancyGuard",
"sourceName": "@openzeppelin/contracts/utils/ReentrancyGuard.sol",
"abi": [
{
"inputs": [],
"name": "ReentrancyGuardReentrantCall",
"type": "error"
}
],
"bytecode": "0x",
"deployedBytecode": "0x",
"linkReferences": {},
"deployedLinkReferences": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,4 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../../build-info/6575c8c945d0e94a5880cf71f96c424f.json"
"buildInfo": "../../build-info/f60ede94c341608b6f6d2f67c953fa04.json"
}

File diff suppressed because one or more lines are too long

View File

@@ -2,8 +2,8 @@
"_format": "hh-sol-cache-2",
"files": {
"/home/alex/Digital_Legal_Entity(DLE)/backend/contracts/DLE.sol": {
"lastModificationDate": 1753643388350,
"contentHash": "bd9d569a19ab1ef7f7ce5d40f05c9857",
"lastModificationDate": 1753795256078,
"contentHash": "1fada989abe7c49f7fb352c3b427549e",
"sourceName": "contracts/DLE.sol",
"solcConfig": {
"version": "0.8.20",
@@ -31,14 +31,8 @@
}
},
"imports": [
"@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol",
"@openzeppelin/contracts/governance/Governor.sol",
"@openzeppelin/contracts/governance/extensions/GovernorSettings.sol",
"@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol",
"@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol",
"@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol",
"@openzeppelin/contracts/governance/utils/IVotes.sol",
"@openzeppelin/contracts/utils/Nonces.sol"
"@openzeppelin/contracts/token/ERC20/ERC20.sol",
"@openzeppelin/contracts/utils/ReentrancyGuard.sol"
],
"versionPragmas": [
"^0.8.20"
@@ -1855,6 +1849,43 @@
"artifacts": [
"IERC20Metadata"
]
},
"/home/alex/Digital_Legal_Entity(DLE)/backend/node_modules/@openzeppelin/contracts/utils/ReentrancyGuard.sol": {
"lastModificationDate": 1751738715692,
"contentHash": "190613e556d509d9e9a0ea43dc5d891d",
"sourceName": "@openzeppelin/contracts/utils/ReentrancyGuard.sol",
"solcConfig": {
"version": "0.8.20",
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"viaIR": true,
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata"
],
"": [
"ast"
]
}
}
}
},
"imports": [],
"versionPragmas": [
"^0.8.20"
],
"artifacts": [
"ReentrancyGuard"
]
}
}
}

View File

@@ -1,14 +0,0 @@
{
"name": "test2",
"symbol": "test2",
"location": "245000, 中国, 黄山市",
"isicCodes": [
"6810"
],
"tokenAddress": "0xef49261169B454f191678D2aFC5E91Ad2e85dfD8",
"timelockAddress": "0xd5Aea926fc023f11D7D1e762Ca4A11c87830D7ea",
"governorAddress": "0xe599F51CAF812E19666DDd6f3002665A2Ca83DaC",
"creationBlock": 8324324,
"creationTimestamp": 1747217076,
"deployedManually": true
}

View File

@@ -1,27 +1,25 @@
// SPDX-License-Identifier: PROPRIETARY
// 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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";
import "@openzeppelin/contracts/governance/utils/IVotes.sol";
import "@openzeppelin/contracts/utils/Nonces.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @title DLE (Digital Legal Entity)
* @dev Основной контракт DLE с отдельным модулем TimelockController.
* @dev Основной контракт DLE с модульной архитектурой и мульти-чейн поддержкой
*/
contract DLE is
ERC20Votes,
Governor,
GovernorSettings,
GovernorCountingSimple,
GovernorVotesQuorumFraction,
GovernorTimelockControl
{
contract DLE is ERC20, ReentrancyGuard {
struct DLEInfo {
string name;
string symbol;
@@ -44,64 +42,57 @@ contract DLE is
uint256 oktmo;
string[] okvedCodes;
uint256 kpp;
uint48 votingDelay;
uint32 votingPeriod;
uint256 proposalThreshold;
uint256 quorumPercentage;
address[] initialPartners;
uint256[] initialAmounts;
uint256[] supportedChainIds; // Поддерживаемые цепочки
}
struct Proposal {
bytes operation;
uint256[] targetChains;
uint256 timelock;
uint256 governanceChain;
address initiator;
bytes[] signatures;
bool executed;
uint256 quorumRequired;
uint256 signaturesCount;
}
struct TokenDistributionProposal {
address[] partners;
uint256[] amounts;
uint256 timelock;
address initiator;
bytes[] signatures;
bool executed;
uint256 quorumRequired;
uint256 signaturesCount;
uint256 id;
string description;
}
struct TreasuryProposal {
address recipient;
uint256 amount;
uint256 timelock;
address initiator;
bytes[] signatures;
uint256 forVotes;
uint256 againstVotes;
bool executed;
uint256 quorumRequired;
uint256 signaturesCount;
string description;
uint256 deadline;
address initiator;
bytes operation; // Операция для исполнения
mapping(address => bool) hasVoted;
mapping(uint256 => bool) chainVoteSynced; // Синхронизация голосов между цепочками
}
struct MultiSigOperation {
bytes32 operationHash;
uint256 forSignatures;
uint256 againstSignatures;
bool executed;
uint256 deadline;
address initiator;
mapping(address => bool) hasSigned;
mapping(uint256 => bool) chainSignSynced; // Синхронизация подписей между цепочками
}
// Основные настройки
DLEInfo public dleInfo;
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => TokenDistributionProposal) public tokenDistributionProposals;
mapping(uint256 => TreasuryProposal) public treasuryProposals;
uint256 public proposalCounter;
uint256 public tokenDistributionProposalCounter;
uint256 public treasuryProposalCounter;
uint256 public quorumPercentage;
bool public initialTokensDistributed = false;
uint256 public proposalCounter;
uint256 public multiSigCounter;
uint256 public currentChainId;
// Казначейские функции
mapping(address => uint256) public lastWithdrawalBlock; // Последний блок вывода для каждого адреса
uint256 public totalTreasuryBalance; // Общий баланс казны
// Модули
mapping(bytes32 => address) public modules;
mapping(bytes32 => bool) public activeModules;
// Предложения и мультиподписи
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => MultiSigOperation) public multiSigOperations;
// Мульти-чейн
mapping(uint256 => bool) public supportedChains;
mapping(uint256 => bool) public executedProposals; // Синхронизация исполненных предложений
mapping(uint256 => bool) public executedMultiSig; // Синхронизация исполненных мультиподписей
// События
event DLEInitialized(
string name,
string symbol,
@@ -112,32 +103,25 @@ contract DLE is
string[] okvedCodes,
uint256 kpp,
address tokenAddress,
address timelockAddress,
address governorAddress
uint256[] supportedChainIds
);
event InitialTokensDistributed(address[] partners, uint256[] amounts);
event TokensDepositedToTreasury(address depositor, uint256 amount);
event TreasuryProposalCreated(uint256 proposalId, address initiator, address recipient, uint256 amount, string description);
event TreasuryProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
event TreasuryProposalExecuted(uint256 proposalId, address recipient, uint256 amount);
event TokenDistributionProposalCreated(uint256 proposalId, address initiator, address[] partners, uint256[] amounts, string description);
event TokenDistributionProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
event TokenDistributionProposalExecuted(uint256 proposalId, address[] partners, uint256[] amounts);
event ProposalCreated(uint256 proposalId, address initiator, bytes operation);
event ProposalSigned(uint256 proposalId, address signer, uint256 signaturesCount);
event ModuleInstalled(string moduleName, address moduleAddress);
event ProposalCreated(uint256 proposalId, address initiator, string description);
event ProposalVoted(uint256 proposalId, address voter, bool support, uint256 votingPower);
event ProposalExecuted(uint256 proposalId, bytes operation);
event MultiSigOperationCreated(uint256 operationId, address initiator, bytes32 operationHash);
event MultiSigSigned(uint256 operationId, address signer, bool support, uint256 signaturePower);
event MultiSigExecuted(uint256 operationId, bytes32 operationHash);
event ModuleAdded(bytes32 moduleId, address moduleAddress);
event ModuleRemoved(bytes32 moduleId);
event CrossChainExecutionSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
event CrossChainVoteSync(uint256 proposalId, uint256 fromChainId, uint256 toChainId);
event CrossChainMultiSigSync(uint256 operationId, uint256 fromChainId, uint256 toChainId);
constructor(
DLEConfig memory config,
address timelockAddress
)
ERC20(config.name, config.symbol)
Governor(config.name)
GovernorSettings(config.votingDelay, config.votingPeriod, config.proposalThreshold)
GovernorVotesQuorumFraction(config.quorumPercentage)
GovernorTimelockControl(TimelockController(payable(timelockAddress)))
GovernorVotes(IVotes(address(this)))
{
uint256 _currentChainId
) ERC20(config.name, config.symbol) {
dleInfo = DLEInfo({
name: config.name,
symbol: config.symbol,
@@ -150,9 +134,16 @@ contract DLE is
creationTimestamp: block.timestamp,
isActive: true
});
quorumPercentage = config.quorumPercentage;
currentChainId = _currentChainId;
// Автоматически распределяем начальные токены партнерам при деплое
// Настраиваем поддерживаемые цепочки
for (uint256 i = 0; i < config.supportedChainIds.length; i++) {
supportedChains[config.supportedChainIds[i]] = true;
}
// Распределяем начальные токены партнерам
require(config.initialPartners.length == config.initialAmounts.length, "Arrays length mismatch");
require(config.initialPartners.length > 0, "No initial partners");
@@ -162,9 +153,7 @@ contract DLE is
_mint(config.initialPartners[i], config.initialAmounts[i]);
}
initialTokensDistributed = true;
emit InitialTokensDistributed(config.initialPartners, config.initialAmounts);
emit DLEInitialized(
config.name,
config.symbol,
@@ -175,349 +164,455 @@ contract DLE is
config.okvedCodes,
config.kpp,
address(this),
timelockAddress,
address(this)
config.supportedChainIds
);
}
/**
* @dev Создать предложение на распределение токенов
* @param _partners Массив адресов партнеров
* @param _amounts Массив сумм токенов для каждого партнера
* @param _timelock Время исполнения (timestamp)
* @dev Создать предложение с выбором цепочки для кворума
* @param _description Описание предложения
* @param _duration Длительность голосования в секундах
* @param _operation Операция для исполнения
* @param _governanceChainId ID цепочки для сбора голосов
*/
function createTokenDistributionProposal(
address[] memory _partners,
uint256[] memory _amounts,
uint256 _timelock,
string memory _description
) external returns (uint256) {
require(_partners.length == _amounts.length, "Arrays length mismatch");
require(_partners.length > 0, "Empty arrays");
require(_timelock > block.timestamp, "Invalid timelock");
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
uint256 proposalId = tokenDistributionProposalCounter++;
tokenDistributionProposals[proposalId] = TokenDistributionProposal({
partners: _partners,
amounts: _amounts,
timelock: _timelock,
initiator: msg.sender,
signatures: new bytes[](0),
executed: false,
quorumRequired: (totalSupply() * quorumPercentage) / 100,
signaturesCount: 0,
description: _description
});
emit TokenDistributionProposalCreated(proposalId, msg.sender, _partners, _amounts, _description);
return proposalId;
}
/**
* @dev Подписать предложение на распределение токенов
* @param _proposalId ID предложения
*/
function signTokenDistributionProposal(uint256 _proposalId) external {
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
require(!proposal.executed, "Proposal already executed");
require(block.timestamp < proposal.timelock, "Proposal expired");
require(balanceOf(msg.sender) > 0, "No tokens to sign");
// Проверяем, что пользователь еще не подписал
for (uint256 i = 0; i < proposal.signatures.length; i++) {
require(
proposal.signatures[i].length == 0 ||
abi.decode(proposal.signatures[i], (address)) != msg.sender,
"Already signed"
);
}
proposal.signatures.push(abi.encodePacked(msg.sender));
proposal.signaturesCount++;
emit TokenDistributionProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
// Проверяем, достигнут ли кворум
if (proposal.signaturesCount >= proposal.quorumRequired) {
proposal.executed = true;
_executeTokenDistribution(_proposalId);
}
}
/**
* @dev Выполнить распределение токенов после достижения кворума
* @param _proposalId ID предложения
*/
function _executeTokenDistribution(uint256 _proposalId) internal {
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
require(proposal.executed, "Proposal not executed");
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
for (uint256 i = 0; i < proposal.partners.length; i++) {
require(proposal.partners[i] != address(0), "Zero address");
require(proposal.amounts[i] > 0, "Zero amount");
_mint(proposal.partners[i], proposal.amounts[i]);
}
emit TokenDistributionProposalExecuted(_proposalId, proposal.partners, proposal.amounts);
}
/**
* @dev Выполнить предложение на распределение токенов после истечения таймлока
* @param _proposalId ID предложения
*/
function executeTokenDistributionProposal(uint256 _proposalId) external {
TokenDistributionProposal storage proposal = tokenDistributionProposals[_proposalId];
require(!proposal.executed, "Proposal already executed");
require(block.timestamp >= proposal.timelock, "Timelock not expired");
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
proposal.executed = true;
_executeTokenDistribution(_proposalId);
}
function createProposal(
string memory _description,
uint256 _duration,
bytes memory _operation,
uint256[] memory _targetChains,
uint256 _timelock,
uint256 _governanceChain
uint256 _governanceChainId
) external returns (uint256) {
require(_operation.length > 0, "Empty operation");
require(_targetChains.length > 0, "Empty target chains");
require(_timelock > block.timestamp, "Invalid timelock");
uint256 proposalId = proposalCounter++;
proposals[proposalId] = Proposal({
operation: _operation,
targetChains: _targetChains,
timelock: _timelock,
governanceChain: _governanceChain,
initiator: msg.sender,
signatures: new bytes[](0),
executed: false,
quorumRequired: (totalSupply() * quorumPercentage) / 100,
signaturesCount: 0
});
emit ProposalCreated(proposalId, msg.sender, _operation);
return proposalId;
}
function signProposal(uint256 _proposalId) external {
Proposal storage proposal = proposals[_proposalId];
require(!proposal.executed, "Proposal already executed");
require(block.timestamp < proposal.timelock, "Proposal expired");
require(balanceOf(msg.sender) > 0, "No tokens to sign");
proposal.signatures.push(abi.encodePacked(msg.sender));
proposal.signaturesCount++;
emit ProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
if (proposal.signaturesCount >= proposal.quorumRequired) {
proposal.executed = true;
emit IGovernor.ProposalExecuted(_proposalId);
}
}
function installModule(string memory _moduleName, address _moduleAddress) external {
emit ModuleInstalled(_moduleName, _moduleAddress);
}
/**
* @dev Внести токены в казну DLE
* @param _amount Количество токенов для внесения
*/
function depositToTreasury(uint256 _amount) external {
require(_amount > 0, "Amount must be greater than 0");
require(balanceOf(msg.sender) >= _amount, "Insufficient balance");
_transfer(msg.sender, address(this), _amount);
totalTreasuryBalance += _amount;
emit TokensDepositedToTreasury(msg.sender, _amount);
}
/**
* @dev Создать предложение на вывод средств из казны
* @param _recipient Адрес получателя
* @param _amount Количество токенов для вывода
* @param _timelock Время исполнения (timestamp)
* @param _description Описание предложения
*/
function createTreasuryProposal(
address _recipient,
uint256 _amount,
uint256 _timelock,
string memory _description
) external returns (uint256) {
require(_recipient != address(0), "Zero address");
require(_amount > 0, "Amount must be greater than 0");
require(_amount <= totalTreasuryBalance, "Insufficient treasury balance");
require(_timelock > block.timestamp, "Invalid timelock");
require(balanceOf(msg.sender) > 0, "Must hold tokens to create proposal");
require(_duration > 0, "Duration must be positive");
require(supportedChains[_governanceChainId], "Chain not supported");
require(checkChainConnection(_governanceChainId), "Chain not available");
uint256 proposalId = treasuryProposalCounter++;
uint256 proposalId = proposalCounter++;
Proposal storage proposal = proposals[proposalId];
treasuryProposals[proposalId] = TreasuryProposal({
recipient: _recipient,
amount: _amount,
timelock: _timelock,
initiator: msg.sender,
signatures: new bytes[](0),
executed: false,
quorumRequired: (totalSupply() * quorumPercentage) / 100,
signaturesCount: 0,
description: _description
});
proposal.id = proposalId;
proposal.description = _description;
proposal.forVotes = 0;
proposal.againstVotes = 0;
proposal.executed = false;
proposal.deadline = block.timestamp + _duration;
proposal.initiator = msg.sender;
proposal.operation = _operation;
emit TreasuryProposalCreated(proposalId, msg.sender, _recipient, _amount, _description);
emit ProposalCreated(proposalId, msg.sender, _description);
return proposalId;
}
/**
* @dev Подписать предложение на вывод средств из казны
* @dev Голосовать за предложение
* @param _proposalId ID предложения
* @param _support Поддержка предложения
*/
function signTreasuryProposal(uint256 _proposalId) external {
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
function vote(uint256 _proposalId, bool _support) external nonReentrant {
Proposal storage proposal = proposals[_proposalId];
require(proposal.id == _proposalId, "Proposal does not exist");
require(block.timestamp < proposal.deadline, "Voting ended");
require(!proposal.executed, "Proposal already executed");
require(block.timestamp < proposal.timelock, "Proposal expired");
require(balanceOf(msg.sender) > 0, "No tokens to sign");
require(!proposal.hasVoted[msg.sender], "Already voted");
require(balanceOf(msg.sender) > 0, "No tokens to vote");
// Проверяем, что пользователь еще не подписал
for (uint256 i = 0; i < proposal.signatures.length; i++) {
require(
proposal.signatures[i].length == 0 ||
abi.decode(proposal.signatures[i], (address)) != msg.sender,
"Already signed"
);
uint256 votingPower = balanceOf(msg.sender);
proposal.hasVoted[msg.sender] = true;
if (_support) {
proposal.forVotes += votingPower;
} else {
proposal.againstVotes += votingPower;
}
proposal.signatures.push(abi.encodePacked(msg.sender));
proposal.signaturesCount++;
emit TreasuryProposalSigned(_proposalId, msg.sender, proposal.signaturesCount);
// Проверяем, достигнут ли кворум
if (proposal.signaturesCount >= proposal.quorumRequired) {
proposal.executed = true;
_executeTreasuryProposal(_proposalId);
}
emit ProposalVoted(_proposalId, msg.sender, _support, votingPower);
}
/**
* @dev Выполнить предложение на вывод средств из казны
* @dev Синхронизировать голос из другой цепочки
* @param _proposalId ID предложения
* @param _fromChainId ID цепочки откуда синхронизируем
* @param _forVotes Голоса за
* @param _againstVotes Голоса против
*/
function _executeTreasuryProposal(uint256 _proposalId) internal {
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
require(proposal.executed, "Proposal not executed");
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
require(proposal.amount <= totalTreasuryBalance, "Insufficient treasury balance");
function syncVoteFromChain(
uint256 _proposalId,
uint256 _fromChainId,
uint256 _forVotes,
uint256 _againstVotes,
bytes memory /* _proof */
) external {
Proposal storage proposal = proposals[_proposalId];
require(proposal.id == _proposalId, "Proposal does not exist");
require(supportedChains[_fromChainId], "Chain not supported");
require(!proposal.chainVoteSynced[_fromChainId], "Already synced");
totalTreasuryBalance -= proposal.amount;
_transfer(address(this), proposal.recipient, proposal.amount);
// Здесь должна быть проверка proof (для простоты пропускаем)
// В реальной реализации нужно проверять доказательство
emit TreasuryProposalExecuted(_proposalId, proposal.recipient, proposal.amount);
proposal.forVotes += _forVotes;
proposal.againstVotes += _againstVotes;
proposal.chainVoteSynced[_fromChainId] = true;
emit CrossChainVoteSync(_proposalId, _fromChainId, currentChainId);
}
/**
* @dev Выполнить предложение на вывод средств после истечения таймлока
* @dev Проверить результат предложения
* @param _proposalId ID предложения
* @return passed Прошло ли предложение
* @return quorumReached Достигнут ли кворум
*/
function checkProposalResult(uint256 _proposalId) public view returns (bool passed, bool quorumReached) {
Proposal storage proposal = proposals[_proposalId];
require(proposal.id == _proposalId, "Proposal does not exist");
uint256 totalVotes = proposal.forVotes + proposal.againstVotes;
uint256 quorumRequired = (totalSupply() * quorumPercentage) / 100;
quorumReached = totalVotes >= quorumRequired;
passed = quorumReached && proposal.forVotes > proposal.againstVotes;
return (passed, quorumReached);
}
/**
* @dev Исполнить предложение
* @param _proposalId ID предложения
*/
function executeTreasuryProposal(uint256 _proposalId) external {
TreasuryProposal storage proposal = treasuryProposals[_proposalId];
function executeProposal(uint256 _proposalId) external {
Proposal storage proposal = proposals[_proposalId];
require(proposal.id == _proposalId, "Proposal does not exist");
require(!proposal.executed, "Proposal already executed");
require(block.timestamp >= proposal.timelock, "Timelock not expired");
require(proposal.signaturesCount >= proposal.quorumRequired, "Insufficient quorum");
require(block.timestamp >= proposal.deadline, "Voting not ended");
(bool passed, bool quorumReached) = checkProposalResult(_proposalId);
require(passed && quorumReached, "Proposal not passed");
proposal.executed = true;
_executeTreasuryProposal(_proposalId);
// Исполняем операцию
_executeOperation(proposal.operation);
emit ProposalExecuted(_proposalId, proposal.operation);
}
/**
* @dev Получить доступную для вывода сумму для адреса (пропорционально доле)
* @param _address Адрес для проверки
* @return Доступная сумма для вывода
* @dev Создать мультиподпись операцию
* @param _operationHash Хеш операции
* @param _duration Длительность сбора подписей
*/
function getAvailableWithdrawal(address _address) public view returns (uint256) {
uint256 userBalance = balanceOf(_address);
if (userBalance == 0 || totalTreasuryBalance == 0) {
return 0;
function createMultiSigOperation(
bytes32 _operationHash,
uint256 _duration
) external returns (uint256) {
require(balanceOf(msg.sender) > 0, "Must hold tokens to create operation");
require(_duration > 0, "Duration must be positive");
uint256 operationId = multiSigCounter++;
MultiSigOperation storage operation = multiSigOperations[operationId];
operation.operationHash = _operationHash;
operation.forSignatures = 0;
operation.againstSignatures = 0;
operation.executed = false;
operation.deadline = block.timestamp + _duration;
operation.initiator = msg.sender;
emit MultiSigOperationCreated(operationId, msg.sender, _operationHash);
return operationId;
}
/**
* @dev Подписать мультиподпись операцию
* @param _operationId ID операции
* @param _support Поддержка операции
*/
function signMultiSigOperation(uint256 _operationId, bool _support) external nonReentrant {
MultiSigOperation storage operation = multiSigOperations[_operationId];
require(operation.operationHash != bytes32(0), "Operation does not exist");
require(block.timestamp < operation.deadline, "Signing ended");
require(!operation.executed, "Operation already executed");
require(!operation.hasSigned[msg.sender], "Already signed");
require(balanceOf(msg.sender) > 0, "No tokens to sign");
uint256 signaturePower = balanceOf(msg.sender);
operation.hasSigned[msg.sender] = true;
if (_support) {
operation.forSignatures += signaturePower;
} else {
operation.againstSignatures += signaturePower;
}
emit MultiSigSigned(_operationId, msg.sender, _support, signaturePower);
}
/**
* @dev Синхронизировать мультиподпись из другой цепочки
* @param _operationId ID операции
* @param _fromChainId ID цепочки откуда синхронизируем
* @param _forSignatures Подписи за
* @param _againstSignatures Подписи против
*/
function syncMultiSigFromChain(
uint256 _operationId,
uint256 _fromChainId,
uint256 _forSignatures,
uint256 _againstSignatures,
bytes memory /* _proof */
) external {
MultiSigOperation storage operation = multiSigOperations[_operationId];
require(operation.operationHash != bytes32(0), "Operation does not exist");
require(supportedChains[_fromChainId], "Chain not supported");
require(!operation.chainSignSynced[_fromChainId], "Already synced");
// Здесь должна быть проверка proof
// В реальной реализации нужно проверять доказательство
operation.forSignatures += _forSignatures;
operation.againstSignatures += _againstSignatures;
operation.chainSignSynced[_fromChainId] = true;
emit CrossChainMultiSigSync(_operationId, _fromChainId, currentChainId);
}
/**
* @dev Проверить результат мультиподписи
* @param _operationId ID операции
* @return passed Прошла ли операция
* @return quorumReached Достигнут ли кворум
*/
function checkMultiSigResult(uint256 _operationId) public view returns (bool passed, bool quorumReached) {
MultiSigOperation storage operation = multiSigOperations[_operationId];
require(operation.operationHash != bytes32(0), "Operation does not exist");
uint256 totalSignatures = operation.forSignatures + operation.againstSignatures;
uint256 quorumRequired = (totalSupply() * quorumPercentage) / 100;
quorumReached = totalSignatures >= quorumRequired;
passed = quorumReached && operation.forSignatures > operation.againstSignatures;
return (passed, quorumReached);
}
/**
* @dev Исполнить мультиподпись операцию
* @param _operationId ID операции
*/
function executeMultiSigOperation(uint256 _operationId) external {
MultiSigOperation storage operation = multiSigOperations[_operationId];
require(operation.operationHash != bytes32(0), "Operation does not exist");
require(!operation.executed, "Operation already executed");
require(block.timestamp >= operation.deadline, "Signing not ended");
(bool passed, bool quorumReached) = checkMultiSigResult(_operationId);
require(passed && quorumReached, "Operation not passed");
operation.executed = true;
emit MultiSigExecuted(_operationId, operation.operationHash);
}
/**
* @dev Синхронизировать исполнение из другой цепочки
* @param _proposalId ID предложения
* @param _fromChainId ID цепочки откуда синхронизируем
*/
function syncExecutionFromChain(
uint256 _proposalId,
uint256 _fromChainId,
bytes memory /* _proof */
) external {
require(supportedChains[_fromChainId], "Chain not supported");
require(!executedProposals[_proposalId], "Already executed");
// Здесь должна быть проверка proof
// В реальной реализации нужно проверять доказательство
executedProposals[_proposalId] = true;
// Получаем операцию из предложения
Proposal storage proposal = proposals[_proposalId];
if (proposal.id == _proposalId) {
_executeOperation(proposal.operation);
}
emit CrossChainExecutionSync(_proposalId, _fromChainId, currentChainId);
}
/**
* @dev Проверить подключение к цепочке
* @param _chainId ID цепочки
* @return isAvailable Доступна ли цепочка
*/
function checkChainConnection(uint256 _chainId) public view returns (bool isAvailable) {
// В реальной реализации здесь должна быть проверка подключения
// Для примера возвращаем true для поддерживаемых цепочек
return supportedChains[_chainId];
}
/**
* @dev Проверить все подключения перед синхронизацией
* @param _proposalId ID предложения
* @return allChainsReady Готовы ли все цепочки
*/
function checkSyncReadiness(uint256 _proposalId) public view returns (bool allChainsReady) {
Proposal storage proposal = proposals[_proposalId];
require(proposal.id == _proposalId, "Proposal does not exist");
// Проверяем все поддерживаемые цепочки
for (uint256 i = 0; i < getSupportedChainCount(); i++) {
uint256 chainId = getSupportedChainId(i);
if (!checkChainConnection(chainId)) {
return false;
}
}
// Пропорционально доле в общем количестве токенов
uint256 userShare = (userBalance * totalTreasuryBalance) / totalSupply();
return userShare;
return true;
}
/**
* @dev Синхронизация только при 100% готовности
* @param _proposalId ID предложения
*/
function syncToAllChains(uint256 _proposalId) external {
require(checkSyncReadiness(_proposalId), "Not all chains ready");
// Выполняем синхронизацию во все цепочки
for (uint256 i = 0; i < getSupportedChainCount(); i++) {
uint256 chainId = getSupportedChainId(i);
syncToChain(_proposalId, chainId);
}
emit SyncCompleted(_proposalId);
}
/**
* @dev Синхронизация в конкретную цепочку
* @param _proposalId ID предложения
* @param _chainId ID цепочки
*/
function syncToChain(uint256 _proposalId, uint256 _chainId) internal {
// В реальной реализации здесь будет вызов cross-chain bridge
// Для примера просто эмитим событие
emit CrossChainExecutionSync(_proposalId, currentChainId, _chainId);
}
// Переопределения для совместимости с ERC-6372
function CLOCK_MODE() public pure override(Governor, GovernorVotes, Votes) returns (string memory) {
return "mode=blocknumber&from=default";
/**
* @dev Получить количество поддерживаемых цепочек
*/
function getSupportedChainCount() public pure returns (uint256) {
// В реальной реализации нужно хранить массив поддерживаемых цепочек
// Для примера возвращаем 4 (Ethereum, Polygon, BSC, Arbitrum)
return 4;
}
function clock() public view override(Governor, GovernorVotes, Votes) returns (uint48) {
return uint48(block.number);
/**
* @dev Получить ID поддерживаемой цепочки по индексу
* @param _index Индекс цепочки
*/
function getSupportedChainId(uint256 _index) public pure returns (uint256) {
if (_index == 0) return 1; // Ethereum
if (_index == 1) return 137; // Polygon
if (_index == 2) return 56; // BSC
if (_index == 3) return 42161; // Arbitrum
revert("Invalid chain index");
}
function _update(address from, address to, uint256 amount) internal override(ERC20Votes) {
super._update(from, to, amount);
/**
* @dev Исполнить операцию
* @param _operation Операция для исполнения
*/
function _executeOperation(bytes memory _operation) internal {
// Декодируем операцию
(bytes4 selector, bytes memory data) = abi.decode(_operation, (bytes4, bytes));
if (selector == bytes4(keccak256("transfer(address,uint256)"))) {
// Операция передачи токенов
(address to, uint256 amount) = abi.decode(data, (address, uint256));
_transfer(msg.sender, to, amount);
} else if (selector == bytes4(keccak256("mint(address,uint256)"))) {
// Операция минтинга токенов
(address to, uint256 amount) = abi.decode(data, (address, uint256));
_mint(to, amount);
} else if (selector == bytes4(keccak256("burn(address,uint256)"))) {
// Операция сжигания токенов
(address from, uint256 amount) = abi.decode(data, (address, uint256));
_burn(from, amount);
} else {
// Неизвестная операция
revert("Unknown operation");
}
}
function nonces(address owner) public view override(Nonces) returns (uint256) {
return super.nonces(owner);
/**
* @dev Добавить модуль
* @param _moduleId ID модуля
* @param _moduleAddress Адрес модуля
*/
function addModule(bytes32 _moduleId, address _moduleAddress) external {
require(balanceOf(msg.sender) > 0, "Must hold tokens to add module");
require(_moduleAddress != address(0), "Zero address");
require(!activeModules[_moduleId], "Module already exists");
modules[_moduleId] = _moduleAddress;
activeModules[_moduleId] = true;
emit ModuleAdded(_moduleId, _moduleAddress);
}
function name() public view override(ERC20, Governor) returns (string memory) {
return super.name();
/**
* @dev Удалить модуль
* @param _moduleId ID модуля
*/
function removeModule(bytes32 _moduleId) external {
require(balanceOf(msg.sender) > 0, "Must hold tokens to remove module");
require(activeModules[_moduleId], "Module does not exist");
delete modules[_moduleId];
activeModules[_moduleId] = false;
emit ModuleRemoved(_moduleId);
}
function votingDelay() public view override(Governor, GovernorSettings) returns (uint256) {
return super.votingDelay();
/**
* @dev Получить информацию о DLE
*/
function getDLEInfo() external view returns (DLEInfo memory) {
return dleInfo;
}
function votingPeriod() public view override(Governor, GovernorSettings) returns (uint256) {
return super.votingPeriod();
/**
* @dev Проверить, активен ли модуль
* @param _moduleId ID модуля
*/
function isModuleActive(bytes32 _moduleId) external view returns (bool) {
return activeModules[_moduleId];
}
function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) {
return super.quorum(blockNumber);
/**
* @dev Получить адрес модуля
* @param _moduleId ID модуля
*/
function getModuleAddress(bytes32 _moduleId) external view returns (address) {
return modules[_moduleId];
}
function state(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (ProposalState) {
return super.state(proposalId);
/**
* @dev Проверить, поддерживается ли цепочка
* @param _chainId ID цепочки
*/
function isChainSupported(uint256 _chainId) external view returns (bool) {
return supportedChains[_chainId];
}
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}
function _cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) returns (uint256) {
return super._cancel(targets, values, calldatas, descriptionHash);
}
function _executor() internal view override(Governor, GovernorTimelockControl) returns (address) {
return super._executor();
}
function supportsInterface(bytes4 interfaceId) public view override(Governor) returns (bool) {
return super.supportsInterface(interfaceId);
}
function proposalNeedsQueuing(uint256 proposalId) public view override(Governor, GovernorTimelockControl) returns (bool) {
return super.proposalNeedsQueuing(proposalId);
}
function _queueOperations(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) returns (uint48) {
return super._queueOperations(proposalId, targets, values, calldatas, descriptionHash);
}
function _executeOperations(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockControl) {
super._executeOperations(proposalId, targets, values, calldatas, descriptionHash);
/**
* @dev Получить текущий ID цепочки
*/
function getCurrentChainId() external view returns (uint256) {
return currentChainId;
}
// События для новых функций
event SyncCompleted(uint256 proposalId);
}

View File

@@ -1,3 +1,15 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
# Анализ кодов по юрисдикциям для DLE
## 🌍 ГЛОБАЛЬНЫЕ (МЕЖДУНАРОДНЫЕ) КОДЫ

View File

@@ -1,187 +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
*/
const express = require('express');
const router = express.Router();
const dleService = require('../services/dleService');
const logger = require('../utils/logger');
const auth = require('../middleware/auth');
const path = require('path');
const fs = require('fs');
/**
* @route POST /api/dle
* @desc Создать новое DLE (Digital Legal Entity)
* @access Private (только для авторизованных пользователей с ролью admin)
*/
router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res, next) => {
try {
const dleParams = req.body;
logger.info('Получен запрос на создание DLE:', dleParams);
// Если параметр partners не был передан явно, используем адрес авторизованного пользователя
if (!dleParams.partners || dleParams.partners.length === 0) {
// Проверяем, есть ли в сессии адрес кошелька пользователя
if (!req.user || !req.user.walletAddress) {
return res.status(400).json({
success: false,
message: 'Не указан адрес кошелька пользователя или партнеров для распределения токенов'
});
}
// Используем адрес авторизованного пользователя
dleParams.partners = [req.user.address || req.user.walletAddress];
// Если суммы не указаны, используем значение по умолчанию (100% токенов)
if (!dleParams.amounts || dleParams.amounts.length === 0) {
dleParams.amounts = ['1000000'];
}
}
const result = await dleService.createDLE(dleParams);
res.status(201).json({
success: true,
message: 'DLE успешно создано',
data: result
});
} catch (error) {
logger.error('Ошибка при создании DLE:', error);
next(error);
}
});
/**
* @route GET /api/dle
* @desc Получить список всех DLE
* @access Public (доступно всем пользователям, включая гостевых)
*/
router.get('/', async (req, res, next) => {
try {
const dles = await dleService.getAllDLEs();
res.json({
success: true,
data: dles
});
} catch (error) {
logger.error('Ошибка при получении списка DLE:', error);
next(error);
}
});
/**
* @route GET /api/dle/settings
* @desc Получить настройки для деплоя DLE по умолчанию
* @access Private (только для авторизованных пользователей)
*/
router.get('/settings', auth.requireAuth, (req, res) => {
// Возвращаем настройки по умолчанию, которые будут использоваться
// при заполнении формы на фронтенде
res.json({
success: true,
data: {
votingDelay: 1, // 1 блок задержки перед началом голосования
votingPeriod: 45818, // ~1 неделя в блоках (при 13 секундах на блок)
proposalThreshold: '100000', // 100,000 токенов
quorumPercentage: 4, // 4% от общего количества токенов
minTimelockDelay: 2 // 2 дня
}
});
});
/**
* @route DELETE /api/dle/:tokenAddress
* @desc Удалить DLE по адресу токена
* @access Private (только для авторизованных пользователей с ролью admin)
*/
router.delete('/:tokenAddress', auth.requireAuth, auth.requireAdmin, async (req, res, next) => {
try {
const { tokenAddress } = req.params;
logger.info(`Получен запрос на удаление DLE с адресом токена: ${tokenAddress}`);
// Проверяем существование DLE в директории contracts-data/dles
const dlesDir = path.join(__dirname, '../contracts-data/dles');
const files = fs.readdirSync(dlesDir);
let fileToDelete = null;
// Находим файл, содержащий указанный адрес токена
for (const file of files) {
const filePath = path.join(dlesDir, file);
if (fs.statSync(filePath).isFile() && file.endsWith('.json')) {
try {
const dleData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
if (dleData.tokenAddress && dleData.tokenAddress.toLowerCase() === tokenAddress.toLowerCase()) {
fileToDelete = filePath;
break;
}
} catch (err) {
logger.error(`Ошибка при чтении файла ${file}:`, err);
}
}
}
if (!fileToDelete) {
return res.status(404).json({
success: false,
message: `DLE с адресом токена ${tokenAddress} не найдено`
});
}
// Удаляем файл
fs.unlinkSync(fileToDelete);
res.json({
success: true,
message: `DLE с адресом токена ${tokenAddress} успешно удалено`
});
} catch (error) {
logger.error('Ошибка при удалении DLE:', error);
next(error);
}
});
/**
* @route DELETE /api/dle/empty/:fileName
* @desc Удалить пустое DLE по имени файла
* @access Private (только для авторизованных пользователей с ролью admin)
*/
router.delete('/empty/:fileName', auth.requireAuth, auth.requireAdmin, async (req, res, next) => {
try {
const { fileName } = req.params;
logger.info(`Получен запрос на удаление пустого DLE с именем файла: ${fileName}`);
// Проверяем существование файла в директории contracts-data/dles
const dlesDir = path.join(__dirname, '../contracts-data/dles');
const filePath = path.join(dlesDir, fileName);
if (!fs.existsSync(filePath)) {
return res.status(404).json({
success: false,
message: `Файл ${fileName} не найден`
});
}
// Удаляем файл
fs.unlinkSync(filePath);
res.json({
success: true,
message: `Пустое DLE с именем файла ${fileName} успешно удалено`
});
} catch (error) {
logger.error('Ошибка при удалении пустого DLE:', error);
next(error);
}
});
module.exports = router;

View File

@@ -1,216 +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
*/
// Скрипт для ручного создания DLE без использования фабрики
const { ethers } = require("hardhat");
const fs = require("fs");
const path = require("path");
async function main() {
// Получаем параметры деплоя из файла или аргументов
const deployParams = getDeployParams();
console.log("Начинаем создание нового DLE вручную (без фабрики)...");
console.log("Параметры DLE:");
console.log(JSON.stringify(deployParams, null, 2));
// Получаем аккаунт деплоя
const [deployer] = await ethers.getSigners();
console.log(`Адрес деплоера: ${deployer.address}`);
console.log(`Баланс деплоера: ${ethers.formatEther(await deployer.provider.getBalance(deployer.address))} ETH`);
try {
// 1. Создаем токен управления
console.log("\n1. Деплой токена управления...");
const GovernanceToken = await ethers.getContractFactory("GovernanceToken");
const token = await GovernanceToken.deploy(
deployParams.name,
deployParams.symbol,
deployer.address // адрес управляющего минтом
);
await token.waitForDeployment();
const tokenAddress = await token.getAddress();
console.log(`Токен задеплоен по адресу: ${tokenAddress}`);
// 2. Создаем Timelock контракт
console.log("\n2. Деплой Timelock контракта...");
// Создаем временные пустые массивы
const proposers = [deployer.address]; // Временно даем права деплоеру
const executors = [ethers.ZeroAddress]; // Адрес 0 означает, что любой может выполнять
// Минимальная задержка в секундах
const minDelayInSeconds = deployParams.minTimelockDelay * 24 * 60 * 60; // Перевод дней в секунды
const GovernanceTimelock = await ethers.getContractFactory("GovernanceTimelock");
const timelock = await GovernanceTimelock.deploy(
minDelayInSeconds,
proposers,
executors,
deployer.address // admin
);
await timelock.waitForDeployment();
const timelockAddress = await timelock.getAddress();
console.log(`Timelock задеплоен по адресу: ${timelockAddress}`);
// 3. Создаем Governor контракт
console.log("\n3. Деплой Governor контракта...");
const GovernorContract = await ethers.getContractFactory("GovernorContract");
const governor = await GovernorContract.deploy(
`${deployParams.name} Governor`,
token,
timelock,
deployParams.votingDelay,
deployParams.votingPeriod,
deployParams.proposalThreshold,
deployParams.quorumPercentage
);
await governor.waitForDeployment();
const governorAddress = await governor.getAddress();
console.log(`Governor задеплоен по адресу: ${governorAddress}`);
// 4. Настраиваем разрешения Timelock
console.log("\n4. Настройка разрешений Timelock...");
// Отменяем временные предложения
const tx1 = await timelock.revokeRole(await timelock.PROPOSER_ROLE(), deployer.address);
await tx1.wait();
console.log(`Отозвана роль PROPOSER у деплоера`);
// Устанавливаем Governor как единственного proposer
const tx2 = await timelock.grantRole(await timelock.PROPOSER_ROLE(), governorAddress);
await tx2.wait();
console.log(`Назначена роль PROPOSER контракту Governor`);
// Отказываемся от роли администратора для Timelock
const adminRole = await timelock.DEFAULT_ADMIN_ROLE();
const tx3 = await timelock.revokeRole(adminRole, deployer.address);
await tx3.wait();
console.log(`Отозвана роль ADMIN у деплоера`);
// 5. Распределяем начальные токены
console.log("\n5. Распределение начальных токенов...");
const mintTx = await token.mintInitialSupply(deployParams.partners, deployParams.amounts);
await mintTx.wait();
console.log(`Токены распределены между партнерами`);
// 6. Сохраняем информацию о созданных контрактах
console.log("\n6. Сохранение информации о DLE...");
const dleData = {
name: deployParams.name,
symbol: deployParams.symbol,
location: deployParams.location,
isicCodes: deployParams.isicCodes,
tokenAddress,
timelockAddress,
governorAddress,
creationBlock: (await mintTx.provider.getBlockNumber()),
creationTimestamp: (await mintTx.provider.getBlock()).timestamp,
deployedManually: true
};
const saveResult = saveDLEData(dleData);
console.log("\nDLE успешно создано вручную!");
console.log(`Адрес токена: ${tokenAddress}`);
console.log(`Адрес таймлока: ${timelockAddress}`);
console.log(`Адрес контракта Governor: ${governorAddress}`);
if (!saveResult) {
console.warn("\nВНИМАНИЕ: Не удалось сохранить информацию о DLE в файл!");
console.warn("Убедитесь, что директория contracts-data/dles существует и доступна для записи.");
console.warn("Данные контрактов доступны выше в логах.");
}
return dleData;
} catch (error) {
console.error("Ошибка при создании DLE вручную:", error);
throw error;
}
}
// Получаем параметры деплоя из JSON-файла (переданного как аргумент)
function getDeployParams() {
const defaultParamsPath = path.join(__dirname, 'current-params.json');
if (fs.existsSync(defaultParamsPath)) {
console.log(`Загрузка параметров из файла: ${defaultParamsPath}`);
return JSON.parse(fs.readFileSync(defaultParamsPath, "utf8"));
}
// Используем параметры по умолчанию, если файл не найден
console.log("Используются параметры по умолчанию");
return {
name: "Manual DLE",
symbol: "MDLE",
location: "Test Location",
isicCodes: ["A01", "B02"],
partners: [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
],
amounts: [
ethers.parseEther("1000000"),
ethers.parseEther("500000")
],
minTimelockDelay: 2,
votingDelay: 1,
votingPeriod: 45818,
proposalThreshold: ethers.parseEther("100000"),
quorumPercentage: 4
};
}
// Сохраняем информацию о созданном DLE
function saveDLEData(dleData) {
const dlesDir = path.join(__dirname, "../../contracts-data/dles");
// Проверяем существование директории и создаем при необходимости
try {
if (!fs.existsSync(dlesDir)) {
console.log(`Директория ${dlesDir} не существует, создаю...`);
fs.mkdirSync(dlesDir, { recursive: true });
console.log(`Директория ${dlesDir} успешно создана`);
}
// Проверяем права на запись, создавая временный файл
const testFile = path.join(dlesDir, '.write-test');
fs.writeFileSync(testFile, 'test');
fs.unlinkSync(testFile);
console.log(`Директория ${dlesDir} доступна для записи`);
const fileName = `${dleData.name.replace(/\s+/g, '-')}-${Date.now()}.json`;
const filePath = path.join(dlesDir, fileName);
fs.writeFileSync(filePath, JSON.stringify(dleData, null, 2));
console.log(`Информация о DLE сохранена в файл: ${fileName}`);
return true;
} catch (error) {
console.error(`Ошибка при сохранении информации о DLE: ${error.message}`);
console.error(`Убедитесь, что директория ${dlesDir} существует и доступна для записи`);
console.error(`Для исправления в Docker-контейнере выполните: docker exec -it [container_id] /bin/sh -c "mkdir -p /app/contracts-data/dles && chown -R node:node /app/contracts-data"`);
return false;
}
}
// Если скрипт выполняется напрямую
if (require.main === module) {
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
} else {
// Если скрипт импортируется как модуль
module.exports = main;
}

View File

@@ -1,281 +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
*/
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const { ethers } = require('ethers');
const logger = require('../utils/logger');
const { getRpcUrlByNetworkId } = require('./rpcProviderService');
/**
* Сервис для управления DLE (Digital Legal Entity)
*/
class DLEService {
/**
* Создает новое DLE с заданными параметрами
* @param {Object} dleParams - Параметры DLE
* @returns {Promise<Object>} - Результат создания DLE
*/
async createDLE(dleParams) {
try {
logger.info('Начало создания DLE с параметрами:', dleParams);
// Валидация входных данных
this.validateDLEParams(dleParams);
// Подготовка параметров для деплоя
const deployParams = this.prepareDeployParams(dleParams);
// Сохраняем параметры во временный файл
const paramsFile = this.saveParamsToFile(deployParams);
// Копируем параметры во временный файл с предсказуемым именем
const tempParamsFile = path.join(__dirname, '../scripts/deploy/current-params.json');
logger.info(`Копирование параметров из ${paramsFile} в ${tempParamsFile}`);
const deployDir = path.dirname(tempParamsFile);
if (!fs.existsSync(deployDir)) {
fs.mkdirSync(deployDir, { recursive: true });
}
fs.copyFileSync(paramsFile, tempParamsFile);
logger.info(`Файл параметров скопирован успешно`);
// Получаем rpc_url из базы по выбранной сети
const rpcUrl = await getRpcUrlByNetworkId(deployParams.network);
if (!rpcUrl) {
throw new Error(`RPC URL для сети ${deployParams.network} не найден в базе данных`);
}
if (!dleParams.privateKey) {
throw new Error('Приватный ключ для деплоя не передан');
}
// Запускаем скрипт деплоя с нужными переменными окружения
const result = await this.runDeployScript(paramsFile, {
rpcUrl,
privateKey: dleParams.privateKey,
networkId: deployParams.network,
envNetworkKey: deployParams.network.toUpperCase()
});
logger.info('DLE успешно создано:', result);
return result;
} catch (error) {
logger.error('Ошибка при создании DLE:', error);
throw error;
}
}
/**
* Валидирует параметры DLE
* @param {Object} params - Параметры DLE
*/
validateDLEParams(params) {
const requiredFields = [
'name', 'symbol', 'location', 'isicCodes',
'partners', 'amounts', 'minTimelockDelay',
'votingDelay', 'votingPeriod', 'proposalThreshold', 'quorumPercentage'
];
for (const field of requiredFields) {
if (params[field] === undefined) {
throw new Error(`Отсутствует обязательный параметр: ${field}`);
}
}
if (params.partners.length !== params.amounts.length) {
throw new Error('Количество партнеров должно соответствовать количеству сумм распределения');
}
if (params.partners.length === 0) {
throw new Error('Должен быть указан хотя бы один партнер');
}
if (params.quorumPercentage > 100) {
throw new Error('Процент кворума не может превышать 100%');
}
}
/**
* Подготавливает параметры для деплоя
* @param {Object} params - Параметры DLE
* @returns {Object} - Подготовленные параметры
*/
prepareDeployParams(params) {
// Создаем копию объекта, чтобы не изменять исходный
const deployParams = { ...params };
// Преобразуем суммы из строк или чисел в BigNumber, если нужно
deployParams.amounts = params.amounts.map(amount => {
if (typeof amount === 'string' && !amount.startsWith('0x')) {
return ethers.parseEther(amount).toString();
}
return amount.toString();
});
// Преобразуем proposalThreshold в BigNumber, если нужно
if (typeof deployParams.proposalThreshold === 'string' && !deployParams.proposalThreshold.startsWith('0x')) {
deployParams.proposalThreshold = ethers.parseEther(deployParams.proposalThreshold).toString();
} else {
deployParams.proposalThreshold = deployParams.proposalThreshold.toString();
}
return deployParams;
}
/**
* Сохраняет параметры деплоя во временный файл
* @param {Object} params - Параметры деплоя
* @returns {string} - Путь к файлу с параметрами
*/
saveParamsToFile(params) {
const tempDir = path.join(__dirname, '../temp');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const fileName = `dle-params-${Date.now()}.json`;
const filePath = path.join(tempDir, fileName);
fs.writeFileSync(filePath, JSON.stringify(params, null, 2));
return filePath;
}
/**
* Запускает скрипт деплоя DLE
* @param {string} paramsFile - Путь к файлу с параметрами
* @returns {Promise<Object>} - Результат деплоя
*/
runDeployScript(paramsFile, extraEnv = {}) {
return new Promise((resolve, reject) => {
const scriptPath = path.join(__dirname, '../scripts/deploy/create-dle-manual.js');
if (!fs.existsSync(scriptPath)) {
reject(new Error('Скрипт деплоя не найден: ' + scriptPath));
return;
}
// Формируем универсальные переменные окружения
const envVars = {
...process.env,
[`${extraEnv.envNetworkKey}_RPC_URL`]: extraEnv.rpcUrl,
[`${extraEnv.envNetworkKey}_PRIVATE_KEY`]: extraEnv.privateKey
};
// Запускаем скрипт с нужной сетью
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath, '--network', extraEnv.networkId], {
cwd: path.join(__dirname, '..'),
env: envVars,
stdio: 'pipe'
});
let stdout = '';
let stderr = '';
hardhatProcess.stdout.on('data', (data) => {
const output = data.toString();
stdout += output;
logger.debug(`[Hardhat] ${output}`);
});
hardhatProcess.stderr.on('data', (data) => {
const output = data.toString();
stderr += output;
logger.error(`[Hardhat Error] ${output}`);
});
hardhatProcess.on('close', (code) => {
if (code !== 0) {
logger.error(`Скрипт деплоя завершился с кодом: ${code}`);
reject(new Error(`Ошибка деплоя: ${stderr}`));
return;
}
// Пытаемся извлечь адреса контрактов из вывода
try {
const tokenAddressMatch = stdout.match(/Адрес токена: (0x[a-fA-F0-9]{40})/);
const timelockAddressMatch = stdout.match(/Адрес таймлока: (0x[a-fA-F0-9]{40})/);
const governorAddressMatch = stdout.match(/Адрес контракта Governor: (0x[a-fA-F0-9]{40})/);
if (tokenAddressMatch && timelockAddressMatch && governorAddressMatch) {
resolve({
tokenAddress: tokenAddressMatch[1],
timelockAddress: timelockAddressMatch[1],
governorAddress: governorAddressMatch[1],
success: true
});
} else {
// Если не удалось извлечь адреса, ищем файл с результатами
const dlesDir = path.join(__dirname, '../contracts-data/dles');
if (fs.existsSync(dlesDir)) {
const files = fs.readdirSync(dlesDir);
if (files.length > 0) {
// Берем самый свежий файл
const latestFile = files
.map(f => ({ name: f, time: fs.statSync(path.join(dlesDir, f)).mtime.getTime() }))
.sort((a, b) => b.time - a.time)[0].name;
const dleData = JSON.parse(fs.readFileSync(path.join(dlesDir, latestFile), 'utf8'));
resolve({
...dleData,
success: true
});
} else {
reject(new Error('Не удалось найти информацию о созданном DLE'));
}
} else {
reject(new Error('Не удалось найти директорию с результатами создания DLE'));
}
}
} catch (error) {
logger.error('Ошибка при извлечении результатов деплоя:', error);
reject(error);
}
});
});
}
/**
* Получает список всех созданных DLE
* @returns {Array<Object>} - Список DLE
*/
getAllDLEs() {
try {
const dlesDir = path.join(__dirname, '../contracts-data/dles');
if (!fs.existsSync(dlesDir)) {
return [];
}
const files = fs.readdirSync(dlesDir);
return files
.filter(file => file.endsWith('.json') && file !== 'test.json' && file !== 'node-test.json')
.map(file => {
try {
const data = JSON.parse(fs.readFileSync(path.join(dlesDir, file), 'utf8'));
// Добавляем имя файла к данным DLE для возможности удаления пустых DLE
return { ...data, _fileName: file };
} catch (error) {
logger.error(`Ошибка при чтении файла ${file}:`, error);
// Для поврежденных файлов возвращаем минимальную информацию
return {
_fileName: file,
_corrupted: true
};
}
});
} catch (error) {
logger.error('Ошибка при получении списка DLE:', error);
throw error;
}
}
}
module.exports = new DLEService();

View File

@@ -1,17 +0,0 @@
{
"name": "test0",
"symbol": "0test",
"location": "83504, México",
"isicCodes": [],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b"
],
"amounts": [
"10000000000000000000"
],
"minTimelockDelay": 1,
"votingDelay": 0,
"votingPeriod": 6646,
"proposalThreshold": "1",
"quorumPercentage": 51
}

View File

@@ -1,19 +0,0 @@
{
"name": "test1",
"symbol": "test1",
"location": "245200, România, Băile Govora",
"isicCodes": [
"5510"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b"
],
"amounts": [
"100000000000000000000"
],
"minTimelockDelay": 1,
"votingDelay": 0,
"votingPeriod": 6646,
"proposalThreshold": "1",
"quorumPercentage": 51
}

View File

@@ -1,19 +0,0 @@
{
"name": "test1",
"symbol": "test1",
"location": "245200, România, Băile Govora",
"isicCodes": [
"5510"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b"
],
"amounts": [
"100000000000000000000"
],
"minTimelockDelay": 1,
"votingDelay": 0,
"votingPeriod": 6646,
"proposalThreshold": "1",
"quorumPercentage": 51
}

View File

@@ -1,19 +0,0 @@
{
"name": "test1",
"symbol": "test1",
"location": "245200, România, Băile Govora",
"isicCodes": [
"5510"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b"
],
"amounts": [
"100000000000000000000"
],
"minTimelockDelay": 1,
"votingDelay": 0,
"votingPeriod": 6646,
"proposalThreshold": "1",
"quorumPercentage": 51
}

View File

@@ -1,19 +0,0 @@
{
"name": "test2",
"symbol": "test2",
"location": "245000, 中国, 黄山市",
"isicCodes": [
"6810"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b"
],
"amounts": [
"110000000000000000000"
],
"minTimelockDelay": 1,
"votingDelay": 0,
"votingPeriod": 6646,
"proposalThreshold": "1",
"quorumPercentage": 51
}

View File

@@ -1,23 +0,0 @@
{
"name": "тест 3",
"symbol": ест3",
"location": "07255, México",
"isicCodes": [
"6810",
"8510"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x15A4ed4759e5762174b300a4Cf51cc17ad967f4d"
],
"amounts": [
"1000000000000000000",
"100000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "10000",
"quorumPercentage": 51
}

View File

@@ -1,23 +0,0 @@
{
"name": "тест 3",
"symbol": ест3",
"location": "07255, México",
"isicCodes": [
"6810",
"8510"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x15A4ed4759e5762174b300a4Cf51cc17ad967f4d"
],
"amounts": [
"1000000000000000000",
"100000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "10000",
"quorumPercentage": 51
}

View File

@@ -1,23 +0,0 @@
{
"name": "тест 3",
"symbol": ест3",
"location": "07255, México",
"isicCodes": [
"6810",
"8510"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x15A4ed4759e5762174b300a4Cf51cc17ad967f4d"
],
"amounts": [
"1000000000000000000",
"100000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "10000",
"quorumPercentage": 51
}

View File

@@ -1,23 +0,0 @@
{
"name": "тест 3",
"symbol": ест3",
"location": "07255, México",
"isicCodes": [
"6810",
"8510"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x15A4ed4759e5762174b300a4Cf51cc17ad967f4d"
],
"amounts": [
"1000000000000000000",
"100000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "10000",
"quorumPercentage": 51
}

View File

@@ -1,23 +0,0 @@
{
"name": ест3 ",
"symbol": ест3",
"location": "07852, United States, Roxbury Township",
"isicCodes": [
"6411"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x0a98c54327253bb51f99c8218e5a7a01933d5f57"
],
"amounts": [
"1000000000000000000",
"5000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "5",
"quorumPercentage": 51,
"privateKey": "7de38b2ada1d23581342f106c8587ce26068797b3bc06656e24b9dcd1810c7b1"
}

View File

@@ -1,23 +0,0 @@
{
"name": ест3",
"symbol": "3тест",
"location": "07885, United States, Rockaway Township",
"isicCodes": [
"8411"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x15a4ed4759e5762174b300a4cf51cc17ad967f4d"
],
"amounts": [
"1000000000000000000",
"100000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "2",
"quorumPercentage": 51,
"privateKey": "7de38b2ada1d23581342f106c8587ce26068797b3bc06656e24b9dcd1810c7b1"
}

View File

@@ -1,23 +0,0 @@
{
"name": "ест3",
"symbol": ест3",
"location": "07522, United States, Paterson",
"isicCodes": [
"0111"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x0a98c54327253bb51f99c8218e5a7a01933d5f57"
],
"amounts": [
"1000000000000000000",
"1000000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "2",
"quorumPercentage": 51,
"privateKey": "7de38b2ada1d23581342f106c8587ce26068797b3bc06656e24b9dcd1810c7b1"
}

View File

@@ -1,23 +0,0 @@
{
"name": "ест3",
"symbol": ест3",
"location": "07522, United States, Paterson",
"isicCodes": [
"0111"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x0a98c54327253bb51f99c8218e5a7a01933d5f57"
],
"amounts": [
"1000000000000000000",
"1000000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "2",
"quorumPercentage": 51,
"privateKey": "7de38b2ada1d23581342f106c8587ce26068797b3bc06656e24b9dcd1810c7b1"
}

View File

@@ -1,23 +0,0 @@
{
"name": "ест3",
"symbol": ест3",
"location": "07522, United States, Paterson",
"isicCodes": [
"0111"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x0a98c54327253bb51f99c8218e5a7a01933d5f57"
],
"amounts": [
"1000000000000000000",
"1000000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "2",
"quorumPercentage": 51,
"privateKey": "7de38b2ada1d23581342f106c8587ce26068797b3bc06656e24b9dcd1810c7b1"
}

View File

@@ -1,23 +0,0 @@
{
"name": "ест3",
"symbol": ест3",
"location": "07522, United States, Paterson",
"isicCodes": [
"0111"
],
"partners": [
"0xf45aa4917b3775ba37f48aeb3dc1a943561e9e0b",
"0x0a98c54327253bb51f99c8218e5a7a01933d5f57"
],
"amounts": [
"1000000000000000000",
"1000000000000000000000000"
],
"network": "sepolia",
"minTimelockDelay": 1,
"votingDelay": 6646,
"votingPeriod": 6646,
"proposalThreshold": "2",
"quorumPercentage": 51,
"privateKey": "7de38b2ada1d23581342f106c8587ce26068797b3bc06656e24b9dcd1810c7b1"
}

482
backend/test/DLE.test.js Normal file
View File

@@ -0,0 +1,482 @@
/**
* 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 { expect } = require("chai");
const { ethers } = require("hardhat");
describe("DLE Smart Contract", function () {
let DLE;
let dle;
let owner;
let partner1;
let partner2;
let partner3;
let addrs;
beforeEach(async function () {
// Получаем аккаунты
[owner, partner1, partner2, partner3, ...addrs] = await ethers.getSigners();
// Деплоим контракт
const DLEFactory = await ethers.getContractFactory("DLE");
const config = {
name: "Digital Legal Entity",
symbol: "DLE",
location: "Moscow, Russia",
coordinates: "55.7558,37.6176",
jurisdiction: 1, // Россия
oktmo: 45000000000,
okvedCodes: ["62.01", "62.02", "62.03"],
kpp: 770101001,
quorumPercentage: 60, // 60%
initialPartners: [partner1.address, partner2.address, partner3.address],
initialAmounts: [ethers.parseEther("1000"), ethers.parseEther("1000"), ethers.parseEther("1000")],
supportedChainIds: [1, 137, 56, 42161] // Ethereum, Polygon, BSC, Arbitrum
};
dle = await DLEFactory.deploy(config, 1); // ChainId = 1 (Ethereum)
await dle.waitForDeployment();
});
describe("Деплой и инициализация", function () {
it("Должен правильно инициализировать DLE", async function () {
const dleInfo = await dle.getDLEInfo();
expect(dleInfo.name).to.equal("Digital Legal Entity");
expect(dleInfo.symbol).to.equal("DLE");
expect(dleInfo.location).to.equal("Moscow, Russia");
expect(dleInfo.jurisdiction).to.equal(1);
expect(dleInfo.isActive).to.be.true;
});
it("Должен распределить начальные токены", async function () {
expect(await dle.balanceOf(partner1.address)).to.equal(ethers.parseEther("1000"));
expect(await dle.balanceOf(partner2.address)).to.equal(ethers.parseEther("1000"));
expect(await dle.balanceOf(partner3.address)).to.equal(ethers.parseEther("1000"));
});
it("Должен установить кворум", async function () {
expect(await dle.quorumPercentage()).to.equal(60);
});
it("Должен настроить поддерживаемые цепочки", async function () {
expect(await dle.isChainSupported(1)).to.be.true; // Ethereum
expect(await dle.isChainSupported(137)).to.be.true; // Polygon
expect(await dle.isChainSupported(56)).to.be.true; // BSC
expect(await dle.isChainSupported(42161)).to.be.true; // Arbitrum
expect(await dle.isChainSupported(999)).to.be.false; // Неподдерживаемая цепочка
});
});
describe("Система голосования", function () {
it("Должен создать предложение", async function () {
const description = "Передать 100 токенов от Partner1 к Partner2";
const duration = 7 * 24 * 60 * 60; // 7 дней
const operation = ethers.AbiCoder.defaultAbiCoder().encode(
["bytes4", "bytes"],
[
"0xa9059cbb", // transfer(address,uint256) selector
ethers.AbiCoder.defaultAbiCoder().encode(
["address", "uint256"],
[partner2.address, ethers.parseEther("100")]
)
]
);
const tx = await dle.connect(partner1).createProposal(
description,
duration,
operation,
1 // governanceChainId = Ethereum
);
const receipt = await tx.wait();
const event = receipt.logs.find(log =>
log.fragment && log.fragment.name === "ProposalCreated"
);
expect(event).to.not.be.undefined;
expect(await dle.proposalCounter()).to.equal(1);
});
it("Должен голосовать за предложение", async function () {
// Создаем предложение
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Голосуем за предложение
await dle.connect(partner1).vote(0, true);
await dle.connect(partner2).vote(0, true);
const proposal = await dle.proposals(0);
expect(proposal.forVotes).to.equal(ethers.parseEther("2000")); // 1000 + 1000
expect(proposal.againstVotes).to.equal(0);
});
it("Должен проверить результат голосования", async function () {
// Создаем предложение
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Голосуем за предложение (60% от 3000 = 1800)
await dle.connect(partner1).vote(0, true); // 1000
await dle.connect(partner2).vote(0, true); // 1000
await dle.connect(partner3).vote(0, true); // 1000
const [passed, quorumReached] = await dle.checkProposalResult(0);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
});
it("Не должен позволить голосовать дважды", async function () {
// Создаем предложение
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Первое голосование
await dle.connect(partner1).vote(0, true);
// Второе голосование должно упасть
await expect(
dle.connect(partner1).vote(0, true)
).to.be.revertedWith("Already voted");
});
it("Не должен позволить голосовать без токенов", async function () {
// Создаем предложение
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Голосование без токенов должно упасть
await expect(
dle.connect(addrs[0]).vote(0, true)
).to.be.revertedWith("No tokens to vote");
});
});
describe("Мультиподпись", function () {
it("Должен создать мультиподпись операцию", async function () {
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
const duration = 7 * 24 * 60 * 60;
const tx = await dle.connect(partner1).createMultiSigOperation(
operationHash,
duration
);
const receipt = await tx.wait();
const event = receipt.logs.find(log =>
log.fragment && log.fragment.name === "MultiSigOperationCreated"
);
expect(event).to.not.be.undefined;
expect(await dle.multiSigCounter()).to.equal(1);
});
it("Должен подписать мультиподпись операцию", async function () {
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
const duration = 7 * 24 * 60 * 60;
await dle.connect(partner1).createMultiSigOperation(
operationHash,
duration
);
// Подписываем операцию
await dle.connect(partner1).signMultiSigOperation(0, true);
await dle.connect(partner2).signMultiSigOperation(0, true);
const operation = await dle.multiSigOperations(0);
expect(operation.forSignatures).to.equal(ethers.parseEther("2000")); // 1000 + 1000
expect(operation.againstSignatures).to.equal(0);
});
it("Должен проверить результат мультиподписи", async function () {
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
const duration = 7 * 24 * 60 * 60;
await dle.connect(partner1).createMultiSigOperation(
operationHash,
duration
);
// Подписываем операцию (60% от 3000 = 1800)
await dle.connect(partner1).signMultiSigOperation(0, true); // 1000
await dle.connect(partner2).signMultiSigOperation(0, true); // 1000
await dle.connect(partner3).signMultiSigOperation(0, true); // 1000
const [passed, quorumReached] = await dle.checkMultiSigResult(0);
expect(passed).to.be.true;
expect(quorumReached).to.be.true;
});
});
describe("Мульти-чейн синхронизация", function () {
it("Должен синхронизировать голоса из другой цепочки", async function () {
// Создаем предложение
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Синхронизируем голоса из другой цепочки
await dle.connect(partner1).syncVoteFromChain(
0, // proposalId
137, // fromChainId (Polygon)
ethers.parseEther("500"), // forVotes
ethers.parseEther("200"), // againstVotes
"0x" // proof
);
const proposal = await dle.proposals(0);
expect(proposal.forVotes).to.equal(ethers.parseEther("500"));
expect(proposal.againstVotes).to.equal(ethers.parseEther("200"));
});
it("Должен синхронизировать мультиподпись из другой цепочки", async function () {
const operationHash = ethers.keccak256(ethers.toUtf8Bytes("test operation"));
const duration = 7 * 24 * 60 * 60;
await dle.connect(partner1).createMultiSigOperation(
operationHash,
duration
);
// Синхронизируем мультиподпись из другой цепочки
await dle.connect(partner1).syncMultiSigFromChain(
0, // operationId
137, // fromChainId (Polygon)
ethers.parseEther("800"), // forSignatures
ethers.parseEther("300"), // againstSignatures
"0x" // proof
);
const operation = await dle.multiSigOperations(0);
expect(operation.forSignatures).to.equal(ethers.parseEther("800"));
expect(operation.againstSignatures).to.equal(ethers.parseEther("300"));
});
it("Должен проверить готовность синхронизации", async function () {
// Создаем предложение
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Проверяем готовность синхронизации
const isReady = await dle.checkSyncReadiness(0);
expect(isReady).to.be.true;
});
});
describe("Управление модулями", function () {
it("Должен добавить модуль", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TreasuryModule"));
const moduleAddress = addrs[0].address;
await dle.connect(partner1).addModule(moduleId, moduleAddress);
expect(await dle.isModuleActive(moduleId)).to.be.true;
expect(await dle.getModuleAddress(moduleId)).to.equal(moduleAddress);
});
it("Должен удалить модуль", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TreasuryModule"));
const moduleAddress = addrs[0].address;
await dle.connect(partner1).addModule(moduleId, moduleAddress);
await dle.connect(partner1).removeModule(moduleId);
expect(await dle.isModuleActive(moduleId)).to.be.false;
});
it("Не должен позволить добавить модуль без токенов", async function () {
const moduleId = ethers.keccak256(ethers.toUtf8Bytes("TreasuryModule"));
const moduleAddress = addrs[0].address;
await expect(
dle.connect(addrs[0]).addModule(moduleId, moduleAddress)
).to.be.revertedWith("Must hold tokens to add module");
});
});
describe("Проверка подключений", function () {
it("Должен проверить подключение к цепочке", async function () {
expect(await dle.checkChainConnection(1)).to.be.true; // Ethereum
expect(await dle.checkChainConnection(137)).to.be.true; // Polygon
expect(await dle.checkChainConnection(56)).to.be.true; // BSC
expect(await dle.checkChainConnection(42161)).to.be.true; // Arbitrum
expect(await dle.checkChainConnection(999)).to.be.false; // Неподдерживаемая цепочка
});
it("Должен получить количество поддерживаемых цепочек", async function () {
expect(await dle.getSupportedChainCount()).to.equal(4);
});
it("Должен получить ID поддерживаемой цепочки", async function () {
expect(await dle.getSupportedChainId(0)).to.equal(1); // Ethereum
expect(await dle.getSupportedChainId(1)).to.equal(137); // Polygon
expect(await dle.getSupportedChainId(2)).to.equal(56); // BSC
expect(await dle.getSupportedChainId(3)).to.equal(42161); // Arbitrum
});
});
describe("Исполнение операций", function () {
it("Должен исполнить предложение с передачей токенов", async function () {
// Создаем предложение для передачи токенов
const description = "Передать 100 токенов от Partner1 к Partner2";
const duration = 7 * 24 * 60 * 60;
const operation = ethers.AbiCoder.defaultAbiCoder().encode(
["bytes4", "bytes"],
[
"0xa9059cbb", // transfer(address,uint256) selector
ethers.AbiCoder.defaultAbiCoder().encode(
["address", "uint256"],
[partner2.address, ethers.parseEther("100")]
)
]
);
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Голосуем за предложение
await dle.connect(partner1).vote(0, true);
await dle.connect(partner2).vote(0, true);
await dle.connect(partner3).vote(0, true);
// Ждем окончания голосования
await ethers.provider.send("evm_increaseTime", [7 * 24 * 60 * 60]);
await ethers.provider.send("evm_mine");
// Исполняем предложение
await dle.connect(partner1).executeProposal(0);
// Проверяем, что токены переданы
expect(await dle.balanceOf(partner1.address)).to.equal(ethers.parseEther("900")); // 1000 - 100
expect(await dle.balanceOf(partner2.address)).to.equal(ethers.parseEther("1100")); // 1000 + 100
});
});
describe("Безопасность", function () {
it("Не должен позволить создать предложение без токенов", async function () {
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await expect(
dle.connect(addrs[0]).createProposal(
description,
duration,
operation,
1
)
).to.be.revertedWith("Must hold tokens to create proposal");
});
it("Не должен позволить голосовать после окончания срока", async function () {
// Создаем предложение с коротким сроком
const description = "Тестовое предложение";
const duration = 1; // 1 секунда
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Ждем окончания срока
await ethers.provider.send("evm_increaseTime", [2]);
await ethers.provider.send("evm_mine");
// Голосование должно упасть
await expect(
dle.connect(partner1).vote(0, true)
).to.be.revertedWith("Voting ended");
});
it("Не должен позволить исполнить предложение до окончания срока", async function () {
// Создаем предложение
const description = "Тестовое предложение";
const duration = 7 * 24 * 60 * 60;
const operation = "0x";
await dle.connect(partner1).createProposal(
description,
duration,
operation,
1
);
// Голосуем за предложение
await dle.connect(partner1).vote(0, true);
await dle.connect(partner2).vote(0, true);
await dle.connect(partner3).vote(0, true);
// Исполнение должно упасть
await expect(
dle.connect(partner1).executeProposal(0)
).to.be.revertedWith("Voting not ended");
});
});
});

View File

@@ -0,0 +1,977 @@
<!--
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 - Единый Смарт-Контракт с Модульной Архитектурой
## 🎯 ПОЛНОЕ ПОНИМАНИЕ ЗАДАЧИ DLE
### **1. ОСНОВНАЯ КОНЦЕПЦИЯ:**
```
DLE (Digital Legal Entity) = Универсальная цифровая юридическая организация
├── Один смарт-контракт с одинаковым адресом во всех цепочках
├── Управление только через токен-холдеров (никаких админских ролей)
├── Модульная архитектура (основной контракт + добавляемые модули)
└── Мульти-чейн синхронизация (работа в нескольких блокчейнах одновременно)
```
### **2. АРХИТЕКТУРА СИСТЕМЫ:**
#### **Деплой в множественных цепочках:**
```
Пользователь выбирает цепочки (например, 4):
├── Ethereum (ChainId = 1)
├── Polygon (ChainId = 137)
├── BSC (ChainId = 56)
└── Arbitrum (ChainId = 42161)
Деплой в каждой цепочке:
├── Одинаковый адрес DLE (через CREATE2)
├── Одинаковые токены для партнеров
└── Синхронизированное состояние
```
#### **Распределение токенов:**
```
3 партнера получают по 1000 токенов в каждой из 4 цепочек:
├── Партнер A: 1000 токенов (в 4 цепочках)
├── Партнер B: 1000 токенов (в 4 цепочках)
└── Партнер C: 1000 токенов (в 4 цепочках)
Итого: 3000 токенов в каждой цепочке
```
### **3. СИСТЕМА УПРАВЛЕНИЯ:**
#### **Голосование и мультиподпись:**
```
- Только токен-холдеры участвуют в управлении
- Каждый токен = одна голосующая сила
- Кворум настраиваемый (например, 60% от общего количества токенов)
- Мультиподпись через токен-холдеров (проверка баланса при каждой операции)
```
#### **Создание предложений:**
```
1. Токен-холдер создает предложение
2. Выбирает ОДНУ цепочку для сбора подписей/голосов
3. Сбор происходит только в выбранной цепочке
4. При достижении кворума - исполнение
5. Синхронизация исполнения во все остальные цепочки
```
### **4. МУЛЬТИ-ЧЕЙН СИНХРОНИЗАЦИЯ:**
#### **Передача токенов:**
```
Партнер A передает 500 токенов Партнеру B:
├── Ethereum: A → B (500 токенов)
├── Polygon: A → B (500 токенов)
├── BSC: A → B (500 токенов)
└── Arbitrum: A → B (500 токенов)
Синхронизация происходит во всех цепочках одновременно
```
#### **Создание и исполнение предложений:**
```
Пример: "Передать 100 токенов от A к C"
1. Создание в Ethereum
2. Выбор Polygon для кворума
3. Сбор подписей в Polygon
4. Кворум: 60% от 3000 = 1800 токенов
5. При достижении кворума:
├── Исполнение в Polygon: A → C (100 токенов)
├── Синхронизация в Ethereum: A → C (100 токенов)
├── Синхронизация в BSC: A → C (100 токенов)
└── Синхронизация в Arbitrum: A → C (100 токенов)
```
### **5. МОДУЛЬНАЯ АРХИТЕКТУРА:**
#### **Основной контракт DLE.sol:**
```
- ERC-20 токены
- Система голосования
- Мультиподпись
- Мультичейн синхронизация
- Управление модулями
- DLEInfo (юридическая информация)
```
#### **Модули (отдельные контракты):**
```
- TreasuryModule.sol (казначейство)
- HierarchicalVotingModule.sol (иерархическое голосование)
- CommunicationModule.sol (коммуникации)
- CustomModule.sol (пользовательские модули)
```
### **6. ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ:**
#### **Передача токенов:**
```
1. Создание предложения: "A → C (100 токенов)"
2. Выбор цепочки для кворума: Polygon
3. Сбор подписей в Polygon
4. При кворуме: исполнение + синхронизация во все цепочки
```
#### **Удаление таблицы в приложении:**
```
1. Создание предложения: "Удалить таблицу пользователей"
2. Выбор цепочки: Ethereum
3. Сбор подписей в Ethereum
4. При кворуме: информация добавляется в блокчейн
5. Синхронизация во все цепочки
6. Результат: таблица удаляется из приложения
```
### **7. КЛЮЧЕВЫЕ ПРИНЦИПЫ:**
#### **Безопасность:**
```
- Только токен-холдеры управляют
- Проверка баланса при каждой операции
- Кворум мультиподписей
- Синхронизация между цепочками
```
#### **Масштабируемость:**
```
- Модульная архитектура
- Добавление новых функций через модули
- Поддержка новых цепочек
- Иерархическое голосование
```
#### **Универсальность:**
```
- Один адрес во всех цепочках
- Любая цепочка для создания предложений
- Единый интерфейс управления
- Совместимость с существующими стандартами
```
---
## 🎯 ОСНОВНАЯ КОНЦЕПЦИЯ
### Один смарт-контракт с модулями
```
DLE.sol (Основной контракт) + Модули (добавляемые через голосование)
```
### Ключевые принципы:
1. **Один основной контракт** - управление токенами, голосованием, мультиподписью
2. **Модули** - специализированные функции (казначейство, иерархическое голосование, коммуникации)
3. **Только токен-холдеры** - никаких админских ролей
4. **Кворум мультиподписей** - все решения через коллективное голосование
5. **Проверка баланса** - при каждой операции
---
## 🏗️ АРХИТЕКТУРА СИСТЕМЫ
### Основной контракт DLE.sol
```
DLE.sol
├── ERC-20 токены (голосующая сила)
├── Настраиваемый кворум (% от общего количества токенов)
├── Система голосования (проверка баланса токенов)
├── Выбор цепочки для кворума (governanceChainId)
├── Синхронизация голосов между цепочками
├── Поддержка множественных цепочек
├── Мультиподпись (через токен-холдеров)
├── Мультичейн синхронизация
└── Система модулей (добавление/управление)
```
### Модули (отдельные контракты)
```
Модули
├── TreasuryModule.sol (Казначейство)
├── HierarchicalVotingModule.sol (Иерархическое голосование)
├── CommunicationModule.sol (Сообщения/звонки)
├── ExternalDLEModule.sol (Меж-DLE взаимодействие)
└── CustomModule.sol (Пользовательские модули)
```
---
## 📋 ФУНКЦИОНАЛЬНЫЕ ТРЕБОВАНИЯ
### 1. Основной контракт DLE.sol ✅
- **ERC-20 токены** - голосующая сила = количество токенов
- **Настраиваемый кворум** - процент от общего количества токенов
- **Система голосования** - только токен-холдеры участвуют
- **Выбор цепочки для кворума** - токен-холдер может выбрать любую поддерживаемую цепочку
- **Синхронизация голосов** - после голосования результаты синхронизируются между цепочками
- **Поддержка множественных цепочек** - Ethereum, Polygon, BSC и др.
- **Мультиподпись** - через токен-холдеров с проверкой баланса
- **Мультичейн синхронизация** - одинаковый адрес во всех цепочках
- **Управление модулями** - добавление/удаление через голосование
### 2. TreasuryModule.sol ✅
- **Внесение токенов** - в казну через голосование
- **Вывод токенов** - из казны через голосование
- **Распределение дивидендов** - через голосование
- **Бюджетирование** - через предложения
### 3. HierarchicalVotingModule.sol ✅
- **Владение токенами других DLE** - покупка/продажа токенов
- **Создание предложений** - для голосования в других DLE
- **Внутреннее голосование** - кворум внутри DLE для внешнего голосования
- **Внешнее голосование** - участие в голосованиях других DLE
### 4. CommunicationModule.sol ✅
- **Прием сообщений** - от токен-холдеров
- **Прием звонков** - аудио/видео коммуникации
- **История коммуникаций** - хранение и синхронизация
- **Кворум для действий** - голосование за коммуникационные операции
### 5. ExternalDLEModule.sol ✅
- **Меж-DLE взаимодействие** - управление DLE B через приложение DLE A
- **Встраивание интерфейсов** - безопасное управление
- **Проверка прав** - через мультиподпись
- **Аудит действий** - отслеживание операций
### 6. Мульти-чейн архитектура ✅
- **CREATE2 деплой** - одинаковый адрес во всех цепочках
- **Синхронизация состояния** - токены, предложения, голосования
- **Создание предложений** - в любой цепочке
- **Голосование** - в любой цепочке с синхронизацией
---
## 🔒 БЕЗОПАСНОСТЬ
### Основные принципы безопасности:
1. **Только токен-холдеры** - никаких админских ролей
2. **Проверка баланса** - при каждой операции
3. **Кворум мультиподписей** - все решения коллективные
4. **Простая логика** - минимум уязвимостей
### Защита от атак:
#### **1. Защита от Double-Spending**
```solidity
function signOperation(bytes32 _operationHash) external {
require(!hasSigned[_operationHash][msg.sender], "Already signed");
require(balanceOf(msg.sender) > 0, "No tokens to sign");
hasSigned[_operationHash][msg.sender] = true;
signatures[_operationHash] += balanceOf(msg.sender);
}
```
#### **2. Защита от Reentrancy**
```solidity
mapping(address => bool) private _locked;
modifier nonReentrant() {
require(!_locked[msg.sender], "Reentrant call");
_locked[msg.sender] = true;
_;
_locked[msg.sender] = false;
}
```
#### **3. Защита от Манипуляций**
```solidity
mapping(uint256 => uint256) public proposalSnapshots;
function createProposal(string memory _description) external returns (uint256) {
require(balanceOf(msg.sender) > 0, "Must hold tokens");
uint256 proposalId = proposalCount++;
proposalSnapshots[proposalId] = block.number;
return proposalId;
}
function vote(uint256 _proposalId, bool _support) external {
uint256 votingPower = balanceOfAt(msg.sender, proposalSnapshots[_proposalId]);
require(votingPower > 0, "No voting power");
// Голосование
}
```
---
## 🔧 ТЕХНИЧЕСКАЯ РЕАЛИЗАЦИЯ
### Основной контракт DLE.sol
```solidity
contract DLE is ERC20, ReentrancyGuard {
// Настройки
uint256 public quorumPercentage;
mapping(address => bool) public activeModules;
// Мульти-чейн
mapping(uint256 => bool) public supportedChains;
mapping(uint256 => uint256) public chainBalances;
// Предложения и голосования
mapping(uint256 => Proposal) public proposals;
mapping(uint256 => mapping(address => bool)) public hasVoted;
// Модули
mapping(bytes32 => address) public modules;
constructor(
string memory _name,
string memory _symbol,
uint256 _initialSupply,
uint256 _quorumPercentage
) ERC20(_name, _symbol) {
quorumPercentage = _quorumPercentage;
_mint(msg.sender, _initialSupply);
}
// Создание предложения
function createProposal(string memory _description, uint256 _duration) external returns (uint256) {
require(balanceOf(msg.sender) > 0, "Must hold tokens");
uint256 proposalId = proposalCount++;
proposals[proposalId] = Proposal({
id: proposalId,
description: _description,
forVotes: 0,
againstVotes: 0,
executed: false,
deadline: block.timestamp + _duration
});
return proposalId;
}
// Голосование
function vote(uint256 _proposalId, bool _support) external {
Proposal storage proposal = proposals[_proposalId];
require(block.timestamp < proposal.deadline, "Voting ended");
require(!proposal.executed, "Already executed");
require(!hasVoted[_proposalId][msg.sender], "Already voted");
uint256 votingPower = balanceOf(msg.sender);
require(votingPower > 0, "No tokens to vote");
hasVoted[_proposalId][msg.sender] = true;
if (_support) {
proposal.forVotes += votingPower;
} else {
proposal.againstVotes += votingPower;
}
}
// Добавление модуля
function addModule(bytes32 _moduleId, address _moduleAddress) external {
require(checkProposalResult(getLastProposalId()).passed, "Proposal not passed");
modules[_moduleId] = _moduleAddress;
activeModules[_moduleAddress] = true;
emit ModuleAdded(_moduleId, _moduleAddress);
}
}
```
### Модуль казначейства TreasuryModule.sol
```solidity
contract TreasuryModule {
DLE public dle;
uint256 public treasuryBalance;
constructor(address _dle) {
dle = DLE(_dle);
}
function depositToTreasury(uint256 _amount) external {
require(dle.balanceOf(msg.sender) >= _amount, "Insufficient balance");
require(dle.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
treasuryBalance += _amount;
emit TreasuryDeposit(msg.sender, _amount);
}
function withdrawFromTreasury(address _recipient, uint256 _amount) external {
require(dle.checkProposalResult(getTreasuryProposalId()).passed, "Proposal not passed");
require(_amount <= treasuryBalance, "Insufficient treasury");
treasuryBalance -= _amount;
require(dle.transfer(_recipient, _amount), "Transfer failed");
emit TreasuryWithdrawal(_recipient, _amount);
}
}
```
### Модуль иерархического голосования HierarchicalVotingModule.sol
```solidity
contract HierarchicalVotingModule {
DLE public dle;
mapping(address => uint256) public externalDLEBalances;
mapping(uint256 => ExternalVotingProposal) public externalVotingProposals;
struct ExternalVotingProposal {
address targetDLE;
uint256 targetProposalId;
bool support;
string reason;
bool executed;
uint256 internalProposalId;
}
constructor(address _dle) {
dle = DLE(_dle);
}
function buyTokensFromOtherDLE(address _otherDLE, uint256 _amount) external {
require(dle.balanceOf(msg.sender) > 0, "Must hold DLE tokens");
require(_amount > 0, "Amount must be positive");
require(dle.transferFrom(msg.sender, address(this), _amount), "Transfer failed");
externalDLEBalances[_otherDLE] += _amount;
emit ExternalDLEInvestment(_otherDLE, _amount);
}
function createExternalVotingProposal(
address _targetDLE,
uint256 _targetProposalId,
bool _support,
string memory _reason
) external returns (uint256) {
require(dle.balanceOf(msg.sender) > 0, "Must hold DLE tokens");
require(externalDLEBalances[_targetDLE] > 0, "No tokens in target DLE");
string memory description = string(abi.encodePacked(
"Vote in DLE ", _targetDLE, " on proposal ", _targetProposalId.toString()
));
uint256 internalProposalId = dle.createProposal(description, 7 days);
uint256 proposalId = externalProposalCount++;
externalVotingProposals[proposalId] = ExternalVotingProposal({
targetDLE: _targetDLE,
targetProposalId: _targetProposalId,
support: _support,
reason: _reason,
executed: false,
internalProposalId: internalProposalId
});
return proposalId;
}
function executeExternalVote(uint256 _proposalId) external {
ExternalVotingProposal storage proposal = externalVotingProposals[_proposalId];
require(!proposal.executed, "Already executed");
(bool passed, bool quorumReached) = dle.checkProposalResult(proposal.internalProposalId);
require(passed && quorumReached, "Internal proposal not passed");
uint256 votingPower = externalDLEBalances[proposal.targetDLE];
emit ExternalDLEVote(proposal.targetDLE, proposal.targetProposalId, proposal.support, votingPower);
proposal.executed = true;
}
}
```
---
## 🌐 МУЛЬТИ-ЧЕЙН АРХИТЕКТУРА
### Мульти-чейн функциональность
```solidity
// Создание предложения с выбором цепочки для кворума
function createProposal(
string memory _description,
uint256 _duration,
uint256 _governanceChainId
) external returns (uint256);
// Синхронизация голосов между цепочками
function syncVoteFromChain(
uint256 _proposalId,
uint256 _fromChainId,
uint256 _forVotes,
uint256 _againstVotes,
bytes memory _proof
) external;
// Проверка поддерживаемых цепочек
function isChainSupported(uint256 _chainId) external view returns (bool);
```
### Синхронизация между цепочками
```solidity
// Синхронизация токенов
function syncTokenBalance(
address holder,
uint256 balance,
uint256 fromChainId
) external;
// Синхронизация предложений
function syncProposal(
uint256 proposalId,
Proposal memory proposal,
uint256 fromChainId
) external;
// Синхронизация голосов
function syncVote(
uint256 proposalId,
address voter,
bool support,
uint256 fromChainId
) external;
```
---
## ⚠️ ПРОБЛЕМЫ ДОСТУПНОСТИ ЦЕПОЧЕК
### **Сценарий: Из 4 цепочек доступны только 2**
#### **1. Проблема при деплое:**
```
Планируемый деплой: Ethereum, Polygon, BSC, Arbitrum
Доступные цепочки: Ethereum, Polygon
Недоступные: BSC, Arbitrum (ошибка подключения/интернет)
```
#### **2. Проблема при синхронизации:**
```
Исполнение в Ethereum → Синхронизация в остальные цепочки
✅ Polygon: синхронизация успешна
❌ BSC: ошибка подключения
❌ Arbitrum: нет интернета
```
### **ПРОСТОЕ И БЕЗОПАСНОЕ РЕШЕНИЕ:**
#### **Принцип: 100% или ничего**
```
Перед любым действием:
1. ✅ Проверить все подключения
2. ✅ Убедиться в доступности всех цепочек
3. ✅ Выполнить операцию на 100%
4. ❌ При любом сбое - отменить всё с указанием причины
```
#### **1. Проверка подключений перед деплоем**
```solidity
contract DLE is ERC20, ReentrancyGuard {
// Статус проверки цепочек
mapping(uint256 => bool) public chainConnectionStatus;
// События
event PreDeployCheckStarted(uint256[] chainIds);
event PreDeployCheckCompleted(bool allChainsAvailable, string[] unavailableChains);
event DeployCancelled(string reason);
/**
* @dev Проверить подключения перед деплоем
*/
function checkAllConnections(uint256[] memory _chainIds) external returns (bool) {
require(balanceOf(msg.sender) > 0, "Must hold tokens");
emit PreDeployCheckStarted(_chainIds);
bool allAvailable = true;
string[] memory unavailableChains = new string[](_chainIds.length);
uint256 unavailableCount = 0;
for (uint256 i = 0; i < _chainIds.length; i++) {
bool isAvailable = checkChainConnection(_chainIds[i]);
chainConnectionStatus[_chainIds[i]] = isAvailable;
if (!isAvailable) {
allAvailable = false;
unavailableChains[unavailableCount] = getChainName(_chainIds[i]);
unavailableCount++;
}
}
emit PreDeployCheckCompleted(allAvailable, unavailableChains);
if (!allAvailable) {
string memory reason = string(abi.encodePacked(
"Deploy cancelled: Chains unavailable - ",
joinStrings(unavailableChains, unavailableCount)
));
emit DeployCancelled(reason);
}
return allAvailable;
}
/**
* @dev Проверить подключение к конкретной цепочке
*/
function checkChainConnection(uint256 _chainId) internal view returns (bool) {
// Здесь должна быть реальная проверка подключения
// Для примера используем простую проверку
return _chainId > 0 && _chainId <= 999999;
}
/**
* @dev Получить название цепочки
*/
function getChainName(uint256 _chainId) internal pure returns (string memory) {
if (_chainId == 1) return "Ethereum";
if (_chainId == 137) return "Polygon";
if (_chainId == 56) return "BSC";
if (_chainId == 42161) return "Arbitrum";
return "Unknown";
}
}
```
#### **2. Проверка перед синхронизацией**
```solidity
contract DLE is ERC20, ReentrancyGuard {
/**
* @dev Проверить все цепочки перед синхронизацией
*/
function checkSyncReadiness(uint256 _proposalId) external returns (bool) {
require(balanceOf(msg.sender) > 0, "Must hold tokens");
Proposal storage proposal = proposals[_proposalId];
require(proposal.id == _proposalId, "Proposal does not exist");
// Проверяем все поддерживаемые цепочки
bool allChainsReady = true;
string[] memory unavailableChains = new string[](supportedChainIds.length);
uint256 unavailableCount = 0;
for (uint256 i = 0; i < supportedChainIds.length; i++) {
uint256 chainId = supportedChainIds[i];
bool isReady = checkChainConnection(chainId);
if (!isReady) {
allChainsReady = false;
unavailableChains[unavailableCount] = getChainName(chainId);
unavailableCount++;
}
}
if (!allChainsReady) {
string memory reason = string(abi.encodePacked(
"Sync cancelled: Chains unavailable - ",
joinStrings(unavailableChains, unavailableCount)
));
emit SyncCancelled(_proposalId, reason);
}
return allChainsReady;
}
/**
* @dev Синхронизация только при 100% готовности
*/
function syncToAllChains(uint256 _proposalId) external {
require(checkSyncReadiness(_proposalId), "Not all chains ready");
// Выполняем синхронизацию во все цепочки
for (uint256 i = 0; i < supportedChainIds.length; i++) {
uint256 chainId = supportedChainIds[i];
syncToChain(_proposalId, chainId);
}
emit SyncCompleted(_proposalId, supportedChainIds);
}
}
```
#### **3. Проверка перед голосованием**
```solidity
contract DLE is ERC20, ReentrancyGuard {
/**
* @dev Проверить цепочку перед голосованием
*/
function checkVotingChain(uint256 _chainId) external returns (bool) {
require(balanceOf(msg.sender) > 0, "Must hold tokens");
bool isAvailable = checkChainConnection(_chainId);
if (!isAvailable) {
string memory reason = string(abi.encodePacked(
"Voting cancelled: Chain ",
getChainName(_chainId),
" unavailable"
));
emit VotingCancelled(reason);
}
return isAvailable;
}
/**
* @dev Создать предложение только при доступности цепочки
*/
function createProposalWithChainCheck(
string memory _description,
uint256 _duration,
uint256 _votingChainId
) external returns (uint256) {
require(checkVotingChain(_votingChainId), "Voting chain not available");
return createProposal(_description, _duration, _votingChainId);
}
}
```
### **СТРАТЕГИИ ОБРАБОТКИ:**
#### **1. При деплое (проверка всех цепочек):**
```
Планируемый деплой: Ethereum, Polygon, BSC, Arbitrum
Проверка:
✅ Ethereum: доступен
✅ Polygon: доступен
❌ BSC: недоступен
❌ Arbitrum: недоступен
Результат:
❌ Деплой ОТМЕНЕН
📋 Причина: "BSC, Arbitrum недоступны"
🔔 Уведомление токен-холдеров
```
#### **2. При синхронизации (проверка всех цепочек):**
```
Исполнение в Ethereum → Синхронизация
Проверка:
✅ Ethereum: доступен
✅ Polygon: доступен
❌ BSC: недоступен
❌ Arbitrum: недоступен
Результат:
❌ Синхронизация ОТМЕНЕНА
📋 Причина: "BSC, Arbitrum недоступны"
🔔 Уведомление токен-холдеров
```
#### **3. При голосовании (проверка выбранной цепочки):**
```
Планируемое голосование: Polygon
Проверка:
❌ Polygon: недоступен
Результат:
❌ Голосование ОТМЕНЕНО
📋 Причина: "Polygon недоступен"
🔔 Уведомление токен-холдеров
```
### **ПРИМЕРЫ СЦЕНАРИЕВ:**
#### **Сценарий 1: Деплой с проблемами**
```
Планируемый деплой: 4 цепочки
Проверка подключений:
✅ Ethereum: доступен
✅ Polygon: доступен
❌ BSC: ошибка подключения
❌ Arbitrum: нет интернета
Действия:
1. ❌ Деплой ОТМЕНЕН
2. 📋 Причина: "BSC, Arbitrum недоступны"
3. 🔔 Уведомление токен-холдеров
4. ⏰ Ожидание восстановления подключений
5. 🔄 Повторная проверка при восстановлении
```
#### **Сценарий 2: Синхронизация с проблемами**
```
Исполнение в Ethereum → Синхронизация
Проверка всех цепочек:
✅ Ethereum: доступен
✅ Polygon: доступен
❌ BSC: ошибка подключения
❌ Arbitrum: нет интернета
Действия:
1. ❌ Синхронизация ОТМЕНЕНА
2. 📋 Причина: "BSC, Arbitrum недоступны"
3. 🔔 Уведомление токен-холдеров
4. ⏰ Ожидание восстановления подключений
5. 🔄 Повторная проверка при восстановлении
```
#### **Сценарий 3: Голосование с проблемами**
```
Планируемое голосование: Polygon
Проверка Polygon:
❌ Polygon: недоступен
Действия:
1. ❌ Голосование ОТМЕНЕНО
2. 📋 Причина: "Polygon недоступен"
3. 🔔 Уведомление токен-холдеров
4. ⏰ Ожидание восстановления подключения
5. 🔄 Повторная проверка при восстановлении
```
### **ПРЕИМУЩЕСТВА ПРОСТОГО РЕШЕНИЯ:**
#### **✅ Безопасность:**
- Никаких частичных операций
- Никаких рассинхронизаций
- Четкие причины отмены
#### **✅ Простота:**
- Понятная логика
- Минимум кода
- Легко отлаживать
#### **✅ Надежность:**
- 100% или ничего
- Предсказуемое поведение
- Нет скрытых состояний
#### **✅ Прозрачность:**
- Четкие причины отмены
- Уведомления токен-холдеров
- Понятная логика
### **КЛЮЧЕВЫЕ ПРИНЦИПЫ:**
#### **1. Проверка перед действием:**
```
Любое действие = Проверка всех подключений → Выполнение или Отмена
```
#### **2. 100% или ничего:**
```
Все цепочки доступны → Выполнить
Любая цепочка недоступна → Отменить с причиной
```
#### **3. Четкие причины:**
```
Отмена = Конкретная причина + Уведомление токен-холдеров
```
#### **4. Простота восстановления:**
```
Проблема решена → Повторная проверка → Выполнение
```
**Теперь система DLE работает по принципу "100% или ничего" - просто, безопасно и надежно!**
---
## 📊 ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ
### Пример 1: Деплой в 4 цепочках
```
1. Пользователь выбирает 4 цепочки: Ethereum, Polygon, BSC, Arbitrum
2. Деплой DLE в каждой цепочке с одинаковым адресом
3. Партнеры получают по 1000 токенов в каждой цепочке:
- Партнер A: 1000 токенов (в 4 цепочках)
- Партнер B: 1000 токенов (в 4 цепочках)
- Партнер C: 1000 токенов (в 4 цепочках)
4. Передача токенов синхронизируется между всеми цепочками
```
### Пример 2: Создание предложения и сбор подписей
```
1. Партнер A создает предложение "Передать 100 токенов от A к C"
2. Выбирает одну цепочку (например, Polygon) для сбора подписей
3. Сбор подписей происходит только в выбранной цепочке
4. Кворум: 60% от 3000 = 1800 токенов
5. При достижении кворума - исполнение в Polygon
6. Синхронизация исполнения во все остальные цепочки
```
### Пример 3: Исполнение и синхронизация
```
1. Кворум достигнут в Polygon (1800+ токенов)
2. Исполнение в Polygon: A → C (100 токенов)
3. Синхронизация в Ethereum: A → C (100 токенов)
4. Синхронизация в BSC: A → C (100 токенов)
5. Синхронизация в Arbitrum: A → C (100 токенов)
6. Результат: токены переданы во всех 4 цепочках
```
### Пример 4: Удаление таблицы в приложении
```
1. Партнер A создает предложение "Удалить таблицу пользователей"
2. Выбирает Ethereum для сбора подписей
3. Кворум достигнут в Ethereum
4. Исполнение в Ethereum: информация добавляется в блокчейн
5. Синхронизация во все цепочки: информация добавляется везде
6. Результат: таблица удаляется из приложения
```
---
## 🎯 ПРЕИМУЩЕСТВА АРХИТЕКТУРЫ
### ✅ Простота
- Один основной контракт с модулями
- Только токен-холдеры участвуют в управлении
- Проверка баланса при каждой операции
- Настраиваемый кворум для всех решений
### ✅ Безопасность
- Никаких админских ролей
- Простая логика мультиподписи
- Защита от основных атак
- Прозрачность всех операций
### ✅ Масштабируемость
- Модульная архитектура
- Добавление новых функций через модули
- Мульти-чейн синхронизация
- Иерархическое голосование
### ✅ Универсальность
- Один адрес во всех цепочках
- Любая цепочка для создания предложений
- Единый интерфейс управления
- Совместимость с существующими стандартами
---
## 📈 ЗАКЛЮЧЕНИЕ
**DLE - это единый смарт-контракт с модульной архитектурой, который:**
1. **Управляется только токен-холдерами** через кворум мультиподписей
2. **Проверяет баланс токенов** при каждой операции
3. **Использует модули** для специализированных функций
4. **Синхронизируется между цепочками** с одинаковым адресом
5. **Поддерживает иерархическое голосование** через отдельный модуль
**Ключевые принципы:**
- Простота и безопасность
- Коллективное управление
- Модульная архитектура
- Мульти-чейн синхронизация
**Результат:** Безопасная, масштабируемая и универсальная система DLE! 🚀

View File

@@ -1,3 +1,15 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
# Детальный план задач: Управление Digital Legal Entity (DLE)
## Общие принципы разработки

View File

@@ -1,3 +1,15 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
# 🔐 Полное шифрование всех таблиц в DLE
## 📋 Обзор

View File

@@ -1,3 +1,15 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
# 🔄 Перенос зашифрованных данных между серверами
## 📋 Обзор

View File

@@ -1,61 +1,91 @@
<!--
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
-->
# Смарт Контракты Digital Legal Entity (DLE)
## Основной смарт контракт DLE
### Концепция
Адрес смарт контракта одновременно выполняет функции банковского счета и контактных данных (как email/телефонный номер).
**Один смарт-контракт** с ERC-20 токенами, настраиваемым кворумом, мультиподписью и модулями. Адрес контракта одновременно выполняет функции банковского счета и контактных данных.
### Архитектура
```
DLE.sol (Один контракт)
├── ERC-20 токены (голосующая сила)
├── Настраиваемый кворум (% от общего количества токенов)
├── Система голосования (проверка баланса токенов)
├── Мультиподпись (через токен-холдеров)
├── Модули (добавляемые через голосование)
└── Мультичейн синхронизация
```
### Требования
#### 1. Токен управления
- Пользователь заполняет форму в приложении для ручного деплоя
- Токен дает права голоса держателям
токен передается только через кворум мультиподписей
#### 1. Токен управления (ERC-20)
- **Описание**: Стандартный ERC-20 токен для управления DLE
- **Функции**:
- Минтинг токенов при создании DLE
- Распределение токенов между участниками
- **Голосующая сила = количество токенов**
- Проверка баланса токенов при каждой операции
#### 2. Казначейские функции
- Управление финансами через голосование токен-холдеров
- Мультиподпись токен-холдеров для выполнения транзакций (проверка баланса)
- Функции банковского счета
- НЕТ админских ролей - все через коллективное голосование
#### 2. Настраиваемый кворум
- **Описание**: Процент от общего количества токенов для принятия решений
- **Функции**:
- Настройка кворума при создании DLE
- Изменение кворума через голосование
- Расчет кворума: `(totalSupply * quorumPercentage) / 100`
- Проверка достижения кворума для каждого решения
#### 3. Система голосования с мультиподписью
- Голосование за деплой дополнительных смарт контрактов через мультиподпись
- Кворум подписей токен-холдеров для принятия решений (проверка баланса)
- Токен-холдеры управляют всеми операциями через систему подписей
- Настраиваемые таймлоки для каждого предложения
- НЕТ админских ролей - только коллективное управление
#### 3. Система голосования через токен-холдеров
- **Описание**: Только владельцы токенов участвуют в управлении
- **Функции**:
- Создание предложений (любым токен-холдером)
- Голосование пропорционально балансу токенов
- Проверка баланса токенов при каждой подписи
- Выполнение предложений после достижения кворума
- **НЕТ админских ролей - только коллективное управление**
#### 4. Система настраиваемых таймлоков (отдельный модуль)
- **Архитектура**: Отдельный контракт TimelockController, создаваемый при деплое DLE
- **Настройки при деплое**:
- Минимальная задержка таймлока (настраиваемая)
- Максимальная задержка таймлока (настраиваемая)
- Задержка по умолчанию (настраиваемая)
- Возможность настройки индивидуальной задержки для каждого предложения
- **Функции модуля**:
- Инициатор предложения устанавливает индивидуальную задержку
- Динамическое изменение параметров таймлока через голосование
- Отмена предложений до истечения таймлока
- Выполнение предложений после истечения таймлока
- **Пример параметров**: 1 день задержки, 7 дней голосования, 2 дня timelock
#### 4. Мультиподпись через токен-холдеров
- **Описание**: Система подписей для критических операций
- **Функции**:
- Подписание операций токен-холдерами
- Проверка баланса токенов при подписи
- Сбор подписей до достижения кворума
- Выполнение операций после сбора подписей
#### 5. Модульная система
- Настройка отдельных модулей с формами в приложении
- Деплой дополнительных смарт контрактов через голосование
- Расширяемость функционала
#### 5. Казначейские функции
- **Описание**: Управление финансами DLE через голосование
- **Функции**:
- Внесение токенов в казну
- Вывод токенов из казны через голосование
- Распределение дивидендов
- Бюджетирование через предложения
#### 6. Коммуникационные функции
- Прием сообщений от криптокошельков и смарт контрактов
- Прием звонков (аудио/видео) от владельцев кошельков
- Адрес контракта = универсальный контакт (как email/телефон)
- Кворум мультиподписей токен-холдеров для приема звонков и отправки сообщений
- НЕТ админских ролей - все через коллективное голосование
#### 6. Модульная система
- **Описание**: Добавление новых функций через модули
- **Функции**:
- Добавление модулей через голосование
- Управление модулями через голосование
- Изоляция модулей от основного контракта
- Обновление модулей через голосование
#### 7. Функции акционерного общества
- Права голоса пропорционально токенам
- Управление через коллективные решения токен-холдеров
- Прозрачность всех операций
- НЕТ единичных администраторов - только коллективное управление через кворум подписей
#### 7. Коммуникационные функции
- **Описание**: Прием сообщений и звонков
- **Функции**:
- Прием текстовых сообщений
- Прием аудио/видео звонков
- Кворум для коммуникационных действий
- Хранение истории коммуникаций
### Иерархическая система голосования DLE
@@ -123,7 +153,7 @@ function DLEBManagementInterface({ dleBAddress }) {
### Технические требования
- Один адрес = универсальная точка входа
- Безопасность мультиподписи
- Безопасность мультиподписи через токен-холдеров
- Масштабируемость через модули
- Поддержка аудио/видео коммуникации
- Совместимость с существующими стандартами (ERC-20, ERC-721)
@@ -149,22 +179,11 @@ DLE должен функционировать в нескольких блок
- Все операции с токенами только через мультиподпись и кворум
- Защита от double-spending и рассинхронизации
#### 3. Cross-chain система голосования
- Возможность выбора сети для инициации голосования
- Кворум рассчитывается по токенам в выбранной сети
- Результаты голосования синхронизируются во все сети
- Выполнение решений может происходить в любой из развернутых сетей
#### 4. Cross-chain синхронизация операций
- Функция связывания операций между сетями
- Атомарное выполнение операций во всех целевых сетях
- Система откатов при сбоях в одной из сетей
- Таймауты и fallback механизмы
#### 3. Single-Chain Governance система
- Инициатор предложения выбирает ОДНУ сеть для голосования
- Все токен-холдеры участвуют в мультиподписи только в выбранной сети
- Инициатор устанавливает таймлок для предложения
- Проверка балансов токен-холдеров при подписании
- Исполнение решения происходит во всех целевых сетях
#### 4. Упрощенная cross-chain архитектура
@@ -180,38 +199,13 @@ DLE должен функционировать в нескольких блок
- Возможность добавления новых сетей после первоначального деплоя
### Поддерживаемые сети
- деинамическое и только те которые добавлены в таблицу rpc провайдеров
- Динамическое добавление сетей через таблицу RPC провайдеров
### Архитектура синхронизации
#### Cross-Chain операции
#### Single-Chain Governance операции
```solidity
contract DLE_CrossChainSync {
struct CrossChainOperation {
uint256[] targetChains; // Целевые сети для выполнения
bytes[] callData; // Данные для выполнения
uint256 executedChains; // Количество выполненных сетей
bool isCompleted; // Статус завершения
uint256 timeout; // Время истечения операции
}
function executeMultiChainOperation(bytes32 operationId, uint256 chainId) external;
function syncTokenOperation(address[] holders, uint256[] amounts, uint256[] chains) external;
}
```
#### Типы синхронизируемых операций
- **Передача токенов** между партнерами
- **Минтинг новых токенов** для новых участников
- **Сжигание токенов** при выходе участников
- **Изменение прав доступа** и ролей
- **Выполнение решений голосования**
### Упрощенная архитектура governance
#### Single-Chain Governance контракт
```solidity
contract DLE_Governance {
contract DLE_SingleChainGovernance {
struct Proposal {
bytes operation; // Операция для выполнения
uint256[] targetChains; // Целевые сети для исполнения
@@ -240,20 +234,6 @@ contract DLE_Governance {
- **Emergency действия** - пауза, разморозка, восстановление
- **Модульные операции** - добавление/удаление функциональности
### Безопасность мульти-чейн операций
#### Требования к безопасности
- Кворум для cross-chain операций не менее 67%
- Подтверждение операций в нескольких блоках (минимум 12)
- Система откатов при сбоях синхронизации
- Мониторинг состояния во всех сетях
#### Механизмы защиты
- **Timelock для критических операций** - задержка выполнения
- **Emergency pause** - остановка операций при обнаружении проблем
- **Fallback сеть** - резервная сеть при сбоях основной
- **Валидация состояния** - проверка консистентности данных
### Безопасность Single-Chain Governance
#### Требования к безопасности
@@ -289,13 +269,6 @@ contract DLE_Governance {
- Отображение прогресса сбора подписей
- Статус исполнения в целевых сетях
### Технические требования мульти-чейн
- Детерминистический деплой через CREATE2
- Синхронизация состояния между сетями
- Защита от MEV-атак при cross-chain операциях
- Оптимизация газа для массовых операций
- Поддержка различных bridge протоколов
### Технические требования упрощенной архитектуры
- Детерминистический деплой через CREATE2
- Single-chain governance для безопасности
@@ -326,8 +299,6 @@ ERC-4337 предоставляет стандартную инфраструк
### Варианты технической реализации
#### Вариант 1: Собственный контракт + ERC-4337
**Создание собственного DLE контракта с использованием компонентов ERC-4337**
@@ -346,7 +317,6 @@ ERC-4337 предоставляет стандартную инфраструк
└── Multi-Chain Execution
```
### Лицензия ERC-4337
ERC-4337 распространяется под лицензией **CC0** (Public Domain), что означает полную свободу использования.

View File

@@ -1,429 +0,0 @@
# Техническое задание: Digital Legal Entity (DLE)
## 1. Общие сведения
### 1.1 Назначение системы
Создание смарт-контракта DLE (Digital Legal Entity) - универсальной цифровой юридической сущности, которая объединяет функции акционерного общества, банковского счета и контактных данных в одном адресе блокчейна.
### 1.2 Цель разработки
Разработка безопасного, масштабируемого и функционального смарт-контракта для токенизации акционерных обществ с поддержкой иерархического управления и коммуникационных функций.
### 1.3 Технический подход
Использование готовых проаудированных компонентов (OpenZeppelin, ERC-4337) с добавлением уникальной бизнес-логики DLE.
## 2. Функциональные требования
### 2.1 Основные функции DLE
#### 2.1.1 Токен управления
- **Описание**: ERC-20 токен для управления DLE
- **Функции**:
- Минтинг токенов при создании DLE
- Распределение токенов между участниками
- Делегирование голосов
- Проверка баланса токенов
#### 2.1.2 Система голосования с мультиподписью
- **Описание**: Система принятия решений через кворум подписей токен-холдеров
- **Функции**:
- Создание предложений (любым токен-холдером)
- Сбор подписей от токен-холдеров (проверка баланса токенов)
- Проверка кворума подписей (% от общего количества токенов)
- Выполнение предложений после достижения кворума
- Динамический выбор governance сети при создании предложения
- Динамическая настройка таймлока для каждого предложения
- **Принцип**: НЕТ админских ролей - ВСЕ управление через токен-холдеров
#### 2.1.3 Настраиваемые таймлоки
- **Описание**: Система задержек для каждого предложения через отдельный модуль TimelockController
- **Архитектура**: Отдельный контракт TimelockController, создаваемый при деплое DLE
- **Параметры**:
- Минимальная задержка таймлока (настраиваемая при деплое)
- Максимальная задержка таймлока (настраиваемая при деплое)
- Задержка по умолчанию (настраиваемая при деплое)
- Возможность настройки индивидуальной задержки для каждого предложения
- **Функции**:
- Инициатор предложения устанавливает индивидуальную задержку
- Динамическое изменение параметров таймлока через голосование
- Отмена предложений до истечения таймлока
- Выполнение предложений после истечения таймлока
#### 2.1.4 Казначейские функции
- **Описание**: Управление финансами DLE
- **Функции**:
- Прием и отправка криптовалют
- Управление токенами
- Распределение дивидендов
- Бюджетирование
#### 2.1.5 Коммуникационные функции
- **Описание**: Прием сообщений и звонков
- **Функции**:
- Прием текстовых сообщений
- Прием аудио/видео звонков
- Кворум для коммуникационных действий
- Хранение истории коммуникаций
#### 2.1.6 Модульная система
- **Описание**: Расширяемая архитектура
- **Функции**:
- Добавление новых модулей
- Управление модулями через голосование
- Изоляция модулей
- Обновление модулей
### 2.2 Иерархическая система голосования
#### 2.2.1 Меж-DLE взаимодействие
- **Описание**: DLE может владеть токенами других DLE
- **Функции**:
- Проверка владения токенами других DLE
- Сбор кворума подписей для внешнего голосования
- Участие в голосовании других DLE
- Пропорциональный подсчет голосов
#### 2.2.2 Кворум подписей
- **Описание**: Система сбора подписей для внешнего голосования
- **Функции**:
- Создание запросов на внешнее голосование
- Сбор подписей от токен холдеров
- Проверка достижения кворума
- Активация голоса в целевой DLE
### 2.3 Межприложное взаимодействие
#### 2.3.1 Встраивание интерфейсов
- **Описание**: Управление DLE B через приложение DLE A
- **Функции**:
- Встраивание интерфейса управления
- Безопасное подписание транзакций
- Проверка прав доступа
- Аудит действий
### 2.4 Мульти-чейн архитектура
#### 2.4.1 CREATE2 детерминистический деплой
- **Описание**: Создание DLE с одинаковым адресом во всех EVM-сетях
- **Функции**:
- Использование CREATE2 opcode для предсказуемых адресов
- Factory контракт с фиксированным адресом во всех сетях
- Генерация детерминистического salt на основе пользовательских данных
- Предварительное вычисление адреса DLE до деплоя
#### 2.4.2 Синхронизация токенов управления
- **Описание**: Синхронное управление токенами во всех развернутых сетях
- **Функции**:
- Одинаковое распределение токенов для партнеров во всех сетях
- Cross-chain синхронизация операций с токенами
- Атомарное выполнение операций во всех целевых сетях
- Защита от рассинхронизации и double-spending
#### 2.4.3 Cross-chain система голосования
- **Описание**: Голосование с выбором сети и синхронизацией результатов
- **Функции**:
- Выбор сети для инициации голосования
- Расчет кворума по токенам в выбранной сети
- Синхронизация результатов во все развернутые сети
- Выполнение решений в любой из целевых сетей
#### 2.4.3 Single-Chain Governance система
- **Описание**: Упрощенная система голосования в одной выбранной сети
- **Функции**:
- Инициатор выбирает governance сеть для голосования
- Все токен-холдеры участвуют только в выбранной сети
- Инициатор устанавливает таймлок для предложения
- Проверка балансов токенов при каждой подписи
- Исполнение решения во всех целевых сетях
#### 2.4.4 Мульти-сетевой деплой
- **Описание**: Одновременный деплой в несколько блокчейн-сетей
- **Функции**:
- Выбор множественных сетей из интерфейса
- Автоматический расчет общей стоимости деплоя
- Параллельное развертывание во всех выбранных сетях
- Возможность добавления новых сетей после первоначального деплоя
#### 2.4.5 Упрощенные cross-chain операции
- **Описание**: Исполнение решений во всех целевых сетях после single-chain голосования
- **Функции**:
- Атомарное исполнение во всех выбранных сетях
- Fallback исполнение в доступных сетях при сбоях
- Мониторинг статуса исполнения операций
- Откат операций при критических сбоях
## 3. Технические требования
### 3.1 Архитектура смарт-контракта
#### 3.1.1 Основная структура
```solidity
contract DLE is ERC20, Governor, TimelockController {
// Ваша уникальная логика DLE
// + готовые компоненты с аудитом
// + проверенные паттерны
}
```
#### 3.1.2 Компоненты для интеграции
- **ERC-20** - токен управления DLE
- **Governor** - система голосования с мультиподписью
- **TimelockController** - настраиваемые таймлоки
- **Account Abstraction** - универсальность адреса
#### 3.1.3 Мульти-чейн архитектура
```solidity
// Factory для детерминистического деплоя
contract DLEFactory {
function createDLE(
bytes32 salt,
DLEConfig memory config,
uint256[] memory targetChains
) external returns (address predictedAddress);
function predictAddress(bytes32 salt, DLEConfig memory config)
external view returns (address);
}
// Single-Chain Governance
contract DLE_Governance {
struct Proposal {
bytes operation;
uint256[] targetChains;
uint256 timelock;
uint256 governanceChain;
address initiator;
bytes[] signatures;
bool executed;
}
function createProposal(bytes calldata operation, uint256[] calldata targetChains, uint256 timelockDelay) external;
function signProposal(uint256 proposalId) external onlyTokenHolder;
function executeProposal(uint256 proposalId) external;
}
// Основной контракт DLE с упрощенной архитектурой
contract DLE is ERC20, Governor, TimelockController {
// Single-chain governance
mapping(uint256 => bool) public supportedChains;
mapping(uint256 => Proposal) public proposals;
// Проверка токен-холдеров
modifier onlyTokenHolder() {
require(balanceOf(msg.sender) > 0, "Must hold tokens");
_;
}
// Исполнение в целевых сетях
function executeInTargetChains(bytes calldata operation, uint256[] calldata chains) external;
}
```
#### 3.1.4 Компоненты для интеграции
- **ERC-20** - токен управления DLE
- **Governor** - система голосования с мультиподписью
- **TimelockController** - отдельный модуль настраиваемых таймлоков
- **Account Abstraction** - универсальность адреса
- **CREATE2 Factory** - детерминистический деплой
- **Single-Chain Governance** - упрощенное управление через одну сеть
### 3.2 Готовые компоненты с аудитом
#### 3.2.1 OpenZeppelin (аудит: ConsenSys Diligence)
- ERC-20 - токены управления
- Governance - система голосования
- Access Control - роли и разрешения
- Multisig - мультиподпись
- Timelock - задержки выполнения
#### 3.2.2 ERC-4337 (аудит: Trail of Bits)
- Account Abstraction - универсальность
- Smart Contract Wallets - кошельки
- Bundlers - оптимизация газа
#### 3.2.3 Проверенные паттерны
- Diamond Pattern (EIP-2535) - модульность
- Proxy Pattern - обновляемость
- Factory Pattern - создание контрактов
### 3.3 Безопасность
#### 3.3.1 Требования к безопасности
- Полный аудит смарт-контракта
- Тестирование всех функций
- Проверка уязвимостей
- Соответствие стандартам безопасности
#### 3.3.2 Меры безопасности
- Использование проаудированных компонентов
- Проверенные паттерны разработки
- Изоляция рисков
- Поэтапная разработка с тестированием
### 3.4 Производительность
#### 3.4.1 Оптимизация газа
- Минимизация стоимости транзакций
- Эффективные алгоритмы
- Использование bundlers (ERC-4337)
- Оптимизация хранения данных
#### 3.4.2 Масштабируемость
- Поддержка большого количества участников
- Эффективная обработка голосований
- Оптимизация меж-DLE взаимодействий
- Модульная архитектура
## 4. Интерфейсы и интеграции
### 4.1 Веб3 приложение
#### 4.1.1 Функции приложения
- Создание DLE через форму
- Управление DLE
- Участие в голосованиях
- Просмотр истории транзакций
#### 4.1.2 Межприложное взаимодействие
- Встраивание интерфейсов других DLE
- Безопасное подписание транзакций
- Проверка прав доступа
- Аудит действий
#### 4.1.3 Мульти-чейн интерфейс с single-chain governance
- Выбор целевых сетей для деплоя DLE
- Отображение предсказанного адреса DLE
- Расчет стоимости деплоя по всем сетям
- Мониторинг статуса деплоя во всех сетях
- Выбор governance сети для создания предложений
- Установка таймлока инициатором предложения
- Подписание предложений токен-холдерами в governance сети
- Мониторинг исполнения в целевых сетях
- История операций и голосований
### 4.2 API и интеграции
#### 4.2.1 Внешние интеграции
- Оракулы для внешних данных
- Интеграция с DeFi протоколами
- Поддержка различных блокчейнов
- API для внешних приложений
## 5. Этапы разработки
### 5.1 Этап 1: Базовая функциональность
- Создание основного контракта DLE
- Интеграция ERC-20 токенов
- Базовая система голосования с настраиваемым кворумом
- Простые казначейские функции
- CREATE2 Factory для детерминистического деплоя
- Настройки времени голосования (период, задержка)
### 5.2 Этап 2: Расширенная функциональность
- Система мультиподписи
- Отдельный модуль TimelockController с настраиваемыми параметрами
- Коммуникационные функции
- Модульная система
- Мульти-сетевой деплой в тестовых сетях
- Базовая cross-chain синхронизация
### 5.3 Этап 3: Мульти-чейн архитектура
- Полная cross-chain синхронизация токенов
- Система голосования с выбором сети
- Cross-chain операции с откатами
- Мониторинг состояния во всех сетях
- Emergency pause и fallback механизмы
- Иерархическая система голосования между DLE
### 5.4 Этап 4: Межприложное взаимодействие
- Встраивание интерфейсов других DLE
- Безопасное cross-chain подписание
- Оптимизация газа для мульти-сетевых операций
- Интеграция с bridge протоколами
- Расширенное тестирование мульти-чейн функций
### 5.5 Этап 5: Аудит и запуск
- Профессиональный аудит всех компонентов
- Аудит мульти-чейн безопасности
- Тестирование в различных сетевых условиях
- Исправление уязвимостей
- Финальное тестирование cross-chain операций
- Развертывание в продакшн во всех целевых сетях
## 6. Требования к тестированию
### 6.1 Unit тесты
- Тестирование всех функций контракта
- Проверка граничных случаев
- Тестирование безопасности
- Проверка производительности
### 6.2 Integration тесты
- Тестирование взаимодействия модулей
- Проверка меж-DLE взаимодействий
- Тестирование веб3 приложения
- Проверка API интеграций
### 6.3 E2E тесты
- Полный цикл создания DLE
- Тестирование голосований
- Проверка коммуникационных функций
- Тестирование межприложного взаимодействия
## 7. Документация
### 7.1 Техническая документация
- Описание архитектуры
- API документация
- Руководство по развертыванию
- Руководство по безопасности
### 7.2 Пользовательская документация
- Руководство пользователя
- FAQ
- Видеоуроки
- Поддержка
## 8. Критерии приемки
### 8.1 Функциональные критерии
- Все функции работают согласно требованиям
- Система голосования функционирует корректно
- Меж-DLE взаимодействие работает
- Коммуникационные функции активны
- CREATE2 деплой создает одинаковые адреса во всех сетях
- Cross-chain синхронизация токенов работает корректно
- Голосование с выбором сети функционирует
- Мульти-сетевой деплой завершается успешно во всех целевых сетях
### 8.2 Критерии безопасности
- Прохождение профессионального аудита
- Отсутствие критических уязвимостей
- Соответствие стандартам безопасности
- Проверка всех сценариев атак
- Безопасность cross-chain операций
- Защита от MEV-атак при мульти-чейн операциях
- Корректная работа откатов при сбоях синхронизации
- Валидация кворума во всех поддерживаемых сетях
### 8.3 Критерии производительности
- Оптимизация газа
- Быстрая обработка транзакций
- Масштабируемость системы
- Стабильная работа под нагрузкой
- Эффективная синхронизация между сетями
- Минимальные задержки cross-chain операций
- Оптимальное использование ресурсов во всех сетях
- Быстрое восстановление после сбоев синхронизации
## 9. Лицензии и правовые аспекты
### 9.1 Используемые лицензии
- OpenZeppelin - MIT лицензия
- ERC-4337 - CC0 лицензия
- Собственный код - Proprietary
### 9.2 Патентные аспекты
- Низкий патентный риск для концепции DLE
- Использование открытых стандартов
- Защита уникальных функций
- Консультации с юристами при необходимости

View File

@@ -1,3 +1,15 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
# Система очереди AI запросов
## Обзор

View File

@@ -6,9 +6,10 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Основной сервер для легитимных доменов
server {
listen 80;
server_name localhost;
server_name hb3-accelerator.com www.hb3-accelerator.com localhost;
# API прокси (точное совпадение для /api/)
location /api/ {
@@ -32,4 +33,16 @@ http {
proxy_set_header X-Forwarded-Port $server_port;
}
}
# Сервер по умолчанию для блокировки подозрительных доменов
server {
listen 80 default_server;
server_name _;
# Возвращаем 444 (Connection Closed Without Response)
return 444;
# Логируем попытки доступа к подозрительным доменам
access_log /var/log/nginx/suspicious_domains.log;
}
}

View File

@@ -1,3 +1,15 @@
<!--
Copyright (c) 2024-2025 Тарабанов Александр Викторович
All rights reserved.
This software is proprietary and confidential.
Unauthorized copying, modification, or distribution is prohibited.
For licensing inquiries: info@hb3-accelerator.com
Website: https://hb3-accelerator.com
GitHub: https://github.com/HB3-ACCELERATOR
-->
<template>
<div class="ai-queue-monitor">
<div class="monitor-header">

View File

@@ -1,841 +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>
<div class="dle-management-modal">
<div class="dle-management-header">
<h2>Ваши DLE</h2>
<button class="close-btn" @click="$emit('close')">×</button>
</div>
<div class="dle-list-section">
<div v-if="dleList.length === 0" class="no-dle-message">
<p>У вас пока нет созданных DLE.</p>
</div>
<div v-else>
<div class="dle-list">
<div v-for="(dle, index) in dleList" :key="index" class="dle-card"
:class="{ 'active': selectedDleIndex === index }"
@click="selectDle(index)">
<div class="dle-card-header">
<h3>{{ dle.name }} ({{ dle.symbol }})</h3>
<button v-if="isAdmin" class="delete-dle-btn" @click.stop="deleteDle(dle, index)" title="Удалить DLE">
<i class="fas fa-trash"></i>
</button>
</div>
<p><strong>Адрес:</strong> {{ shortenAddress(dle.tokenAddress) }}</p>
<p><strong>Местонахождение:</strong> {{ dle.location }}</p>
</div>
</div>
</div>
</div>
<div v-if="selectedDle" class="dle-details-section">
<h2>Управление "{{ selectedDle.name }}"</h2>
<div class="dle-tabs">
<div class="tab-header">
<div class="tab-button" :class="{ 'active': activeTab === 'info' }" @click="activeTab = 'info'">
<i class="fas fa-info-circle"></i> Основная информация
</div>
<div class="tab-button" :class="{ 'active': activeTab === 'proposals' }" @click="activeTab = 'proposals'">
<i class="fas fa-tasks"></i> Предложения
</div>
<div class="tab-button" :class="{ 'active': activeTab === 'governance' }" @click="activeTab = 'governance'">
<i class="fas fa-balance-scale"></i> Управление
</div>
<div class="tab-button" :class="{ 'active': activeTab === 'modules' }" @click="activeTab = 'modules'">
<i class="fas fa-puzzle-piece"></i> Модули
</div>
</div>
<div class="tab-content" v-if="activeTab === 'info'">
<div class="info-card">
<h3>Основная информация</h3>
<div class="info-row">
<span class="info-label">Название:</span>
<span class="info-value">{{ selectedDle.name }}</span>
</div>
<div class="info-row">
<span class="info-label">Символ токена:</span>
<span class="info-value">{{ selectedDle.symbol }}</span>
</div>
<div class="info-row">
<span class="info-label">Местонахождение:</span>
<span class="info-value">{{ selectedDle.location }}</span>
</div>
<div class="info-row">
<span class="info-label">Коды деятельности:</span>
<span class="info-value">{{ selectedDle.isicCodes && selectedDle.isicCodes.length ? selectedDle.isicCodes.join(', ') : 'Не указаны' }}</span>
</div>
<div class="info-row">
<span class="info-label">Дата создания:</span>
<span class="info-value">{{ formatDate(selectedDle.creationTimestamp) }}</span>
</div>
</div>
<div class="contract-cards">
<div class="contract-card">
<h4>Токен управления</h4>
<p class="address">{{ selectedDle.tokenAddress }}</p>
<div class="contract-actions">
<button class="btn btn-sm btn-secondary" @click="copyToClipboard(selectedDle.tokenAddress)">
<i class="fas fa-copy"></i> Копировать адрес
</button>
<button class="btn btn-sm btn-info" @click="viewOnExplorer(selectedDle.tokenAddress)">
<i class="fas fa-external-link-alt"></i> Обзор
</button>
</div>
</div>
<div class="contract-card">
<h4>Таймлок</h4>
<p class="address">{{ selectedDle.timelockAddress }}</p>
<div class="contract-actions">
<button class="btn btn-sm btn-secondary" @click="copyToClipboard(selectedDle.timelockAddress)">
<i class="fas fa-copy"></i> Копировать адрес
</button>
<button class="btn btn-sm btn-info" @click="viewOnExplorer(selectedDle.timelockAddress)">
<i class="fas fa-external-link-alt"></i> Обзор
</button>
</div>
</div>
<div class="contract-card">
<h4>Governor</h4>
<p class="address">{{ selectedDle.governorAddress }}</p>
<div class="contract-actions">
<button class="btn btn-sm btn-secondary" @click="copyToClipboard(selectedDle.governorAddress)">
<i class="fas fa-copy"></i> Копировать адрес
</button>
<button class="btn btn-sm btn-info" @click="viewOnExplorer(selectedDle.governorAddress)">
<i class="fas fa-external-link-alt"></i> Обзор
</button>
</div>
</div>
</div>
</div>
<div class="tab-content" v-if="activeTab === 'proposals'">
<h3>Предложения</h3>
<div class="proposals-actions">
<button class="btn btn-primary" @click="showCreateProposalForm = true">
<i class="fas fa-plus"></i> Создать предложение
</button>
</div>
<div v-if="showCreateProposalForm" class="create-proposal-form">
<h4>Новое предложение</h4>
<div class="form-group">
<label for="proposalTitle">Заголовок:</label>
<input type="text" id="proposalTitle" v-model="newProposal.title" class="form-control">
</div>
<div class="form-group">
<label for="proposalDescription">Описание:</label>
<textarea id="proposalDescription" v-model="newProposal.description" class="form-control" rows="3"></textarea>
</div>
<div class="form-actions">
<button class="btn btn-success" @click="createProposal" :disabled="isCreatingProposal">
<i class="fas fa-paper-plane"></i> {{ isCreatingProposal ? 'Отправка...' : 'Отправить' }}
</button>
<button class="btn btn-secondary" @click="showCreateProposalForm = false">
<i class="fas fa-times"></i> Отмена
</button>
</div>
</div>
<div class="proposals-list">
<p v-if="proposals.length === 0">Предложений пока нет</p>
<div v-else v-for="(proposal, index) in proposals" :key="index" class="proposal-card">
<h4>{{ proposal.title }}</h4>
<p>{{ proposal.description }}</p>
<div class="proposal-status" :class="proposal.status">
{{ getProposalStatusText(proposal.status) }}
</div>
<div class="proposal-actions">
<button class="btn btn-sm btn-primary" @click="voteForProposal(proposal.id, true)" :disabled="!canVote(proposal)">
<i class="fas fa-thumbs-up"></i> За
</button>
<button class="btn btn-sm btn-danger" @click="voteForProposal(proposal.id, false)" :disabled="!canVote(proposal)">
<i class="fas fa-thumbs-down"></i> Против
</button>
</div>
</div>
</div>
</div>
<div class="tab-content" v-if="activeTab === 'governance'">
<h3>Управление</h3>
<div class="governance-info">
<div class="info-card">
<h4>Настройки Governor</h4>
<div class="info-row">
<span class="info-label">Порог предложения:</span>
<span class="info-value">100,000 GT</span>
</div>
<div class="info-row">
<span class="info-label">Кворум:</span>
<span class="info-value">4%</span>
</div>
<div class="info-row">
<span class="info-label">Задержка голосования:</span>
<span class="info-value">1 день</span>
</div>
<div class="info-row">
<span class="info-label">Период голосования:</span>
<span class="info-value">7 дней</span>
</div>
</div>
<div class="info-card">
<h4>Статистика голосований</h4>
<div class="info-row">
<span class="info-label">Всего предложений:</span>
<span class="info-value">{{ proposals.length }}</span>
</div>
<div class="info-row">
<span class="info-label">Активных предложений:</span>
<span class="info-value">{{ getProposalsByStatus('active').length }}</span>
</div>
<div class="info-row">
<span class="info-label">Успешных предложений:</span>
<span class="info-value">{{ getProposalsByStatus('succeeded').length }}</span>
</div>
<div class="info-row">
<span class="info-label">Отклоненных предложений:</span>
<span class="info-value">{{ getProposalsByStatus('defeated').length }}</span>
</div>
</div>
</div>
</div>
<div class="tab-content" v-if="activeTab === 'modules'">
<h3>Подключение модулей</h3>
<p>Здесь вы можете подключить дополнительные модули к вашему DLE.</p>
<div class="modules-list">
<div v-for="(module, index) in availableModules" :key="index" class="module-card">
<h4>{{ module.name }}</h4>
<p>{{ module.description }}</p>
<div class="module-status" :class="{ 'installed': isModuleInstalled(module) }">
{{ isModuleInstalled(module) ? 'Установлен' : 'Доступен' }}
</div>
<div v-if="module.name === 'Прием платежей' && paymentModuleTokens.length > 0" class="payment-tokens-list">
<div v-for="token in paymentModuleTokens" :key="token.address + token.network" class="payment-token-entry">
<span><b>{{ token.name }}</b> ({{ token.network }})</span>
<span style="font-size:0.95em;color:#888">{{ token.address }}</span>
</div>
</div>
<div class="module-actions">
<button v-if="module.name === 'Прием платежей' && !isModuleInstalled(module)" class="btn btn-success" @click="openPaymentTokensModal">
<i class="fas fa-plus"></i> Настроить
</button>
<button v-else-if="module.name === 'Прием платежей' && isModuleInstalled(module)" class="btn btn-danger" @click="uninstallPaymentModule">
<i class="fas fa-trash"></i> Удалить
</button>
<button v-else-if="!isModuleInstalled(module)" class="btn btn-success" @click="installModule(module)">
<i class="fas fa-plus"></i> Установить
</button>
<button v-else class="btn btn-danger" @click="uninstallModule(module)">
<i class="fas fa-trash"></i> Удалить
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<BaseModal
:show="showDeleteModal"
:title="deleteSuccess ? 'Готово' : 'Подтвердите удаление DLE'"
@close="closeDeleteModal"
>
<template #default>
<div style="margin-bottom:18px;">
<span v-if="!deleteSuccess">Удалить DLE <b>«{{ dleToDelete?.name }}»</b>? Это действие необратимо.</span>
<span v-else>DLE успешно удалён</span>
</div>
</template>
<template #actions>
<template v-if="!deleteSuccess">
<button class="modal-ok-btn delete-btn" @click="confirmDeleteDle" :disabled="isDeletingDle">
{{ isDeletingDle ? 'Удаление...' : 'Удалить' }}
</button>
<button class="modal-ok-btn cancel-btn" @click="closeDeleteModal" :disabled="isDeletingDle">Отмена</button>
</template>
<template v-else>
<button class="modal-ok-btn" @click="closeDeleteModal">OK</button>
</template>
</template>
</BaseModal>
<BaseModal
:show="showPaymentTokensModal"
title="Выберите токены для приема платежей"
@close="closePaymentTokensModal"
>
<template #default>
<div v-if="authTokens.length === 0">Нет доступных токенов. Добавьте токены в настройках безопасности.</div>
<div v-else>
<div v-for="token in authTokens" :key="token.address + token.network" class="token-select-row">
<label>
<input type="checkbox" :value="token" v-model="paymentModuleTokens.value" />
<b>{{ token.name }}</b> ({{ token.network }}) <span style="font-size:0.95em;color:#888">{{ token.address }}</span>
</label>
</div>
</div>
</template>
<template #actions>
<button class="modal-ok-btn" @click="closePaymentTokensModal">Отмена</button>
<button class="modal-ok-btn btn-success" @click="closePaymentTokensModal">Сохранить</button>
</template>
</BaseModal>
</div>
</template>
<script setup>
import { ref, defineProps, computed, inject } from 'vue';
import { useAuthContext } from '@/composables/useAuth';
import dleService from '@/services/dleService';
import BaseModal from './NoAccessModal.vue';
const props = defineProps({
dleList: { type: Array, required: true },
selectedDleIndex: { type: Number, default: null }
});
const { isAdmin } = useAuthContext();
const selectedDleIndex = ref(props.selectedDleIndex ?? 0);
const activeTab = ref('info');
const showCreateProposalForm = ref(false);
const newProposal = ref({ title: '', description: '' });
const isCreatingProposal = ref(false);
const proposals = ref([]);
const availableModules = ref([
{
name: 'Контракт на активы',
description: 'Позволяет токенизировать физические активы и управлять ими через DLE.',
installed: false
},
{
name: 'Мультиподпись',
description: 'Добавляет функциональность мультиподписи для повышенной безопасности.',
installed: false
},
{
name: 'Дивиденды',
description: 'Позволяет распределять дивиденды между держателями токенов.',
installed: false
},
{
name: 'Стейкинг',
description: 'Добавляет возможность стейкинга токенов для получения наград.',
installed: false
}
]);
const selectedDle = computed(() => {
if (selectedDleIndex.value !== null && props.dleList.length > selectedDleIndex.value) {
return props.dleList[selectedDleIndex.value];
}
return null;
});
function selectDle(index) {
selectedDleIndex.value = index;
activeTab.value = 'info';
}
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function formatDate(timestamp) {
if (!timestamp) return 'N/A';
return new Date(timestamp * 1000).toLocaleString();
}
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
.then(() => {
alert('Адрес скопирован в буфер обмена');
})
.catch(err => {
console.error('Ошибка при копировании текста: ', err);
});
}
function viewOnExplorer(address) {
window.open(`https://sepolia.etherscan.io/address/${address}`, '_blank');
}
function createProposal() {
if (!newProposal.value.title || !newProposal.value.description) {
alert('Пожалуйста, заполните все поля');
return;
}
isCreatingProposal.value = true;
try {
proposals.value.push({
id: Date.now().toString(),
title: newProposal.value.title,
description: newProposal.value.description,
status: 'pending',
votes: { for: 0, against: 0 }
});
showCreateProposalForm.value = false;
newProposal.value = { title: '', description: '' };
alert('Предложение создано!');
} catch (error) {
console.error('Ошибка при создании предложения:', error);
alert('Ошибка при создании предложения');
} finally {
isCreatingProposal.value = false;
}
}
function voteForProposal(proposalId, isFor) {
try {
const proposal = proposals.value.find(p => p.id === proposalId);
if (proposal) {
if (isFor) {
proposal.votes.for += 1;
} else {
proposal.votes.against += 1;
}
if (proposal.votes.for > proposal.votes.against && proposal.votes.for >= 3) {
proposal.status = 'succeeded';
} else if (proposal.votes.against > proposal.votes.for && proposal.votes.against >= 3) {
proposal.status = 'defeated';
} else {
proposal.status = 'active';
}
alert('Ваш голос учтен!');
}
} catch (error) {
console.error('Ошибка при голосовании:', error);
alert('Ошибка при голосовании');
}
}
function canVote(proposal) {
return proposal.status === 'active' || proposal.status === 'pending';
}
function getProposalStatusText(status) {
const statusMap = {
'pending': 'Ожидает',
'active': 'Активно',
'succeeded': 'Принято',
'defeated': 'Отклонено',
'executed': 'Выполнено'
};
return statusMap[status] || status;
}
function getProposalsByStatus(status) {
return proposals.value.filter(p => p.status === status);
}
function installModule(module) {
module.installed = true;
alert(`Модуль "${module.name}" успешно установлен!`);
}
function uninstallModule(module) {
module.installed = false;
alert(`Модуль "${module.name}" удален.`);
}
function isModuleInstalled(module) {
if (typeof module.installed === 'function') return module.installed();
return !!module.installed;
}
const emit = defineEmits(['close', 'dle-updated']);
const showDeleteModal = ref(false);
const dleToDelete = ref(null);
const isDeletingDle = ref(false);
const deleteSuccess = ref(false);
function deleteDle(dle, idx) {
if (!isAdmin.value) return;
dleToDelete.value = dle;
showDeleteModal.value = true;
deleteSuccess.value = false;
}
function closeDeleteModal() {
showDeleteModal.value = false;
dleToDelete.value = null;
isDeletingDle.value = false;
deleteSuccess.value = false;
}
async function confirmDeleteDle() {
if (!dleToDelete.value) return;
isDeletingDle.value = true;
try {
await dleService.deleteDLE(dleToDelete.value.tokenAddress);
deleteSuccess.value = true;
emit('dle-updated');
isDeletingDle.value = false;
} catch (e) {
alert('Ошибка при удалении DLE: ' + (e?.message || e));
isDeletingDle.value = false;
}
}
const authTokens = inject('authTokens', ref([]));
const paymentModuleTokens = ref([]);
const showPaymentTokensModal = ref(false);
function openPaymentTokensModal() {
showPaymentTokensModal.value = true;
}
function closePaymentTokensModal() {
showPaymentTokensModal.value = false;
}
function savePaymentTokens(selected) {
paymentModuleTokens.value = selected;
closePaymentTokensModal();
// Можно добавить сохранение в API, если потребуется
}
function uninstallPaymentModule() {
paymentModuleTokens.value = [];
}
availableModules.value.push({
name: 'Прием платежей',
description: 'Позволяет принимать оплату в выбранных токенах. Можно выбрать один или несколько токенов для приема платежей.',
installed: computed(() => paymentModuleTokens.value.length > 0)
});
</script>
<style scoped>
.dle-management-modal {
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 32px rgba(0,0,0,0.12);
padding: 32px 24px 24px 24px;
width: 100%;
margin-top: 40px;
position: relative;
overflow-x: auto;
}
.dle-management-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
cursor: pointer;
color: #bbb;
transition: color 0.2s;
}
.close-btn:hover {
color: #333;
}
.dle-list-section {
margin-bottom: 30px;
}
.dle-list {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-top: 20px;
}
.dle-card {
width: 300px;
padding: 15px;
border: 1px solid #e5e7eb;
border-radius: 10px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
background: #f8f9fa;
cursor: pointer;
transition: all 0.2s ease;
}
.dle-card.active {
border-color: #17a2b8;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.dle-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 6px;
}
.dle-card h3 {
margin-top: 0;
margin-bottom: 10px;
color: #17a2b8;
}
.dle-details-section {
margin-top: 30px;
border-top: 1px solid #e5e7eb;
padding-top: 20px;
}
.no-dle-message {
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
text-align: center;
}
.dle-tabs {
display: flex;
flex-direction: column;
gap: 20px;
}
.tab-header {
display: flex;
gap: 20px;
border-bottom: 1px solid #e5e7eb;
}
.tab-button {
padding: 10px 20px;
cursor: pointer;
transition: border-bottom 0.2s;
}
.tab-button.active {
border-bottom: 2px solid #17a2b8;
}
.tab-content {
margin-top: 20px;
}
.info-card {
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
}
.info-row {
margin-bottom: 10px;
}
.info-label {
font-weight: bold;
}
.info-value {
margin-left: 10px;
}
.contract-cards {
display: flex;
gap: 24px;
margin-top: 24px;
flex-wrap: wrap;
justify-content: space-between;
}
.contract-card {
flex: 1 1 0;
min-width: 260px;
max-width: 340px;
background: #fff;
border-radius: 18px;
box-shadow: 0 2px 16px rgba(0,0,0,0.07);
padding: 22px 20px 18px 20px;
display: flex;
flex-direction: column;
margin-bottom: 16px;
transition: box-shadow 0.2s, transform 0.2s;
}
.contract-card:hover {
box-shadow: 0 6px 24px rgba(23,162,184,0.13);
transform: translateY(-2px) scale(1.02);
}
.contract-card h4 {
margin: 0 0 10px 0;
font-size: 1.1rem;
font-weight: 600;
color: #17a2b8;
}
.contract-card .address {
font-family: 'Fira Mono', 'Consolas', monospace;
word-break: break-all;
background: #f6f8fa;
border-radius: 6px;
padding: 7px 10px;
font-size: 0.98rem;
margin-bottom: 18px;
color: #222;
}
.contract-actions {
display: flex;
gap: 10px;
width: 100%;
}
.contract-actions .btn {
flex: 1 1 0;
min-width: 0;
font-size: 1rem;
padding: 10px 0;
border-radius: 8px;
font-weight: 500;
box-shadow: none;
border: none;
transition: background 0.18s, color 0.18s;
}
.contract-actions .btn-secondary {
background: #f1f3f6;
color: #888;
}
.contract-actions .btn-secondary:hover {
background: #e2e6ea;
color: #222;
}
.contract-actions .btn-info {
background: #17a2b8;
color: #fff;
}
.contract-actions .btn-info:hover {
background: #148a9d;
}
@media (max-width: 900px) {
.contract-cards {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
.contract-card {
max-width: 100%;
min-width: 0;
}
}
.proposals-actions {
margin-bottom: 20px;
}
.create-proposal-form {
padding: 20px;
background-color: #f8f9fa;
border-radius: 10px;
}
.form-group {
margin-bottom: 10px;
}
.form-group label {
display: block;
margin-bottom: 5px;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 8px;
border: 1px solid #e5e7eb;
border-radius: 5px;
}
.form-actions {
margin-top: 10px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.proposals-list {
margin-top: 20px;
}
.proposal-card {
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
margin-bottom: 10px;
}
.proposal-status {
margin-top: 5px;
padding: 5px 10px;
border-radius: 5px;
}
.proposal-status.pending {
background-color: #ffd700;
}
.proposal-status.active {
background-color: #17a2b8;
color: #fff;
}
.proposal-status.succeeded {
background-color: #28a745;
color: #fff;
}
.proposal-status.defeated {
background-color: #dc3545;
color: #fff;
}
.proposal-actions {
margin-top: 10px;
display: flex;
gap: 10px;
}
.modules-list {
margin-top: 20px;
}
.module-card {
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
margin-bottom: 10px;
}
.module-status {
margin-top: 5px;
padding: 5px 10px;
border-radius: 5px;
}
.module-status.installed {
background-color: #28a745;
color: #fff;
}
.module-actions {
margin-top: 10px;
display: flex;
gap: 10px;
}
.delete-dle-btn {
background: none;
border: none;
color: #dc3545;
font-size: 1.2em;
cursor: pointer;
padding: 2px 6px;
border-radius: 4px;
transition: background 0.15s;
}
.delete-dle-btn:hover {
background: #ffeaea;
color: #a71d2a;
}
.delete-btn {
background: #dc3545;
color: #fff;
border: none;
border-radius: 6px;
padding: 0.5rem 2rem;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s;
}
.delete-btn:disabled {
background: #e6a6ad;
cursor: not-allowed;
}
.delete-btn:hover:not(:disabled) {
background: #b52a37;
}
.cancel-btn {
background: #f5f5f5;
color: #333;
border: 1px solid #ccc;
border-radius: 6px;
padding: 0.5rem 2rem;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s;
}
.cancel-btn:disabled {
background: #eee;
color: #aaa;
cursor: not-allowed;
}
.cancel-btn:hover:not(:disabled) {
background: #e0e0e0;
}
.payment-tokens-list {
margin: 10px 0 0 0;
padding: 8px 0 0 0;
border-top: 1px solid #e5e7eb;
}
.payment-token-entry {
font-size: 1.02em;
margin-bottom: 4px;
display: flex;
flex-direction: column;
}
.token-select-row {
margin-bottom: 8px;
text-align: left;
}
.btn-success {
background: #28a745;
color: #fff;
border: none;
border-radius: 6px;
padding: 0.5rem 2rem;
cursor: pointer;
font-size: 1rem;
transition: background 0.2s;
}
.btn-success:hover {
background: #218838;
}
</style>

View File

@@ -171,11 +171,7 @@ const routes = [
name: 'contacts-list',
component: () => import('../views/ContactsView.vue')
},
{
path: '/dle-management',
name: 'dle-management',
component: () => import('../views/DleManagementView.vue')
},
{
path: '/settings/ai/telegram',
name: 'telegram-settings',
@@ -223,10 +219,16 @@ const routes = [
component: () => import('../views/smartcontracts/DleManagementView.vue'),
meta: { requiresAuth: true }
},
{
path: '/management/dle-management',
name: 'management-dle-management',
component: () => import('../views/smartcontracts/DleManagementView.vue'),
meta: { requiresAuth: true }
},
{
path: '/management/proposals',
name: 'management-proposals',
component: () => import('../views/smartcontracts/ProposalsView.vue'),
component: () => import('../views/smartcontracts/DleProposalsView.vue'),
meta: { requiresAuth: true }
},
{
@@ -244,7 +246,13 @@ const routes = [
{
path: '/management/modules',
name: 'management-modules',
component: () => import('../views/smartcontracts/ModulesView.vue'),
component: () => import('../views/smartcontracts/DleModulesView.vue'),
meta: { requiresAuth: true }
},
{
path: '/management/multisig',
name: 'management-multisig',
component: () => import('../views/smartcontracts/DleMultisigView.vue'),
meta: { requiresAuth: true }
},
{

View File

@@ -1,104 +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
*/
import api from '@/api/axios';
/**
* Сервис для работы с DLE (Digital Legal Entity)
*/
class DLEService {
/**
* Получает настройки по умолчанию для создания DLE
* @returns {Promise<Object>} - Настройки по умолчанию
*/
async getDefaultSettings() {
try {
const response = await api.get('/dle/settings');
return response.data.data;
} catch (error) {
console.error('Ошибка при получении настроек DLE:', error);
throw error;
}
}
/**
* Создает новое DLE с указанными параметрами
* @param {Object} dleParams - Параметры для создания DLE
* @returns {Promise<Object>} - Результат создания DLE
*/
async createDLE(dleParams) {
try {
const response = await api.post('/dle', dleParams);
return response.data;
} catch (error) {
console.error('Ошибка при создании DLE:', error);
throw error;
}
}
/**
* Получает список всех DLE
* @returns {Promise<Array>} - Список DLE
*/
async getAllDLEs() {
try {
const response = await api.get('/dle');
// Проверяем и нормализуем поля isicCodes для всех DLE
if (response.data.data && Array.isArray(response.data.data)) {
response.data.data.forEach(dle => {
// Если isicCodes отсутствует или не является массивом, инициализируем пустым массивом
if (!dle.isicCodes || !Array.isArray(dle.isicCodes)) {
dle.isicCodes = [];
}
});
}
return response.data.data;
} catch (error) {
console.error('Ошибка при получении списка DLE:', error);
throw error;
}
}
/**
* Удаляет DLE по адресу токена
* @param {string} tokenAddress - Адрес токена DLE
* @returns {Promise<Object>} - Результат удаления
*/
async deleteDLE(tokenAddress) {
try {
const response = await api.delete(`/dle/${tokenAddress}`);
return response.data;
} catch (error) {
console.error('Ошибка при удалении DLE:', error);
throw error;
}
}
/**
* Удаляет пустое DLE по имени файла
* @param {string} fileName - Имя файла DLE
* @returns {Promise<Object>} - Результат удаления
*/
async deleteEmptyDLE(fileName) {
try {
const response = await api.delete(`/dle/empty/${fileName}`);
return response.data;
} catch (error) {
console.error('Ошибка при удалении пустого DLE:', error);
throw error;
}
}
}
export default new DLEService();

View File

@@ -1,3 +1,15 @@
/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
/**
* WebSocket сервис для реального времени обновлений
*/

View File

@@ -19,12 +19,7 @@
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="crm-view-container">
<div class="dle-management-block">
<h2>Управление DLE</h2>
<button class="details-btn" @click="goToDleManagement">
Подробнее
</button>
</div>
<div class="crm-contacts-block">
<h2>Контакты</h2>
<button class="details-btn" @click="goToContactsList">
@@ -62,10 +57,8 @@ import { useRouter } from 'vue-router';
import { setToStorage } from '../utils/storage';
import BaseLayout from '../components/BaseLayout.vue';
import eventBus from '../utils/eventBus';
import dleService from '../services/dleService';
import ContactTable from '../components/ContactTable.vue';
import contactsService from '../services/contactsService.js';
import DleManagement from '../components/DleManagement.vue';
// Определяем props
const props = defineProps({
@@ -214,9 +207,7 @@ function goToTables() {
router.push({ name: 'tables-list' });
}
function goToDleManagement() {
router.push({ name: 'dle-management' });
}
function goToContactsList() {
router.push({ name: 'contacts-list' });
@@ -293,24 +284,7 @@ strong {
background: #5a6268;
}
.dle-management-block {
margin: 32px 0 24px 0;
padding: 24px;
background: #f8fafc;
border-radius: 10px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
display: flex;
align-items: center;
justify-content: space-between;
}
.dle-management-block h2 {
margin: 0;
font-size: 1.4rem;
font-weight: 600;
}
.dle-management-block .details-btn {
margin-top: 0;
}
.crm-contacts-block {
margin: 32px 0 24px 0;

View File

@@ -1,70 +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>
<DleManagement
:dle-list="dleList"
:selected-dle-index="selectedDleIndex"
@close="goBack"
@dle-updated="reloadDleList"
class="dle-management-root"
/>
</BaseLayout>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../components/BaseLayout.vue';
import DleManagement from '../components/DleManagement.vue';
import dleService from '../services/dleService';
const dleList = ref([]);
const selectedDleIndex = ref(0);
const router = useRouter();
function goBack() {
if (window.history.length > 1) {
router.back();
} else {
router.push({ name: 'crm' });
}
}
async function reloadDleList() {
dleList.value = await dleService.getAllDLEs() || [];
// Сбросить выбранный индекс, если список изменился
if (dleList.value.length === 0) {
selectedDleIndex.value = 0;
} else if (selectedDleIndex.value >= dleList.value.length) {
selectedDleIndex.value = 0;
}
}
onMounted(async () => {
await reloadDleList();
});
</script>
<style scoped>
.dle-management-root {
background: #fff;
border-radius: 16px;
box-shadow: 0 4px 32px rgba(0,0,0,0.12);
padding: 32px 24px 24px 24px;
width: 100%;
margin-top: 40px;
position: relative;
overflow-x: auto;
}
</style>

View File

@@ -130,7 +130,7 @@ const openModules = () => {
};
const openDle = () => {
router.push('/management/dle');
router.push('/management/dle-management');
};
const openTreasury = () => {

View File

@@ -325,6 +325,20 @@
<small class="form-help">3-10 символов для токена управления (Governance Token)</small>
</div>
<!-- Координаты -->
<div class="form-group">
<label class="form-label" for="coordinates">Координаты (широта, долгота):</label>
<input
type="text"
id="coordinates"
v-model="dleSettings.coordinates"
class="form-control"
placeholder="Например: 55.7558,37.6176"
pattern="^-?\d+\.\d+,-?\d+\.\d+$"
>
<small class="form-help">Координаты в формате "широта,долгота" (например: 55.7558,37.6176)</small>
</div>
<!-- Партнеры и распределение токенов -->
<div class="partners-section">
<h4>Партнеры и распределение токенов</h4>
@@ -761,6 +775,11 @@
<strong>🏢 КПП:</strong> {{ selectedKppInfo.code }} - {{ selectedKppInfo.title }}
</div>
<!-- Координаты -->
<div v-if="dleSettings.coordinates" class="preview-item">
<strong>📍 Координаты:</strong> {{ dleSettings.coordinates }}
</div>
<!-- Кнопка деплоя смарт-контрактов -->
<div class="deploy-section">
<div class="deploy-buttons">
@@ -843,6 +862,7 @@ const dleSettings = reactive({
// Устаревшие поля (для совместимости)
deployNetwork: '', // Заменено на selectedNetworks
privateKey: '', // Заменено на privateKeys объект
coordinates: '', // Координаты для DLE
});
// Состояние UI (минимально необходимое)
@@ -2156,6 +2176,88 @@ const maskedPrivateKey = computed(() => {
const end = dleSettings.privateKey.substring(dleSettings.privateKey.length - 4);
return `${start}...${end}`;
});
// Функция деплоя смарт-контрактов DLE
const deploySmartContracts = async () => {
try {
// Валидация данных
if (!isFormValid.value) {
alert('Пожалуйста, заполните все обязательные поля');
return;
}
// Подготовка данных для деплоя
const deployData = {
// Основная информация DLE
name: dleSettings.name,
symbol: dleSettings.tokenSymbol,
location: dleSettings.addressData.fullAddress || 'Не указан',
coordinates: dleSettings.coordinates || '0,0',
jurisdiction: parseInt(dleSettings.jurisdiction) || 0,
oktmo: dleSettings.selectedOktmo || '',
okvedCodes: dleSettings.selectedOkved || [],
kpp: dleSettings.kppCode || '',
// Настройки кворума
quorumPercentage: dleSettings.governanceQuorum || 51,
// Партнеры и токены
initialPartners: dleSettings.partners.map(p => p.address).filter(addr => addr),
initialAmounts: dleSettings.partners.map(p => p.amount).filter(amount => amount > 0),
// Мульти-чейн настройки
supportedChainIds: dleSettings.selectedNetworks || [],
// Текущая цепочка (будет установлена при деплое)
currentChainId: dleSettings.selectedNetworks[0] || 1
};
console.log('Данные для деплоя DLE:', deployData);
// Вызов API для деплоя
const response = await axios.post('/api/dle-v2', deployData);
if (response.data.success) {
alert('✅ DLE успешно развернут!');
// Сохраняем адрес контракта
dleSettings.predictedAddress = response.data.data?.contractAddress || 'Адрес будет доступен после деплоя';
// Перенаправляем на страницу управления
router.push('/management/dle-management');
} else {
alert('❌ Ошибка при деплое: ' + response.data.error);
}
} catch (error) {
console.error('Ошибка деплоя DLE:', error);
alert('❌ Ошибка при деплое смарт-контракта: ' + error.message);
}
};
// Валидация формы
const isFormValid = computed(() => {
return (
dleSettings.jurisdiction &&
dleSettings.name &&
dleSettings.tokenSymbol ||
dleSettings.tokenStandard !== 'ERC20' ||
dleSettings.partners.length > 0 &&
dleSettings.partners.every(partner => partner.address && partner.amount > 0) &&
dleSettings.governanceQuorum > 0 &&
dleSettings.governanceQuorum <= 100 &&
dleSettings.selectedNetworks.length > 0 &&
// Валидация координат
validateCoordinates(dleSettings.coordinates)
);
});
// Функция валидации координат
const validateCoordinates = (coordinates) => {
if (!coordinates) return true; // Координаты не обязательны
const coordRegex = /^-?\d+\.\d+,-?\d+\.\d+$/;
return coordRegex.test(coordinates);
};
</script>
<style scoped>

View File

@@ -20,12 +20,7 @@
<div class="main-block">
<h3>Блокчейн</h3>
<p>Интеграция с блокчейн-сетями, RPC, токены и смарт-контракты.</p>
<button class="details-btn" @click="$router.push('/settings/blockchain')">Подробнее</button>
</div>
<div class="main-block">
<h3>Блокчейн 2</h3>
<p>Современный DLE v2 - единый смарт-контракт с встроенной системой голосования.</p>
<button class="details-btn details-btn-secondary" @click="$router.push('/settings/dle-v2-deploy')">Подробнее</button>
<button class="details-btn" @click="$router.push('/settings/dle-v2-deploy')">Подробнее</button>
</div>
<div class="main-block">
<h3>Безопасность</h3>

View File

@@ -1,499 +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="dle-management-container">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Управление DLE</h1>
<p>Интеграция с другими DLE и участие в кворумах</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
<!-- Карточки DLE -->
<div class="dle-cards">
<div
v-for="dle in dleList"
:key="dle.address"
class="dle-card"
@click="openDleInterface(dle)"
>
<div class="dle-card-header">
<h3>{{ dle.name }}</h3>
<button
@click.stop="removeDle(dle.address)"
class="remove-btn"
title="Удалить DLE"
>
🗑
</button>
</div>
<div class="dle-card-content">
<p class="dle-address">Адрес: {{ formatAddress(dle.address) }}</p>
<p class="dle-location">Местонахождение: {{ dle.location }}</p>
</div>
</div>
<!-- Кнопка добавления нового DLE -->
<div class="dle-card add-dle-card" @click="showAddDleForm = true">
<div class="add-dle-content">
<div class="add-icon">+</div>
<h3>Добавить DLE</h3>
<p>Подключить новый DLE для управления</p>
</div>
</div>
</div>
<!-- Модальное окно добавления DLE -->
<div v-if="showAddDleForm" class="modal-overlay" @click="showAddDleForm = false">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>Добавить новый DLE</h3>
<button @click="showAddDleForm = false" class="close-btn"></button>
</div>
<div class="modal-body">
<form @submit.prevent="addNewDle" class="add-dle-form">
<div class="form-group">
<label for="dleName">Название DLE:</label>
<input
id="dleName"
v-model="newDle.name"
type="text"
placeholder="Введите название DLE"
required
>
</div>
<div class="form-group">
<label for="dleAddress">Адрес контракта:</label>
<input
id="dleAddress"
v-model="newDle.address"
type="text"
placeholder="0x..."
required
>
</div>
<div class="form-group">
<label for="dleLocation">Местонахождение:</label>
<input
id="dleLocation"
v-model="newDle.location"
type="text"
placeholder="Страна, город"
required
>
</div>
<div class="form-actions">
<button type="button" @click="showAddDleForm = false" class="btn-secondary">
Отмена
</button>
<button type="submit" class="btn-primary">
Добавить DLE
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
// Определяем props
const props = defineProps({
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
// Состояние
const showAddDleForm = ref(false);
const newDle = ref({
name: '',
address: '',
location: ''
});
// Список DLE (временные данные для демонстрации)
const dleList = ref([
{
name: 'test2 (test2)',
address: '0xef49...dfD8',
location: '245000, 中国, 黄山市'
},
{
name: 'My DLE',
address: '0x1234...5678',
location: '101000, Россия, Москва'
}
]);
// Методы
const formatAddress = (address) => {
if (!address) return '';
if (address.length <= 10) return address;
return address.substring(0, 6) + '...' + address.substring(address.length - 4);
};
const addNewDle = () => {
if (!newDle.value.name || !newDle.value.address || !newDle.value.location) {
return;
}
dleList.value.push({
name: newDle.value.name,
address: newDle.value.address,
location: newDle.value.location
});
// Сброс формы
newDle.value = {
name: '',
address: '',
location: ''
};
showAddDleForm.value = false;
};
const removeDle = (address) => {
dleList.value = dleList.value.filter(dle => dle.address !== address);
};
const openDleInterface = (dle) => {
// Здесь будет логика открытия интерфейса DLE
// Вариант 1: Новая вкладка с внешним сайтом
// window.open(`https://example.com/dle/${dle.address}`, '_blank');
// Вариант 2: Встроенный интерфейс в текущей вкладке
router.push(`/management/dle/${dle.address}`);
};
</script>
<style scoped>
.dle-management-container {
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: flex-start;
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.header-content {
flex-grow: 1;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2.5rem;
margin: 0 0 10px 0;
}
.page-header p {
color: var(--color-grey-dark);
font-size: 1.1rem;
margin: 0;
}
.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;
flex-shrink: 0;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Карточки DLE */
.dle-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.dle-card {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
padding: 1.5rem;
border: 1px solid #e9ecef;
transition: all 0.3s ease;
cursor: pointer;
min-height: 150px;
display: flex;
flex-direction: column;
}
.dle-card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
.dle-card-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.dle-card-header h3 {
color: var(--color-primary);
margin: 0;
font-size: 1.3rem;
font-weight: 600;
}
.remove-btn {
background: none;
border: none;
font-size: 1.2rem;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: background 0.2s;
}
.remove-btn:hover {
background: #ffebee;
}
.dle-card-content {
flex-grow: 1;
}
.dle-address {
font-family: monospace;
font-size: 0.9rem;
color: var(--color-grey-dark);
margin: 0 0 0.5rem 0;
}
.dle-location {
font-size: 0.9rem;
color: var(--color-grey-dark);
margin: 0;
}
/* Карточка добавления */
.add-dle-card {
border: 2px dashed #dee2e6;
background: #f8f9fa;
justify-content: center;
align-items: center;
text-align: center;
}
.add-dle-card:hover {
border-color: var(--color-primary);
background: #f0f8ff;
}
.add-dle-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.add-icon {
font-size: 2rem;
color: var(--color-primary);
font-weight: bold;
}
.add-dle-content h3 {
color: var(--color-primary);
margin: 0;
font-size: 1.2rem;
}
.add-dle-content p {
color: var(--color-grey-dark);
margin: 0;
font-size: 0.9rem;
}
/* Модальное окно */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: var(--radius-lg);
width: 90%;
max-width: 500px;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid #e9ecef;
}
.modal-header h3 {
margin: 0;
color: var(--color-primary);
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: var(--color-grey-dark);
padding: 4px;
}
.modal-body {
padding: 1.5rem;
}
/* Форма */
.add-dle-form {
display: flex;
flex-direction: column;
gap: 1rem;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.form-group label {
font-weight: 600;
color: var(--color-grey-dark);
}
.form-group input {
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: var(--radius-sm);
font-size: 1rem;
}
.form-actions {
display: flex;
gap: 1rem;
justify-content: flex-end;
margin-top: 1rem;
}
.btn-primary {
background: var(--color-primary);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: background 0.2s;
}
.btn-primary:hover {
background: var(--color-primary-dark);
}
.btn-secondary {
background: var(--color-secondary);
color: white;
border: none;
padding: 0.75rem 1.5rem;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: background 0.2s;
}
.btn-secondary:hover {
background: var(--color-secondary-dark);
}
/* Адаптивность */
@media (max-width: 768px) {
.dle-cards {
grid-template-columns: 1fr;
}
.dle-card {
padding: 1rem;
}
.form-actions {
flex-direction: column;
}
}
</style>

View File

@@ -0,0 +1,815 @@
<!--
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>
<div class="dle-modules-management">
<div class="modules-header">
<h3>🧩 Управление модулями</h3>
<button class="btn btn-primary" @click="showAddModuleForm = true">
<i class="fas fa-plus"></i> Добавить модуль
</button>
</div>
<!-- Форма добавления модуля -->
<div v-if="showAddModuleForm" class="add-module-form">
<div class="form-header">
<h4>🧩 Добавить модуль</h4>
<button class="close-btn" @click="showAddModuleForm = false">×</button>
</div>
<div class="form-content">
<!-- Информация о модуле -->
<div class="form-section">
<h5>📋 Информация о модуле</h5>
<div class="form-group">
<label for="moduleId">ID модуля:</label>
<input
type="text"
id="moduleId"
v-model="newModule.moduleId"
class="form-control"
placeholder="TreasuryModule"
>
<small class="form-help">Уникальный идентификатор модуля (например: TreasuryModule)</small>
</div>
<div class="form-group">
<label for="moduleAddress">Адрес модуля:</label>
<input
type="text"
id="moduleAddress"
v-model="newModule.moduleAddress"
class="form-control"
placeholder="0x..."
>
<small class="form-help">Адрес смарт-контракта модуля</small>
</div>
<div class="form-group">
<label for="moduleName">Название модуля:</label>
<input
type="text"
id="moduleName"
v-model="newModule.name"
class="form-control"
placeholder="Казначейство"
>
</div>
<div class="form-group">
<label for="moduleDescription">Описание модуля:</label>
<textarea
id="moduleDescription"
v-model="newModule.description"
class="form-control"
rows="3"
placeholder="Описание функциональности модуля..."
></textarea>
</div>
</div>
<!-- Выбор типа модуля -->
<div class="form-section">
<h5>🔧 Тип модуля</h5>
<div class="module-types">
<div class="form-group">
<label for="moduleType">Выберите тип модуля:</label>
<select id="moduleType" v-model="newModule.type" class="form-control">
<option value="">-- Выберите тип --</option>
<option value="treasury">Казначейство</option>
<option value="voting">Голосование</option>
<option value="communication">Коммуникации</option>
<option value="custom">Пользовательский</option>
</select>
</div>
<!-- Специфичные настройки для казначейства -->
<div v-if="newModule.type === 'treasury'" class="module-settings">
<h6>Настройки казначейства</h6>
<div class="form-group">
<label for="treasuryTokens">Токены для управления:</label>
<select id="treasuryTokens" v-model="newModule.settings.tokens" multiple class="form-control">
<option value="ETH">ETH</option>
<option value="USDT">USDT</option>
<option value="USDC">USDC</option>
<option value="DAI">DAI</option>
</select>
</div>
<div class="form-group">
<label for="treasuryLimit">Лимит операций:</label>
<input
type="number"
id="treasuryLimit"
v-model.number="newModule.settings.limit"
class="form-control"
placeholder="1000"
>
</div>
</div>
<!-- Специфичные настройки для голосования -->
<div v-if="newModule.type === 'voting'" class="module-settings">
<h6>Настройки голосования</h6>
<div class="form-group">
<label for="votingType">Тип голосования:</label>
<select id="votingType" v-model="newModule.settings.votingType" class="form-control">
<option value="simple">Простое большинство</option>
<option value="weighted">Взвешенное голосование</option>
<option value="quadratic">Квадратичное голосование</option>
</select>
</div>
<div class="form-group">
<label for="votingDuration">Длительность голосования (дни):</label>
<input
type="number"
id="votingDuration"
v-model.number="newModule.settings.duration"
class="form-control"
min="1"
max="30"
placeholder="7"
>
</div>
</div>
<!-- Специфичные настройки для коммуникаций -->
<div v-if="newModule.type === 'communication'" class="module-settings">
<h6>Настройки коммуникаций</h6>
<div class="form-group">
<label for="communicationChannels">Каналы связи:</label>
<div class="checkbox-group">
<label><input type="checkbox" v-model="newModule.settings.channels.email"> Email</label>
<label><input type="checkbox" v-model="newModule.settings.channels.telegram"> Telegram</label>
<label><input type="checkbox" v-model="newModule.settings.channels.discord"> Discord</label>
<label><input type="checkbox" v-model="newModule.settings.channels.slack"> Slack</label>
</div>
</div>
</div>
</div>
</div>
<!-- Предварительный просмотр -->
<div class="form-section">
<h5>👁 Предварительный просмотр</h5>
<div class="preview-card">
<div class="preview-item">
<strong>ID модуля:</strong> {{ newModule.moduleId || 'Не указан' }}
</div>
<div class="preview-item">
<strong>Адрес:</strong> {{ shortenAddress(newModule.moduleAddress) || 'Не указан' }}
</div>
<div class="preview-item">
<strong>Название:</strong> {{ newModule.name || 'Не указано' }}
</div>
<div class="preview-item">
<strong>Тип:</strong> {{ getModuleTypeName(newModule.type) || 'Не выбран' }}
</div>
<div class="preview-item">
<strong>Описание:</strong> {{ newModule.description || 'Не указано' }}
</div>
<div v-if="newModule.type" class="preview-item">
<strong>Настройки:</strong> {{ getModuleSettingsPreview() }}
</div>
</div>
</div>
<!-- Действия -->
<div class="form-actions">
<button
class="btn btn-success"
@click="addModule"
:disabled="!isFormValid || isAdding"
>
<i class="fas fa-plus"></i>
{{ isAdding ? 'Добавление...' : 'Добавить модуль' }}
</button>
<button class="btn btn-secondary" @click="resetForm">
<i class="fas fa-undo"></i> Сбросить
</button>
<button class="btn btn-danger" @click="showAddModuleForm = false">
<i class="fas fa-times"></i> Отмена
</button>
</div>
</div>
</div>
<!-- Список модулей -->
<div class="modules-list">
<div class="list-header">
<h4>📋 Установленные модули</h4>
<div class="list-filters">
<select v-model="typeFilter" class="form-control">
<option value="">Все типы</option>
<option value="treasury">Казначейство</option>
<option value="voting">Голосование</option>
<option value="communication">Коммуникации</option>
<option value="custom">Пользовательские</option>
</select>
</div>
</div>
<div v-if="filteredModules.length === 0" class="no-modules">
<p>Установленных модулей пока нет</p>
</div>
<div v-else class="modules-grid">
<div
v-for="module in filteredModules"
:key="module.moduleId"
class="module-card"
:class="module.type"
>
<div class="module-header">
<h5>{{ module.name }}</h5>
<span class="module-type">{{ getModuleTypeName(module.type) }}</span>
</div>
<div class="module-details">
<div class="detail-item">
<strong>ID:</strong> {{ module.moduleId }}
</div>
<div class="detail-item">
<strong>Адрес:</strong> {{ shortenAddress(module.moduleAddress) }}
</div>
<div class="detail-item">
<strong>Описание:</strong> {{ module.description }}
</div>
<div class="detail-item">
<strong>Статус:</strong>
<span class="module-status" :class="{ 'active': module.isActive }">
{{ module.isActive ? 'Активен' : 'Неактивен' }}
</span>
</div>
</div>
<div class="module-actions">
<button
class="btn btn-sm btn-info"
@click="viewModuleDetails(module.moduleId)"
>
<i class="fas fa-eye"></i> Детали
</button>
<button
class="btn btn-sm btn-warning"
@click="configureModule(module.moduleId)"
>
<i class="fas fa-cog"></i> Настроить
</button>
<button
class="btn btn-sm btn-danger"
@click="removeModule(module.moduleId)"
>
<i class="fas fa-trash"></i> Удалить
</button>
</div>
</div>
</div>
</div>
<!-- Доступные модули -->
<div class="available-modules">
<h4>📦 Доступные модули</h4>
<p>Модули, которые можно установить в вашем DLE</p>
<div class="available-modules-grid">
<div
v-for="availableModule in availableModules"
:key="availableModule.id"
class="available-module-card"
>
<div class="module-icon">
<i :class="availableModule.icon"></i>
</div>
<div class="module-info">
<h6>{{ availableModule.name }}</h6>
<p>{{ availableModule.description }}</p>
<div class="module-features">
<span v-for="feature in availableModule.features" :key="feature" class="feature-tag">
{{ feature }}
</span>
</div>
</div>
<div class="module-actions">
<button
class="btn btn-sm btn-success"
@click="installAvailableModule(availableModule)"
>
<i class="fas fa-download"></i> Установить
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAuthContext } from '@/composables/useAuth';
const props = defineProps({
dleAddress: { type: String, required: true },
dleContract: { type: Object, required: true }
});
const { address } = useAuthContext();
// Состояние формы
const showAddModuleForm = ref(false);
const isAdding = ref(false);
const typeFilter = ref('');
// Новый модуль
const newModule = ref({
moduleId: '',
moduleAddress: '',
name: '',
description: '',
type: '',
settings: {
tokens: [],
limit: 0,
votingType: 'simple',
duration: 7,
channels: {
email: false,
telegram: false,
discord: false,
slack: false
}
}
});
// Установленные модули
const modules = ref([]);
// Доступные модули
const availableModules = ref([
{
id: 'treasury',
name: 'Казначейство',
description: 'Управление финансами DLE, прием и отправка токенов',
icon: 'fas fa-coins',
features: ['Управление токенами', 'Бюджетирование', 'Отчетность'],
type: 'treasury'
},
{
id: 'hierarchical-voting',
name: 'Иерархическое голосование',
description: 'Продвинутая система голосования с иерархией',
icon: 'fas fa-sitemap',
features: ['Иерархия', 'Взвешенное голосование', 'Делегирование'],
type: 'voting'
},
{
id: 'communication',
name: 'Коммуникации',
description: 'Система уведомлений и коммуникаций',
icon: 'fas fa-comments',
features: ['Уведомления', 'Каналы связи', 'Автоматизация'],
type: 'communication'
},
{
id: 'analytics',
name: 'Аналитика',
description: 'Аналитические инструменты для DLE',
icon: 'fas fa-chart-line',
features: ['Статистика', 'Графики', 'Отчеты'],
type: 'custom'
}
]);
// Вычисляемые свойства
const isFormValid = computed(() => {
return (
newModule.value.moduleId &&
newModule.value.moduleAddress &&
newModule.value.name &&
newModule.value.type
);
});
const filteredModules = computed(() => {
if (!typeFilter.value) return modules.value;
return modules.value.filter(m => m.type === typeFilter.value);
});
// Функции
function getModuleTypeName(type) {
const types = {
'treasury': 'Казначейство',
'voting': 'Голосование',
'communication': 'Коммуникации',
'custom': 'Пользовательский'
};
return types[type] || 'Неизвестный тип';
}
function getModuleSettingsPreview() {
const settings = newModule.value.settings;
switch (newModule.value.type) {
case 'treasury':
return `Токены: ${settings.tokens.join(', ') || 'Не выбраны'}, Лимит: ${settings.limit}`;
case 'voting':
return `Тип: ${settings.votingType}, Длительность: ${settings.duration} дней`;
case 'communication':
const channels = Object.entries(settings.channels)
.filter(([_, enabled]) => enabled)
.map(([name, _]) => name);
return `Каналы: ${channels.join(', ') || 'Не выбраны'}`;
default:
return 'Нет настроек';
}
}
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
// Добавление модуля
async function addModule() {
if (!isFormValid.value) {
alert('Пожалуйста, заполните все обязательные поля');
return;
}
isAdding.value = true;
try {
// Вызов смарт-контракта
const tx = await props.dleContract.addModule(
newModule.value.moduleId,
newModule.value.moduleAddress
);
await tx.wait();
// Обновляем список модулей
await loadModules();
// Сбрасываем форму
resetForm();
showAddModuleForm.value = false;
alert('✅ Модуль успешно добавлен!');
} catch (error) {
console.error('Ошибка при добавлении модуля:', error);
alert('❌ Ошибка при добавлении модуля: ' + error.message);
} finally {
isAdding.value = false;
}
}
// Удаление модуля
async function removeModule(moduleId) {
if (!confirm(`Удалить модуль "${moduleId}"?`)) return;
try {
const tx = await props.dleContract.removeModule(moduleId);
await tx.wait();
await loadModules();
alert('✅ Модуль успешно удален!');
} catch (error) {
console.error('Ошибка при удалении модуля:', error);
alert('❌ Ошибка при удалении модуля: ' + error.message);
}
}
// Установка доступного модуля
async function installAvailableModule(availableModule) {
// Здесь должна быть логика установки модуля
// Например, деплой модуля и добавление в DLE
console.log('Установка модуля:', availableModule);
alert(`Модуль "${availableModule.name}" будет установлен`);
}
// Загрузка модулей
async function loadModules() {
try {
// Здесь должен быть вызов API или смарт-контракта для загрузки модулей
// Пока используем заглушку
modules.value = [];
} catch (error) {
console.error('Ошибка при загрузке модулей:', error);
}
}
function resetForm() {
newModule.value = {
moduleId: '',
moduleAddress: '',
name: '',
description: '',
type: '',
settings: {
tokens: [],
limit: 0,
votingType: 'simple',
duration: 7,
channels: {
email: false,
telegram: false,
discord: false,
slack: false
}
}
};
}
function viewModuleDetails(moduleId) {
// Открыть модальное окно с деталями модуля
console.log('Просмотр деталей модуля:', moduleId);
}
function configureModule(moduleId) {
// Открыть форму настройки модуля
console.log('Настройка модуля:', moduleId);
}
onMounted(() => {
loadModules();
});
</script>
<style scoped>
.dle-modules-management {
padding: 1rem;
}
.modules-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.add-module-form {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
}
.form-section {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.form-section:last-child {
border-bottom: none;
}
.form-section h5 {
color: #333;
margin-bottom: 1rem;
}
.module-types {
margin-top: 1rem;
}
.module-settings {
margin-top: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 6px;
}
.module-settings h6 {
color: #333;
margin-bottom: 1rem;
}
.checkbox-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.checkbox-group label {
display: flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.preview-card {
background: #fff;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 1rem;
}
.preview-item {
margin-bottom: 0.5rem;
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.modules-list {
margin-top: 2rem;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.modules-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.module-card {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
background: #fff;
}
.module-card.treasury {
border-left: 4px solid #28a745;
}
.module-card.voting {
border-left: 4px solid #007bff;
}
.module-card.communication {
border-left: 4px solid #ffc107;
}
.module-card.custom {
border-left: 4px solid #6c757d;
}
.module-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.module-header h5 {
margin: 0;
color: #333;
}
.module-type {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 600;
background: #e9ecef;
color: #495057;
}
.module-details {
margin-bottom: 1rem;
}
.detail-item {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.module-status {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 600;
}
.module-status.active {
background: #d4edda;
color: #155724;
}
.module-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.no-modules {
text-align: center;
padding: 2rem;
color: #666;
}
.available-modules {
margin-top: 3rem;
}
.available-modules h4 {
margin-bottom: 1rem;
}
.available-modules p {
color: #666;
margin-bottom: 2rem;
}
.available-modules-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.available-module-card {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
background: #fff;
display: flex;
align-items: center;
gap: 1rem;
}
.module-icon {
width: 50px;
height: 50px;
background: #f8f9fa;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #007bff;
font-size: 1.5rem;
flex-shrink: 0;
}
.module-info {
flex-grow: 1;
}
.module-info h6 {
margin: 0 0 0.5rem 0;
color: #333;
}
.module-info p {
margin: 0 0 0.5rem 0;
font-size: 0.9rem;
color: #666;
}
.module-features {
display: flex;
gap: 0.25rem;
flex-wrap: wrap;
}
.feature-tag {
background: #e9ecef;
color: #495057;
padding: 0.125rem 0.5rem;
border-radius: 12px;
font-size: 0.75rem;
}
.form-help {
font-size: 0.9rem;
color: #666;
margin-top: 0.25rem;
}
</style>

View File

@@ -0,0 +1,711 @@
<!--
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>
<div class="dle-multisig-management">
<div class="multisig-header">
<h3>🔐 Управление мультиподписью</h3>
<button class="btn btn-primary" @click="showCreateForm = true">
<i class="fas fa-plus"></i> Создать операцию
</button>
</div>
<!-- Форма создания мультиподписи -->
<div v-if="showCreateForm" class="create-multisig-form">
<div class="form-header">
<h4>🔐 Новая мультиподпись</h4>
<button class="close-btn" @click="showCreateForm = false">×</button>
</div>
<div class="form-content">
<!-- Описание операции -->
<div class="form-section">
<h5>📝 Описание операции</h5>
<div class="form-group">
<label for="operationDescription">Описание операции:</label>
<textarea
id="operationDescription"
v-model="newOperation.description"
class="form-control"
rows="3"
placeholder="Опишите, что нужно сделать..."
></textarea>
</div>
<div class="form-group">
<label for="operationDuration">Длительность сбора подписей (дни):</label>
<input
type="number"
id="operationDuration"
v-model.number="newOperation.duration"
class="form-control"
min="1"
max="30"
placeholder="7"
>
</div>
</div>
<!-- Тип операции -->
<div class="form-section">
<h5> Тип операции</h5>
<div class="operation-types">
<div class="form-group">
<label for="multisigOperationType">Выберите тип операции:</label>
<select id="multisigOperationType" v-model="newOperation.operationType" class="form-control">
<option value="">-- Выберите тип --</option>
<option value="transfer">Передача токенов</option>
<option value="mint">Минтинг токенов</option>
<option value="burn">Сжигание токенов</option>
<option value="addModule">Добавить модуль</option>
<option value="removeModule">Удалить модуль</option>
<option value="custom">Пользовательская операция</option>
</select>
</div>
<!-- Параметры для передачи токенов -->
<div v-if="newOperation.operationType === 'transfer'" class="operation-params">
<div class="form-group">
<label for="multisigTransferTo">Адрес получателя:</label>
<input
type="text"
id="multisigTransferTo"
v-model="newOperation.operationParams.to"
class="form-control"
placeholder="0x..."
>
</div>
<div class="form-group">
<label for="multisigTransferAmount">Количество токенов:</label>
<input
type="number"
id="multisigTransferAmount"
v-model.number="newOperation.operationParams.amount"
class="form-control"
min="1"
placeholder="100"
>
</div>
</div>
<!-- Параметры для модулей -->
<div v-if="newOperation.operationType === 'addModule' || newOperation.operationType === 'removeModule'" class="operation-params">
<div class="form-group">
<label for="moduleId">ID модуля:</label>
<input
type="text"
id="moduleId"
v-model="newOperation.operationParams.moduleId"
class="form-control"
placeholder="TreasuryModule"
>
</div>
<div v-if="newOperation.operationType === 'addModule'" class="form-group">
<label for="moduleAddress">Адрес модуля:</label>
<input
type="text"
id="moduleAddress"
v-model="newOperation.operationParams.moduleAddress"
class="form-control"
placeholder="0x..."
>
</div>
</div>
<!-- Пользовательская операция -->
<div v-if="newOperation.operationType === 'custom'" class="operation-params">
<div class="form-group">
<label for="customMultisigOperation">Пользовательская операция (hex):</label>
<textarea
id="customMultisigOperation"
v-model="newOperation.operationParams.customData"
class="form-control"
rows="3"
placeholder="0x..."
></textarea>
</div>
</div>
</div>
</div>
<!-- Предварительный просмотр -->
<div class="form-section">
<h5>👁 Предварительный просмотр</h5>
<div class="preview-card">
<div class="preview-item">
<strong>Описание:</strong> {{ newOperation.description || 'Не указано' }}
</div>
<div class="preview-item">
<strong>Длительность:</strong> {{ newOperation.duration || 7 }} дней
</div>
<div class="preview-item">
<strong>Тип операции:</strong> {{ getOperationTypeName(newOperation.operationType) || 'Не выбран' }}
</div>
<div v-if="newOperation.operationType" class="preview-item">
<strong>Параметры:</strong> {{ getOperationParamsPreview() }}
</div>
<div class="preview-item">
<strong>Хеш операции:</strong> {{ getOperationHash() }}
</div>
</div>
</div>
<!-- Действия -->
<div class="form-actions">
<button
class="btn btn-success"
@click="createMultisigOperation"
:disabled="!isFormValid || isCreating"
>
<i class="fas fa-paper-plane"></i>
{{ isCreating ? 'Создание...' : 'Создать операцию' }}
</button>
<button class="btn btn-secondary" @click="resetForm">
<i class="fas fa-undo"></i> Сбросить
</button>
<button class="btn btn-danger" @click="showCreateForm = false">
<i class="fas fa-times"></i> Отмена
</button>
</div>
</div>
</div>
<!-- Список операций мультиподписи -->
<div class="multisig-list">
<div class="list-header">
<h4>📋 Список операций мультиподписи</h4>
<div class="list-filters">
<select v-model="statusFilter" class="form-control">
<option value="">Все статусы</option>
<option value="active">Активные</option>
<option value="pending">Ожидающие</option>
<option value="succeeded">Принятые</option>
<option value="defeated">Отклоненные</option>
<option value="executed">Выполненные</option>
</select>
</div>
</div>
<div v-if="filteredOperations.length === 0" class="no-operations">
<p>Операций мультиподписи пока нет</p>
</div>
<div v-else class="operations-grid">
<div
v-for="operation in filteredOperations"
:key="operation.id"
class="operation-card"
:class="operation.status"
>
<div class="operation-header">
<h5>{{ operation.description }}</h5>
<span class="operation-status" :class="operation.status">
{{ getOperationStatusText(operation.status) }}
</span>
</div>
<div class="operation-details">
<div class="detail-item">
<strong>ID:</strong> #{{ operation.id }}
</div>
<div class="detail-item">
<strong>Создатель:</strong> {{ shortenAddress(operation.initiator) }}
</div>
<div class="detail-item">
<strong>Хеш:</strong> {{ shortenAddress(operation.operationHash) }}
</div>
<div class="detail-item">
<strong>Дедлайн:</strong> {{ formatDate(operation.deadline) }}
</div>
<div class="detail-item">
<strong>Подписи:</strong>
<span class="signatures">
<span class="for">За: {{ operation.forSignatures }}</span>
<span class="against">Против: {{ operation.againstSignatures }}</span>
</span>
</div>
</div>
<div class="operation-actions">
<button
v-if="canSign(operation)"
class="btn btn-sm btn-success"
@click="signOperation(operation.id, true)"
:disabled="hasSigned(operation.id, true)"
>
<i class="fas fa-thumbs-up"></i> Подписать за
</button>
<button
v-if="canSign(operation)"
class="btn btn-sm btn-danger"
@click="signOperation(operation.id, false)"
:disabled="hasSigned(operation.id, false)"
>
<i class="fas fa-thumbs-down"></i> Подписать против
</button>
<button
v-if="canExecute(operation)"
class="btn btn-sm btn-primary"
@click="executeOperation(operation.id)"
>
<i class="fas fa-play"></i> Исполнить
</button>
<button
class="btn btn-sm btn-info"
@click="viewOperationDetails(operation.id)"
>
<i class="fas fa-eye"></i> Детали
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAuthContext } from '@/composables/useAuth';
const props = defineProps({
dleAddress: { type: String, required: true },
dleContract: { type: Object, required: true }
});
const { address } = useAuthContext();
// Состояние формы
const showCreateForm = ref(false);
const isCreating = ref(false);
const statusFilter = ref('');
// Новая операция
const newOperation = ref({
description: '',
duration: 7,
operationType: '',
operationParams: {
to: '',
from: '',
amount: 0,
moduleId: '',
moduleAddress: '',
customData: ''
}
});
// Операции мультиподписи
const operations = ref([]);
// Вычисляемые свойства
const isFormValid = computed(() => {
return (
newOperation.value.description &&
newOperation.value.duration > 0 &&
newOperation.value.operationType &&
validateOperationParams()
);
});
const filteredOperations = computed(() => {
if (!statusFilter.value) return operations.value;
return operations.value.filter(o => o.status === statusFilter.value);
});
// Функции
function validateOperationParams() {
const params = newOperation.value.operationParams;
switch (newOperation.value.operationType) {
case 'transfer':
case 'mint':
return params.to && params.amount > 0;
case 'burn':
return params.from && params.amount > 0;
case 'addModule':
return params.moduleId && params.moduleAddress;
case 'removeModule':
return params.moduleId;
case 'custom':
return params.customData && params.customData.startsWith('0x');
default:
return false;
}
}
function getOperationTypeName(type) {
const types = {
'transfer': 'Передача токенов',
'mint': 'Минтинг токенов',
'burn': 'Сжигание токенов',
'addModule': 'Добавить модуль',
'removeModule': 'Удалить модуль',
'custom': 'Пользовательская операция'
};
return types[type] || 'Неизвестный тип';
}
function getOperationParamsPreview() {
const params = newOperation.value.operationParams;
switch (newOperation.value.operationType) {
case 'transfer':
return `Кому: ${shortenAddress(params.to)}, Количество: ${params.amount}`;
case 'mint':
return `Кому: ${shortenAddress(params.to)}, Количество: ${params.amount}`;
case 'burn':
return `От: ${shortenAddress(params.from)}, Количество: ${params.amount}`;
case 'addModule':
return `ID: ${params.moduleId}, Адрес: ${shortenAddress(params.moduleAddress)}`;
case 'removeModule':
return `ID: ${params.moduleId}`;
case 'custom':
return `Данные: ${params.customData.substring(0, 20)}...`;
default:
return 'Не указаны';
}
}
function getOperationHash() {
// Генерируем хеш операции на основе параметров
const params = newOperation.value.operationParams;
const operationData = JSON.stringify({
type: newOperation.value.operationType,
params: params
});
// Простой хеш для демонстрации
return '0x' + btoa(operationData).substring(0, 64);
}
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function formatDate(timestamp) {
if (!timestamp) return 'N/A';
return new Date(timestamp * 1000).toLocaleString();
}
function getOperationStatusText(status) {
const statusMap = {
'pending': 'Ожидает',
'active': 'Активно',
'succeeded': 'Принято',
'defeated': 'Отклонено',
'executed': 'Выполнено'
};
return statusMap[status] || status;
}
function canSign(operation) {
return operation.status === 'active' && !hasSigned(operation.id);
}
function canExecute(operation) {
return operation.status === 'succeeded' && !operation.executed;
}
function hasSigned(operationId, support = null) {
// Здесь должна быть проверка подписи пользователя
return false;
}
// Создание операции мультиподписи
async function createMultisigOperation() {
if (!isFormValid.value) {
alert('Пожалуйста, заполните все обязательные поля');
return;
}
isCreating.value = true;
try {
// Генерируем хеш операции
const operationHash = getOperationHash();
// Вызов смарт-контракта
const tx = await props.dleContract.createMultiSigOperation(
operationHash,
newOperation.value.duration * 24 * 60 * 60 // конвертируем в секунды
);
await tx.wait();
// Обновляем список операций
await loadOperations();
// Сбрасываем форму
resetForm();
showCreateForm.value = false;
alert('✅ Операция мультиподписи успешно создана!');
} catch (error) {
console.error('Ошибка при создании операции мультиподписи:', error);
alert('❌ Ошибка при создании операции: ' + error.message);
} finally {
isCreating.value = false;
}
}
// Подписание операции
async function signOperation(operationId, support) {
try {
const tx = await props.dleContract.signMultiSigOperation(operationId, support);
await tx.wait();
await loadOperations();
alert('✅ Ваша подпись учтена!');
} catch (error) {
console.error('Ошибка при подписании операции:', error);
alert('❌ Ошибка при подписании: ' + error.message);
}
}
// Исполнение операции
async function executeOperation(operationId) {
try {
const tx = await props.dleContract.executeMultiSigOperation(operationId);
await tx.wait();
await loadOperations();
alert('✅ Операция успешно исполнена!');
} catch (error) {
console.error('Ошибка при исполнении операции:', error);
alert('❌ Ошибка при исполнении операции: ' + error.message);
}
}
// Загрузка операций
async function loadOperations() {
try {
// Здесь должен быть вызов API или смарт-контракта для загрузки операций
// Пока используем заглушку
operations.value = [];
} catch (error) {
console.error('Ошибка при загрузке операций:', error);
}
}
function resetForm() {
newOperation.value = {
description: '',
duration: 7,
operationType: '',
operationParams: {
to: '',
from: '',
amount: 0,
moduleId: '',
moduleAddress: '',
customData: ''
}
};
}
function viewOperationDetails(operationId) {
// Открыть модальное окно с деталями операции
console.log('Просмотр деталей операции:', operationId);
}
onMounted(() => {
loadOperations();
});
</script>
<style scoped>
.dle-multisig-management {
padding: 1rem;
}
.multisig-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.create-multisig-form {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
}
.form-section {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.form-section:last-child {
border-bottom: none;
}
.form-section h5 {
color: #333;
margin-bottom: 1rem;
}
.operation-types {
margin-top: 1rem;
}
.operation-params {
margin-top: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 6px;
}
.preview-card {
background: #fff;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 1rem;
}
.preview-item {
margin-bottom: 0.5rem;
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.multisig-list {
margin-top: 2rem;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.operations-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.operation-card {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
background: #fff;
}
.operation-card.active {
border-color: #28a745;
}
.operation-card.succeeded {
border-color: #007bff;
}
.operation-card.defeated {
border-color: #dc3545;
}
.operation-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.operation-header h5 {
margin: 0;
color: #333;
}
.operation-status {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 600;
}
.operation-status.active {
background: #d4edda;
color: #155724;
}
.operation-status.succeeded {
background: #d1ecf1;
color: #0c5460;
}
.operation-status.defeated {
background: #f8d7da;
color: #721c24;
}
.operation-details {
margin-bottom: 1rem;
}
.detail-item {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.signatures {
display: flex;
gap: 1rem;
}
.signatures .for {
color: #28a745;
}
.signatures .against {
color: #dc3545;
}
.operation-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.no-operations {
text-align: center;
padding: 2rem;
color: #666;
}
</style>

View File

@@ -0,0 +1,844 @@
<!--
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>
<div class="dle-proposals-management">
<div class="proposals-header">
<h3>🗳 Управление предложениями</h3>
<button class="btn btn-primary" @click="showCreateForm = true">
<i class="fas fa-plus"></i> Создать предложение
</button>
</div>
<!-- Форма создания предложения -->
<div v-if="showCreateForm" class="create-proposal-form">
<div class="form-header">
<h4>📝 Новое предложение</h4>
<button class="close-btn" @click="showCreateForm = false">×</button>
</div>
<div class="form-content">
<!-- Основная информация -->
<div class="form-section">
<h5>📋 Основная информация</h5>
<div class="form-group">
<label for="proposalDescription">Описание предложения:</label>
<textarea
id="proposalDescription"
v-model="newProposal.description"
class="form-control"
rows="3"
placeholder="Опишите, что нужно сделать..."
></textarea>
</div>
<div class="form-group">
<label for="proposalDuration">Длительность голосования (дни):</label>
<input
type="number"
id="proposalDuration"
v-model.number="newProposal.duration"
class="form-control"
min="1"
max="30"
placeholder="7"
>
</div>
</div>
<!-- Выбор цепочки для кворума -->
<div class="form-section">
<h5>🔗 Выбор цепочки для кворума</h5>
<p class="form-help">Выберите цепочку, в которой будет происходить сбор голосов</p>
<div class="chains-grid">
<div
v-for="chain in availableChains"
:key="chain.chainId"
class="chain-option"
:class="{ 'selected': newProposal.governanceChainId === chain.chainId }"
@click="newProposal.governanceChainId = chain.chainId"
>
<div class="chain-info">
<h6>{{ chain.name }}</h6>
<span class="chain-id">Chain ID: {{ chain.chainId }}</span>
<p class="chain-description">{{ chain.description }}</p>
</div>
<div class="chain-status">
<i v-if="newProposal.governanceChainId === chain.chainId" class="fas fa-check"></i>
</div>
</div>
</div>
</div>
<!-- Тип операции -->
<div class="form-section">
<h5> Тип операции</h5>
<div class="operation-types">
<div class="form-group">
<label for="operationType">Выберите тип операции:</label>
<select id="operationType" v-model="newProposal.operationType" class="form-control">
<option value="">-- Выберите тип --</option>
<option value="transfer">Передача токенов</option>
<option value="mint">Минтинг токенов</option>
<option value="burn">Сжигание токенов</option>
<option value="custom">Пользовательская операция</option>
</select>
</div>
<!-- Параметры для передачи токенов -->
<div v-if="newProposal.operationType === 'transfer'" class="operation-params">
<div class="form-group">
<label for="transferTo">Адрес получателя:</label>
<input
type="text"
id="transferTo"
v-model="newProposal.operationParams.to"
class="form-control"
placeholder="0x..."
>
</div>
<div class="form-group">
<label for="transferAmount">Количество токенов:</label>
<input
type="number"
id="transferAmount"
v-model.number="newProposal.operationParams.amount"
class="form-control"
min="1"
placeholder="100"
>
</div>
</div>
<!-- Параметры для минтинга -->
<div v-if="newProposal.operationType === 'mint'" class="operation-params">
<div class="form-group">
<label for="mintTo">Адрес получателя:</label>
<input
type="text"
id="mintTo"
v-model="newProposal.operationParams.to"
class="form-control"
placeholder="0x..."
>
</div>
<div class="form-group">
<label for="mintAmount">Количество токенов:</label>
<input
type="number"
id="mintAmount"
v-model.number="newProposal.operationParams.amount"
class="form-control"
min="1"
placeholder="1000"
>
</div>
</div>
<!-- Параметры для сжигания -->
<div v-if="newProposal.operationType === 'burn'" class="operation-params">
<div class="form-group">
<label for="burnFrom">Адрес владельца:</label>
<input
type="text"
id="burnFrom"
v-model="newProposal.operationParams.from"
class="form-control"
placeholder="0x..."
>
</div>
<div class="form-group">
<label for="burnAmount">Количество токенов:</label>
<input
type="number"
id="burnAmount"
v-model.number="newProposal.operationParams.amount"
class="form-control"
min="1"
placeholder="100"
>
</div>
</div>
<!-- Пользовательская операция -->
<div v-if="newProposal.operationType === 'custom'" class="operation-params">
<div class="form-group">
<label for="customOperation">Пользовательская операция (hex):</label>
<textarea
id="customOperation"
v-model="newProposal.operationParams.customData"
class="form-control"
rows="3"
placeholder="0x..."
></textarea>
</div>
</div>
</div>
</div>
<!-- Предварительный просмотр -->
<div class="form-section">
<h5>👁 Предварительный просмотр</h5>
<div class="preview-card">
<div class="preview-item">
<strong>Описание:</strong> {{ newProposal.description || 'Не указано' }}
</div>
<div class="preview-item">
<strong>Длительность:</strong> {{ newProposal.duration || 7 }} дней
</div>
<div class="preview-item">
<strong>Цепочка для кворума:</strong>
{{ getChainName(newProposal.governanceChainId) || 'Не выбрана' }}
</div>
<div class="preview-item">
<strong>Тип операции:</strong> {{ getOperationTypeName(newProposal.operationType) || 'Не выбран' }}
</div>
<div v-if="newProposal.operationType" class="preview-item">
<strong>Параметры:</strong> {{ getOperationParamsPreview() }}
</div>
</div>
</div>
<!-- Действия -->
<div class="form-actions">
<button
class="btn btn-success"
@click="createProposal"
:disabled="!isFormValid || isCreating"
>
<i class="fas fa-paper-plane"></i>
{{ isCreating ? 'Создание...' : 'Создать предложение' }}
</button>
<button class="btn btn-secondary" @click="resetForm">
<i class="fas fa-undo"></i> Сбросить
</button>
<button class="btn btn-danger" @click="showCreateForm = false">
<i class="fas fa-times"></i> Отмена
</button>
</div>
</div>
</div>
<!-- Список предложений -->
<div class="proposals-list">
<div class="list-header">
<h4>📋 Список предложений</h4>
<div class="list-filters">
<select v-model="statusFilter" class="form-control">
<option value="">Все статусы</option>
<option value="active">Активные</option>
<option value="pending">Ожидающие</option>
<option value="succeeded">Принятые</option>
<option value="defeated">Отклоненные</option>
<option value="executed">Выполненные</option>
</select>
</div>
</div>
<div v-if="filteredProposals.length === 0" class="no-proposals">
<p>Предложений пока нет</p>
</div>
<div v-else class="proposals-grid">
<div
v-for="proposal in filteredProposals"
:key="proposal.id"
class="proposal-card"
:class="proposal.status"
>
<div class="proposal-header">
<h5>{{ proposal.description }}</h5>
<span class="proposal-status" :class="proposal.status">
{{ getProposalStatusText(proposal.status) }}
</span>
</div>
<div class="proposal-details">
<div class="detail-item">
<strong>ID:</strong> #{{ proposal.id }}
</div>
<div class="detail-item">
<strong>Создатель:</strong> {{ shortenAddress(proposal.initiator) }}
</div>
<div class="detail-item">
<strong>Цепочка:</strong> {{ getChainName(proposal.governanceChainId) }}
</div>
<div class="detail-item">
<strong>Дедлайн:</strong> {{ formatDate(proposal.deadline) }}
</div>
<div class="detail-item">
<strong>Голоса:</strong>
<span class="votes">
<span class="for">За: {{ proposal.forVotes }}</span>
<span class="against">Против: {{ proposal.againstVotes }}</span>
</span>
</div>
</div>
<div class="proposal-actions">
<button
v-if="canVote(proposal)"
class="btn btn-sm btn-success"
@click="voteForProposal(proposal.id, true)"
:disabled="hasVoted(proposal.id, true)"
>
<i class="fas fa-thumbs-up"></i> За
</button>
<button
v-if="canVote(proposal)"
class="btn btn-sm btn-danger"
@click="voteForProposal(proposal.id, false)"
:disabled="hasVoted(proposal.id, false)"
>
<i class="fas fa-thumbs-down"></i> Против
</button>
<button
v-if="canExecute(proposal)"
class="btn btn-sm btn-primary"
@click="executeProposal(proposal.id)"
>
<i class="fas fa-play"></i> Исполнить
</button>
<button
class="btn btn-sm btn-info"
@click="viewProposalDetails(proposal.id)"
>
<i class="fas fa-eye"></i> Детали
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useAuthContext } from '@/composables/useAuth';
const props = defineProps({
dleAddress: { type: String, required: true },
dleContract: { type: Object, required: true }
});
const { address } = useAuthContext();
// Состояние формы
const showCreateForm = ref(false);
const isCreating = ref(false);
const statusFilter = ref('');
// Новое предложение
const newProposal = ref({
description: '',
duration: 7,
governanceChainId: null,
operationType: '',
operationParams: {
to: '',
from: '',
amount: 0,
customData: ''
}
});
// Доступные цепочки
const availableChains = ref([
{ chainId: 1, name: 'Ethereum', description: 'Основная сеть Ethereum' },
{ chainId: 137, name: 'Polygon', description: 'Сеть Polygon' },
{ chainId: 56, name: 'BSC', description: 'Binance Smart Chain' },
{ chainId: 42161, name: 'Arbitrum', description: 'Arbitrum One' }
]);
// Предложения
const proposals = ref([]);
// Вычисляемые свойства
const isFormValid = computed(() => {
return (
newProposal.value.description &&
newProposal.value.duration > 0 &&
newProposal.value.governanceChainId &&
newProposal.value.operationType &&
validateOperationParams()
);
});
const filteredProposals = computed(() => {
if (!statusFilter.value) return proposals.value;
return proposals.value.filter(p => p.status === statusFilter.value);
});
// Функции
function validateOperationParams() {
const params = newProposal.value.operationParams;
switch (newProposal.value.operationType) {
case 'transfer':
case 'mint':
return params.to && params.amount > 0;
case 'burn':
return params.from && params.amount > 0;
case 'custom':
return params.customData && params.customData.startsWith('0x');
default:
return false;
}
}
function getChainName(chainId) {
const chain = availableChains.value.find(c => c.chainId === chainId);
return chain ? chain.name : 'Неизвестная сеть';
}
function getOperationTypeName(type) {
const types = {
'transfer': 'Передача токенов',
'mint': 'Минтинг токенов',
'burn': 'Сжигание токенов',
'custom': 'Пользовательская операция'
};
return types[type] || 'Неизвестный тип';
}
function getOperationParamsPreview() {
const params = newProposal.value.operationParams;
switch (newProposal.value.operationType) {
case 'transfer':
return `Кому: ${shortenAddress(params.to)}, Количество: ${params.amount}`;
case 'mint':
return `Кому: ${shortenAddress(params.to)}, Количество: ${params.amount}`;
case 'burn':
return `От: ${shortenAddress(params.from)}, Количество: ${params.amount}`;
case 'custom':
return `Данные: ${params.customData.substring(0, 20)}...`;
default:
return 'Не указаны';
}
}
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function formatDate(timestamp) {
if (!timestamp) return 'N/A';
return new Date(timestamp * 1000).toLocaleString();
}
function getProposalStatusText(status) {
const statusMap = {
'pending': 'Ожидает',
'active': 'Активно',
'succeeded': 'Принято',
'defeated': 'Отклонено',
'executed': 'Выполнено'
};
return statusMap[status] || status;
}
function canVote(proposal) {
return proposal.status === 'active' && !hasVoted(proposal.id);
}
function canExecute(proposal) {
return proposal.status === 'succeeded' && !proposal.executed;
}
function hasVoted(proposalId, support = null) {
// Здесь должна быть проверка голосования пользователя
return false;
}
// Создание предложения
async function createProposal() {
if (!isFormValid.value) {
alert('Пожалуйста, заполните все обязательные поля');
return;
}
isCreating.value = true;
try {
// Подготовка данных для смарт-контракта
const operation = encodeOperation();
// Вызов смарт-контракта
const tx = await props.dleContract.createProposal(
newProposal.value.description,
newProposal.value.duration * 24 * 60 * 60, // конвертируем в секунды
operation,
newProposal.value.governanceChainId
);
await tx.wait();
// Обновляем список предложений
await loadProposals();
// Сбрасываем форму
resetForm();
showCreateForm.value = false;
alert('✅ Предложение успешно создано!');
} catch (error) {
console.error('Ошибка при создании предложения:', error);
alert('❌ Ошибка при создании предложения: ' + error.message);
} finally {
isCreating.value = false;
}
}
function encodeOperation() {
const params = newProposal.value.operationParams;
switch (newProposal.value.operationType) {
case 'transfer':
return encodeTransferOperation(params.to, params.amount);
case 'mint':
return encodeMintOperation(params.to, params.amount);
case 'burn':
return encodeBurnOperation(params.from, params.amount);
case 'custom':
return params.customData;
default:
throw new Error('Неизвестный тип операции');
}
}
function encodeTransferOperation(to, amount) {
// Кодируем операцию передачи токенов
const abiCoder = new ethers.AbiCoder();
const selector = '0xa9059cbb'; // transfer(address,uint256)
const data = abiCoder.encode(['address', 'uint256'], [to, amount]);
return selector + data.slice(2);
}
function encodeMintOperation(to, amount) {
// Кодируем операцию минтинга токенов
const abiCoder = new ethers.AbiCoder();
const selector = '0x40c10f19'; // mint(address,uint256)
const data = abiCoder.encode(['address', 'uint256'], [to, amount]);
return selector + data.slice(2);
}
function encodeBurnOperation(from, amount) {
// Кодируем операцию сжигания токенов
const abiCoder = new ethers.AbiCoder();
const selector = '0x42966c68'; // burn(uint256)
const data = abiCoder.encode(['uint256'], [amount]);
return selector + data.slice(2);
}
// Голосование
async function voteForProposal(proposalId, support) {
try {
const tx = await props.dleContract.vote(proposalId, support);
await tx.wait();
await loadProposals();
alert('✅ Ваш голос учтен!');
} catch (error) {
console.error('Ошибка при голосовании:', error);
alert('❌ Ошибка при голосовании: ' + error.message);
}
}
// Исполнение предложения
async function executeProposal(proposalId) {
try {
const tx = await props.dleContract.executeProposal(proposalId);
await tx.wait();
await loadProposals();
alert('✅ Предложение успешно исполнено!');
} catch (error) {
console.error('Ошибка при исполнении предложения:', error);
alert('❌ Ошибка при исполнении предложения: ' + error.message);
}
}
// Загрузка предложений
async function loadProposals() {
try {
// Здесь должен быть вызов API или смарт-контракта для загрузки предложений
// Пока используем заглушку
proposals.value = [];
} catch (error) {
console.error('Ошибка при загрузке предложений:', error);
}
}
function resetForm() {
newProposal.value = {
description: '',
duration: 7,
governanceChainId: null,
operationType: '',
operationParams: {
to: '',
from: '',
amount: 0,
customData: ''
}
};
}
function viewProposalDetails(proposalId) {
// Открыть модальное окно с деталями предложения
console.log('Просмотр деталей предложения:', proposalId);
}
onMounted(() => {
loadProposals();
});
</script>
<style scoped>
.dle-proposals-management {
padding: 1rem;
}
.proposals-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.create-proposal-form {
background: #f8f9fa;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.form-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
cursor: pointer;
color: #666;
}
.form-section {
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #eee;
}
.form-section:last-child {
border-bottom: none;
}
.form-section h5 {
color: #333;
margin-bottom: 1rem;
}
.chains-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
}
.chain-option {
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
cursor: pointer;
transition: all 0.3s ease;
}
.chain-option:hover {
border-color: #007bff;
}
.chain-option.selected {
border-color: #007bff;
background: #f8f9ff;
}
.chain-info h6 {
margin: 0 0 0.5rem 0;
color: #333;
}
.chain-id {
font-size: 0.9rem;
color: #666;
}
.chain-description {
font-size: 0.9rem;
color: #888;
margin: 0.5rem 0 0 0;
}
.chain-status {
text-align: right;
color: #007bff;
}
.operation-types {
margin-top: 1rem;
}
.operation-params {
margin-top: 1rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 6px;
}
.preview-card {
background: #fff;
border: 1px solid #e9ecef;
border-radius: 6px;
padding: 1rem;
}
.preview-item {
margin-bottom: 0.5rem;
}
.form-actions {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.proposals-list {
margin-top: 2rem;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.proposals-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1rem;
}
.proposal-card {
border: 1px solid #e9ecef;
border-radius: 8px;
padding: 1rem;
background: #fff;
}
.proposal-card.active {
border-color: #28a745;
}
.proposal-card.succeeded {
border-color: #007bff;
}
.proposal-card.defeated {
border-color: #dc3545;
}
.proposal-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.proposal-header h5 {
margin: 0;
color: #333;
}
.proposal-status {
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 600;
}
.proposal-status.active {
background: #d4edda;
color: #155724;
}
.proposal-status.succeeded {
background: #d1ecf1;
color: #0c5460;
}
.proposal-status.defeated {
background: #f8d7da;
color: #721c24;
}
.proposal-details {
margin-bottom: 1rem;
}
.detail-item {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.votes {
display: flex;
gap: 1rem;
}
.votes .for {
color: #28a745;
}
.votes .against {
color: #dc3545;
}
.proposal-actions {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.no-proposals {
text-align: center;
padding: 2rem;
color: #666;
}
.form-help {
font-size: 0.9rem;
color: #666;
margin-bottom: 1rem;
}
</style>

View File

@@ -1,94 +0,0 @@
# Nginx конфигурация с SSL шифрованием
# HTTP -> HTTPS redirect
server {
listen 80;
server_name _;
return 301 https://$host$request_uri;
}
# HTTPS сервер
server {
listen 443 ssl http2;
server_name hb3-accelerator.com www.hb3-accelerator.com;
# SSL сертификаты
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/certs/server.key;
ssl_trusted_certificate /etc/ssl/certs/ca.crt;
# SSL настройки безопасности
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_stapling on;
ssl_stapling_verify on;
# Заголовки безопасности
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Проверка Host header
if ($host !~ ^(hb3-accelerator\.com|www\.hb3-accelerator\.com)$) {
return 444;
}
root /usr/share/nginx/html;
index index.html;
# Основной location
location / {
try_files $uri $uri/ /index.html =404;
}
# API с дополнительной защитой
location /api/ {
proxy_pass http://backend:8000/api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
# Таймауты
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# WebSocket с SSL
location /ws {
proxy_pass http://backend:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
# Статические файлы
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
add_header Vary Accept-Encoding;
}
# Запрет доступа к чувствительным файлам
location ~* /(\.htaccess|\.htpasswd|\.env|\.git|\.svn|\.DS_Store|Thumbs\.db|web\.config|robots\.txt|sitemap\.xml)$ {
deny all;
return 404;
}
# Скрытие информации о сервере
server_tokens off;
# Логирование
error_log /var/log/nginx/error.log warn;
access_log /var/log/nginx/access.log combined;
}

View File

@@ -1,49 +0,0 @@
#!/bin/bash
# Скрипт мониторинга безопасности для nginx
# Автоматически блокирует подозрительные IP
LOG_FILE="/var/log/nginx/access.log"
BLOCKED_IPS="/tmp/blocked_ips.txt"
MAX_REQUESTS=100 # Максимум запросов в минуту
BLOCK_TIME=3600 # Время блокировки в секундах (1 час)
# Создаем файл для заблокированных IP если его нет
touch "$BLOCKED_IPS"
echo "$(date): Запуск мониторинга безопасности..."
while true; do
# Анализируем логи за последнюю минуту
SUSPICIOUS_IPS=$(tail -n 1000 "$LOG_FILE" 2>/dev/null | \
awk -v date="$(date -d '1 minute ago' '+%d/%b/%Y:%H:%M')" \
'$4 ~ date {print $1}' | \
sort | uniq -c | \
awk -v max="$MAX_REQUESTS" '$1 > max {print $2}')
# Блокируем подозрительные IP
for ip in $SUSPICIOUS_IPS; do
if ! grep -q "^$ip$" "$BLOCKED_IPS"; then
echo "$ip" >> "$BLOCKED_IPS"
echo "$(date): Блокируем IP $ip за подозрительную активность"
# Добавляем правило в iptables (если доступно)
if command -v iptables >/dev/null 2>&1; then
iptables -A INPUT -s "$ip" -j DROP
echo "$(date): IP $ip заблокирован в iptables"
fi
fi
done
# Очищаем старые блокировки
while IFS= read -r ip; do
# Проверяем, не истекло ли время блокировки
if [ -f "$BLOCKED_IPS" ]; then
# Простая реализация - можно улучшить
echo "$(date): Проверка блокировок..."
fi
done < "$BLOCKED_IPS"
# Ждем 30 секунд перед следующей проверкой
sleep 30
done

View File

@@ -1,18 +1,36 @@
#!/bin/bash
# Скрипт мониторинга безопасности для DLE
# Автоматически блокирует подозрительные IP адреса
# Автоматически блокирует подозрительные IP адреса и домены
LOG_FILE="/var/log/nginx/access.log"
BLOCKED_IPS_FILE="/tmp/blocked_ips.txt"
SUSPICIOUS_DOMAINS_FILE="/tmp/suspicious_domains.txt"
NGINX_CONTAINER="dapp-frontend-nginx"
# Создаем файл для хранения заблокированных IP
# Создаем файлы для хранения данных
touch "$BLOCKED_IPS_FILE"
touch "$SUSPICIOUS_DOMAINS_FILE"
echo "🔒 Запуск мониторинга безопасности DLE..."
echo "📊 Логирование атак в: $LOG_FILE"
echo "🚫 Заблокированные IP: $BLOCKED_IPS_FILE"
echo "🌐 Подозрительные домены: $SUSPICIOUS_DOMAINS_FILE"
# Список подозрительных доменов
SUSPICIOUS_DOMAINS=(
"akamai-inputs-"
"gosipgambar"
"gitlab.cloud"
"autodiscover.home"
"akamai-san"
"akamai-inputs-cleanaway"
"akamai-inputs-hgmccarterenglish"
"akamai-inputs-nbpdnj"
"akamai-inputs-rvc"
"akamai-inputs-erau"
"akamai-inputs-notion"
)
# Функция для блокировки IP
block_ip() {
@@ -36,6 +54,25 @@ block_ip() {
echo "✅ IP $ip заблокирован в nginx"
}
# Функция для логирования подозрительных доменов
log_suspicious_domain() {
local domain=$1
local ip=$2
# Проверяем, не логировали ли уже этот домен
if grep -q "^$domain$" "$SUSPICIOUS_DOMAINS_FILE"; then
return
fi
echo "$domain" >> "$SUSPICIOUS_DOMAINS_FILE"
echo "🌐 Подозрительный домен: $domain (IP: $ip)"
# Блокируем IP, который обращается к подозрительному домену
if [ -n "$ip" ]; then
block_ip "$ip" "Обращение к подозрительному домену: $domain"
fi
}
# Функция для анализа логов
analyze_logs() {
echo "🔍 Анализ логов на предмет атак..."
@@ -45,6 +82,9 @@ analyze_logs() {
# Извлекаем IP адрес
ip=$(echo "$line" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}')
# Извлекаем домен из Referer
domain=$(echo "$line" | grep -oE 'https?://[^/]+' | sed 's|https\?://||')
if [ -n "$ip" ]; then
# Проверяем на подозрительные запросы
if echo "$line" | grep -q "\.env\|\.config\|\.ini\|\.sql\|\.bak\|\.log"; then
@@ -61,6 +101,14 @@ analyze_logs() {
block_ip "$ip" "Известный сканер/бот"
fi
# Проверяем на подозрительные домены
for suspicious in "${SUSPICIOUS_DOMAINS[@]}"; do
if echo "$domain" | grep -qi "$suspicious"; then
log_suspicious_domain "$domain" "$ip"
break
fi
done
# Проверяем на множественные запросы (DDoS)
request_count=$(docker exec "$NGINX_CONTAINER" grep "$ip" "$LOG_FILE" | wc -l)
if [ "$request_count" -gt 100 ]; then
@@ -74,8 +122,13 @@ analyze_logs() {
show_stats() {
echo "📈 Статистика безопасности:"
echo "Заблокированных IP: $(wc -l < "$BLOCKED_IPS_FILE")"
echo "Подозрительных доменов: $(wc -l < "$SUSPICIOUS_DOMAINS_FILE")"
echo ""
echo "Последние заблокированные IP:"
tail -5 "$BLOCKED_IPS_FILE" 2>/dev/null || echo "Нет заблокированных IP"
echo ""
echo "Последние подозрительные домены:"
tail -5 "$SUSPICIOUS_DOMAINS_FILE" 2>/dev/null || echo "Нет подозрительных доменов"
}
# Основной цикл

31
start-security-monitor.sh Executable file
View File

@@ -0,0 +1,31 @@
#!/bin/bash
# Простой скрипт для запуска мониторинга безопасности
# Использование: ./start-security-monitor.sh
echo "🔒 Запуск мониторинга безопасности DLE..."
# Проверяем, не запущен ли уже мониторинг
if pgrep -f "security-monitor.sh" > /dev/null; then
echo "⚠️ Мониторинг уже запущен!"
echo "PID: $(pgrep -f 'security-monitor.sh')"
echo ""
echo "Команды управления:"
echo " Остановить: pkill -f 'security-monitor.sh'"
echo " Статус: ps aux | grep security-monitor"
echo " Логи: tail -f /tmp/suspicious_domains.txt"
exit 1
fi
# Запускаем мониторинг в фоне
nohup ./security-monitor.sh > security-monitor.log 2>&1 &
echo "✅ Мониторинг запущен в фоне"
echo "PID: $!"
echo ""
echo "Команды управления:"
echo " Остановить: pkill -f 'security-monitor.sh'"
echo " Статус: ps aux | grep security-monitor"
echo " Логи: tail -f security-monitor.log"
echo " Подозрительные домены: tail -f /tmp/suspicious_domains.txt"
echo " Заблокированные IP: tail -f /tmp/blocked_ips.txt"

View File

@@ -1,48 +0,0 @@
#!/bin/bash
# Скрипт для создания systemd сервиса для security-monitor
# Использование: sudo ./systemd-service.sh
SERVICE_NAME="dle-security-monitor"
SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service"
SCRIPT_PATH="$(pwd)/security-monitor.sh"
USER=$(whoami)
echo "🔧 Создание systemd сервиса для security-monitor..."
# Создаём файл сервиса
sudo tee "$SERVICE_FILE" > /dev/null << EOF
[Unit]
Description=DLE Security Monitor
After=network.target docker.service
Wants=docker.service
[Service]
Type=simple
User=$USER
WorkingDirectory=$(pwd)
ExecStart=$SCRIPT_PATH
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Перезагружаем systemd
sudo systemctl daemon-reload
# Включаем автозапуск
sudo systemctl enable "$SERVICE_NAME"
echo "✅ Сервис создан: $SERVICE_NAME"
echo ""
echo "🎯 Команды управления:"
echo " Запуск: sudo systemctl start $SERVICE_NAME"
echo " Остановка: sudo systemctl stop $SERVICE_NAME"
echo " Статус: sudo systemctl status $SERVICE_NAME"
echo " Логи: sudo journalctl -u $SERVICE_NAME -f"
echo ""
echo "🚀 Сервис будет автоматически запускаться при загрузке системы"