/** * 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 { ethers } = require('ethers'); const rpcProviderService = require('../services/rpcProviderService'); const DeployParamsService = require('../services/deployParamsService'); /** * Получить информацию о мультиконтрактном предложении * @route POST /api/dle-multichain/get-proposal-multichain-info */ router.post('/get-proposal-multichain-info', async (req, res) => { try { const { dleAddress, proposalId, governanceChainId } = req.body; if (!dleAddress || proposalId === undefined || !governanceChainId) { return res.status(400).json({ success: false, error: 'Адрес DLE, ID предложения и ID сети голосования обязательны' }); } console.log(`[DLE Multichain] Получение информации о предложении ${proposalId} для DLE: ${dleAddress}`); // Получаем RPC URL для сети голосования const rpcUrl = await rpcProviderService.getRpcUrlByChainId(governanceChainId); if (!rpcUrl) { return res.status(500).json({ success: false, error: `RPC URL для сети ${governanceChainId} не найден` }); } const provider = new ethers.JsonRpcProvider(rpcUrl); const dleAbi = [ "function proposals(uint256) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, bytes memory operation, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targetChains)", "function getProposalState(uint256 _proposalId) external view returns (uint8 state)", "function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)", "function getSupportedChainCount() external view returns (uint256)", "function getSupportedChainId(uint256 _index) external view returns (uint256)" ]; const dle = new ethers.Contract(dleAddress, dleAbi, provider); // Получаем данные предложения const proposal = await dle.proposals(proposalId); const state = await dle.getProposalState(proposalId); const result = await dle.checkProposalResult(proposalId); // Получаем поддерживаемые сети const chainCount = await dle.getSupportedChainCount(); const supportedChains = []; for (let i = 0; i < chainCount; i++) { const chainId = await dle.getSupportedChainId(i); supportedChains.push(Number(chainId)); } const proposalInfo = { id: Number(proposal.id), description: proposal.description, forVotes: Number(proposal.forVotes), againstVotes: Number(proposal.againstVotes), executed: proposal.executed, canceled: proposal.canceled, deadline: Number(proposal.deadline), initiator: proposal.initiator, operation: proposal.operation, governanceChainId: Number(proposal.governanceChainId), targetChains: proposal.targetChains.map(chain => Number(chain)), snapshotTimepoint: Number(proposal.snapshotTimepoint), state: Number(state), isPassed: result.passed, quorumReached: result.quorumReached, supportedChains: supportedChains, canExecuteInTargetChains: result.passed && result.quorumReached && !proposal.executed && !proposal.canceled }; console.log(`[DLE Multichain] Информация о предложении получена:`, proposalInfo); res.json({ success: true, data: proposalInfo }); } catch (error) { console.error('[DLE Multichain] Ошибка при получении информации о предложении:', error); res.status(500).json({ success: false, error: 'Ошибка при получении информации о предложении: ' + error.message }); } }); /** * Исполнить предложение во всех целевых сетях * @route POST /api/dle-multichain/execute-in-all-target-chains */ router.post('/execute-in-all-target-chains', async (req, res) => { try { const { dleAddress, proposalId, deploymentId, userAddress } = req.body; if (!dleAddress || proposalId === undefined || !deploymentId || !userAddress) { return res.status(400).json({ success: false, error: 'Все поля обязательны' }); } console.log(`[DLE Multichain] Исполнение предложения ${proposalId} во всех целевых сетях для DLE: ${dleAddress}`); // Получаем параметры деплоя const deployParamsService = new DeployParamsService(); const deployParams = await deployParamsService.getDeployParams(deploymentId); if (!deployParams || !deployParams.privateKey) { return res.status(400).json({ success: false, error: 'Приватный ключ не найден в параметрах деплоя' }); } // Получаем информацию о предложении const proposalInfoResponse = await fetch(`${req.protocol}://${req.get('host')}/api/dle-multichain/get-proposal-multichain-info`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ dleAddress, proposalId, governanceChainId: deployParams.currentChainId }) }); const proposalInfo = await proposalInfoResponse.json(); if (!proposalInfo.success) { return res.status(400).json({ success: false, error: 'Не удалось получить информацию о предложении' }); } const { targetChains, canExecuteInTargetChains } = proposalInfo.data; if (!canExecuteInTargetChains) { return res.status(400).json({ success: false, error: 'Предложение не готово к исполнению в целевых сетях' }); } if (targetChains.length === 0) { return res.status(400).json({ success: false, error: 'У предложения нет целевых сетей для исполнения' }); } // Исполняем в каждой целевой сети const executionResults = []; for (const targetChainId of targetChains) { try { console.log(`[DLE Multichain] Исполнение в сети ${targetChainId}`); const result = await executeProposalInChain( dleAddress, proposalId, targetChainId, deployParams.privateKey, userAddress ); executionResults.push({ chainId: targetChainId, success: true, transactionHash: result.transactionHash }); } catch (error) { console.error(`[DLE Multichain] Ошибка исполнения в сети ${targetChainId}:`, error.message); executionResults.push({ chainId: targetChainId, success: false, error: error.message }); } } const successCount = executionResults.filter(r => r.success).length; const totalCount = executionResults.length; console.log(`[DLE Multichain] Исполнение завершено: ${successCount}/${totalCount} успешно`); res.json({ success: true, data: { proposalId, targetChains, executionResults, summary: { total: totalCount, successful: successCount, failed: totalCount - successCount } } }); } catch (error) { console.error('[DLE Multichain] Ошибка при исполнении во всех целевых сетях:', error); res.status(500).json({ success: false, error: 'Ошибка при исполнении во всех целевых сетях: ' + error.message }); } }); /** * Исполнить предложение в конкретной целевой сети * @route POST /api/dle-multichain/execute-in-target-chain */ router.post('/execute-in-target-chain', async (req, res) => { try { const { dleAddress, proposalId, targetChainId, deploymentId, userAddress } = req.body; if (!dleAddress || proposalId === undefined || !targetChainId || !deploymentId || !userAddress) { return res.status(400).json({ success: false, error: 'Все поля обязательны' }); } console.log(`[DLE Multichain] Исполнение предложения ${proposalId} в сети ${targetChainId} для DLE: ${dleAddress}`); // Получаем параметры деплоя const deployParamsService = new DeployParamsService(); const deployParams = await deployParamsService.getDeployParams(deploymentId); if (!deployParams || !deployParams.privateKey) { return res.status(400).json({ success: false, error: 'Приватный ключ не найден в параметрах деплоя' }); } // Исполняем в целевой сети const result = await executeProposalInChain( dleAddress, proposalId, targetChainId, deployParams.privateKey, userAddress ); res.json({ success: true, data: { proposalId, targetChainId, transactionHash: result.transactionHash, blockNumber: result.blockNumber } }); } catch (error) { console.error('[DLE Multichain] Ошибка при исполнении в целевой сети:', error); res.status(500).json({ success: false, error: 'Ошибка при исполнении в целевой сети: ' + error.message }); } }); /** * Вспомогательная функция для исполнения предложения в конкретной сети */ async function executeProposalInChain(dleAddress, proposalId, chainId, privateKey, userAddress) { // Получаем RPC URL для целевой сети const rpcUrl = await rpcProviderService.getRpcUrlByChainId(chainId); if (!rpcUrl) { throw new Error(`RPC URL для сети ${chainId} не найден`); } const provider = new ethers.JsonRpcProvider(rpcUrl); const wallet = new ethers.Wallet(privateKey, provider); const dleAbi = [ "function executeProposalBySignatures(uint256 _proposalId, address[] calldata signers, bytes[] calldata signatures) external" ]; const dle = new ethers.Contract(dleAddress, dleAbi, wallet); // Для простоты используем подпись от одного адреса (кошелька с приватным ключом) // В реальности нужно собрать подписи от держателей токенов const signers = [wallet.address]; const signatures = []; // TODO: Реализовать сбор подписей // Временная заглушка - используем прямое исполнение если это возможно // В реальности нужно реализовать сбор подписей от держателей токенов try { // Пытаемся исполнить напрямую (если это сеть голосования) const directExecuteAbi = [ "function executeProposal(uint256 _proposalId) external" ]; const directDle = new ethers.Contract(dleAddress, directExecuteAbi, wallet); const tx = await directDle.executeProposal(proposalId); const receipt = await tx.wait(); return { transactionHash: receipt.hash, blockNumber: receipt.blockNumber }; } catch (directError) { // Если прямое исполнение невозможно, используем подписи if (signatures.length === 0) { throw new Error('Необходимо собрать подписи от держателей токенов для исполнения в целевой сети'); } const tx = await dle.executeProposalBySignatures(proposalId, signers, signatures); const receipt = await tx.wait(); return { transactionHash: receipt.hash, blockNumber: receipt.blockNumber }; } } module.exports = router;