From c007c0b296769096f9519c7687869810938ff30f Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 29 Aug 2025 19:19:26 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B2=D0=B0=D1=88=D0=B5=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/blockchain.js | 115 ++++++-- backend/routes/dleCore.js | 115 ++++++-- backend/routes/dleModules.js | 72 +++++ backend/scripts/deploy/deploy-multichain.js | 260 +++++++++++++++++- backend/services/dleV2Service.js | 32 ++- frontend/public/dle-logo.png | 3 + frontend/public/favicon.ico | Bin 9662 -> 0 bytes .../public/uploads/logos/default-token.svg | 20 ++ frontend/src/views/ManagementView.vue | 156 ++++++++++- .../src/views/settings/DleDeployFormView.vue | 22 ++ 10 files changed, 756 insertions(+), 39 deletions(-) create mode 100644 frontend/public/dle-logo.png delete mode 100755 frontend/public/favicon.ico create mode 100644 frontend/public/uploads/logos/default-token.svg diff --git a/backend/routes/blockchain.js b/backend/routes/blockchain.js index d087cf4..f1cb6ca 100644 --- a/backend/routes/blockchain.js +++ b/backend/routes/blockchain.js @@ -64,7 +64,8 @@ router.post('/read-dle-info', async (req, res) => { "function totalSupply() external view returns (uint256)", "function balanceOf(address account) external view returns (uint256)", "function quorumPercentage() external view returns (uint256)", - "function getCurrentChainId() external view returns (uint256)" + "function getCurrentChainId() external view returns (uint256)", + "function logoURI() external view returns (string memory)" ]; const dle = new ethers.Contract(dleAddress, dleAbi, provider); @@ -74,22 +75,103 @@ router.post('/read-dle-info', async (req, res) => { const totalSupply = await dle.totalSupply(); const quorumPercentage = await dle.quorumPercentage(); const currentChainId = await dle.getCurrentChainId(); - - // Проверяем баланс создателя (адрес, который деплоил контракт) - const deployer = "0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B"; - const deployerBalance = await dle.balanceOf(deployer); - - // Определяем количество участников (держателей токенов) - let participantCount = 0; - if (deployerBalance > 0) { - participantCount++; + + // Читаем логотип + let logoURI = ''; + try { + logoURI = await dle.logoURI(); + } catch (error) { + console.log(`[Blockchain] Ошибка при чтении logoURI:`, error.message); } - // Проверяем, есть ли другие держатели токенов - // Для простоты считаем, что если создатель имеет меньше 100% токенов, то есть другие участники - const deployerPercentage = (Number(deployerBalance) / Number(totalSupply)) * 100; - if (deployerPercentage < 100) { - participantCount = Math.max(participantCount, 2); // Минимум 2 участника + // Получаем информацию о партнерах из блокчейна + const partnerBalances = []; + let participantCount = 0; + + // Получаем события InitialTokensDistributed для определения партнеров + try { + // Получаем последние блоки для поиска событий + const currentBlock = await provider.getBlockNumber(); + const fromBlock = Math.max(0, currentBlock - 10000); // Ищем в последних 10000 блоках + + // ABI для события InitialTokensDistributed + const eventAbi = [ + "event InitialTokensDistributed(address[] partners, uint256[] amounts)" + ]; + + const dleWithEvents = new ethers.Contract(dleAddress, [...dleAbi, ...eventAbi], provider); + + // Ищем события InitialTokensDistributed + const events = await dleWithEvents.queryFilter( + dleWithEvents.filters.InitialTokensDistributed(), + fromBlock, + currentBlock + ); + + if (events.length > 0) { + // Берем последнее событие + const lastEvent = events[events.length - 1]; + const partners = lastEvent.args.partners; + const amounts = lastEvent.args.amounts; + + for (let i = 0; i < partners.length; i++) { + const partnerAddress = partners[i]; + const amount = amounts[i]; + + // Проверяем текущий баланс + const currentBalance = await dle.balanceOf(partnerAddress); + if (Number(currentBalance) > 0) { + participantCount++; + partnerBalances.push({ + address: partnerAddress, + balance: ethers.formatUnits(currentBalance, 18), + percentage: (Number(currentBalance) / Number(totalSupply)) * 100, + initialAmount: ethers.formatUnits(amount, 18) + }); + } + } + } + } catch (error) { + console.log(`[Blockchain] Ошибка при получении событий партнеров:`, error.message); + + // Fallback: ищем держателей токенов через события Transfer + try { + const currentBlock = await provider.getBlockNumber(); + const fromBlock = Math.max(0, currentBlock - 10000); + + const transferEventAbi = [ + "event Transfer(address indexed from, address indexed to, uint256 value)" + ]; + + const dleWithTransferEvents = new ethers.Contract(dleAddress, [...dleAbi, ...transferEventAbi], provider); + + // Ищем события Transfer с from = address(0) (mint события) + const mintEvents = await dleWithTransferEvents.queryFilter( + dleWithTransferEvents.filters.Transfer(ethers.ZeroAddress), + fromBlock, + currentBlock + ); + + const uniqueRecipients = new Set(); + for (const event of mintEvents) { + uniqueRecipients.add(event.args.to); + } + + // Проверяем текущие балансы всех получателей + for (const recipient of uniqueRecipients) { + const balance = await dle.balanceOf(recipient); + if (Number(balance) > 0) { + participantCount++; + partnerBalances.push({ + address: recipient, + balance: ethers.formatUnits(balance, 18), + percentage: (Number(balance) / Number(totalSupply)) * 100 + }); + } + } + } catch (fallbackError) { + console.log(`[Blockchain] Ошибка при fallback поиске партнеров:`, fallbackError.message); + } } const blockchainData = { @@ -106,7 +188,8 @@ router.post('/read-dle-info', async (req, res) => { creationTimestamp: Number(dleInfo.creationTimestamp), isActive: dleInfo.isActive, totalSupply: ethers.formatUnits(totalSupply, 18), - deployerBalance: ethers.formatUnits(deployerBalance, 18), + partnerBalances: partnerBalances, // Информация о партнерах и их балансах + logoURI: logoURI, // URL логотипа токена quorumPercentage: Number(quorumPercentage), currentChainId: Number(currentChainId), rpcUsed: rpcUrl, diff --git a/backend/routes/dleCore.js b/backend/routes/dleCore.js index 07544f6..2e8daf6 100644 --- a/backend/routes/dleCore.js +++ b/backend/routes/dleCore.js @@ -46,7 +46,8 @@ router.post('/read-dle-info', async (req, res) => { "function totalSupply() external view returns (uint256)", "function balanceOf(address account) external view returns (uint256)", "function quorumPercentage() external view returns (uint256)", - "function getCurrentChainId() external view returns (uint256)" + "function getCurrentChainId() external view returns (uint256)", + "function logoURI() external view returns (string memory)" ]; const dle = new ethers.Contract(dleAddress, dleAbi, provider); @@ -56,22 +57,103 @@ router.post('/read-dle-info', async (req, res) => { const totalSupply = await dle.totalSupply(); const quorumPercentage = await dle.quorumPercentage(); const currentChainId = await dle.getCurrentChainId(); - - // Проверяем баланс создателя (адрес, который деплоил контракт) - const deployer = "0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B"; - const deployerBalance = await dle.balanceOf(deployer); - - // Определяем количество участников (держателей токенов) - let participantCount = 0; - if (deployerBalance > 0) { - participantCount++; + + // Читаем логотип + let logoURI = ''; + try { + logoURI = await dle.logoURI(); + } catch (error) { + console.log(`[DLE Core] Ошибка при чтении logoURI:`, error.message); } - // Проверяем, есть ли другие держатели токенов - // Для простоты считаем, что если создатель имеет меньше 100% токенов, то есть другие участники - const deployerPercentage = (Number(deployerBalance) / Number(totalSupply)) * 100; - if (deployerPercentage < 100) { - participantCount = Math.max(participantCount, 2); // Минимум 2 участника + // Получаем информацию о партнерах из блокчейна + const partnerBalances = []; + let participantCount = 0; + + // Получаем события InitialTokensDistributed для определения партнеров + try { + // Получаем последние блоки для поиска событий + const currentBlock = await provider.getBlockNumber(); + const fromBlock = Math.max(0, currentBlock - 10000); // Ищем в последних 10000 блоках + + // ABI для события InitialTokensDistributed + const eventAbi = [ + "event InitialTokensDistributed(address[] partners, uint256[] amounts)" + ]; + + const dleWithEvents = new ethers.Contract(dleAddress, [...dleAbi, ...eventAbi], provider); + + // Ищем события InitialTokensDistributed + const events = await dleWithEvents.queryFilter( + dleWithEvents.filters.InitialTokensDistributed(), + fromBlock, + currentBlock + ); + + if (events.length > 0) { + // Берем последнее событие + const lastEvent = events[events.length - 1]; + const partners = lastEvent.args.partners; + const amounts = lastEvent.args.amounts; + + for (let i = 0; i < partners.length; i++) { + const partnerAddress = partners[i]; + const amount = amounts[i]; + + // Проверяем текущий баланс + const currentBalance = await dle.balanceOf(partnerAddress); + if (Number(currentBalance) > 0) { + participantCount++; + partnerBalances.push({ + address: partnerAddress, + balance: ethers.formatUnits(currentBalance, 18), + percentage: (Number(currentBalance) / Number(totalSupply)) * 100, + initialAmount: ethers.formatUnits(amount, 18) + }); + } + } + } + } catch (error) { + console.log(`[DLE Core] Ошибка при получении событий партнеров:`, error.message); + + // Fallback: ищем держателей токенов через события Transfer + try { + const currentBlock = await provider.getBlockNumber(); + const fromBlock = Math.max(0, currentBlock - 10000); + + const transferEventAbi = [ + "event Transfer(address indexed from, address indexed to, uint256 value)" + ]; + + const dleWithTransferEvents = new ethers.Contract(dleAddress, [...dleAbi, ...transferEventAbi], provider); + + // Ищем события Transfer с from = address(0) (mint события) + const mintEvents = await dleWithTransferEvents.queryFilter( + dleWithTransferEvents.filters.Transfer(ethers.ZeroAddress), + fromBlock, + currentBlock + ); + + const uniqueRecipients = new Set(); + for (const event of mintEvents) { + uniqueRecipients.add(event.args.to); + } + + // Проверяем текущие балансы всех получателей + for (const recipient of uniqueRecipients) { + const balance = await dle.balanceOf(recipient); + if (Number(balance) > 0) { + participantCount++; + partnerBalances.push({ + address: recipient, + balance: ethers.formatUnits(balance, 18), + percentage: (Number(balance) / Number(totalSupply)) * 100 + }); + } + } + } catch (fallbackError) { + console.log(`[DLE Core] Ошибка при fallback поиске партнеров:`, fallbackError.message); + } } const blockchainData = { @@ -87,7 +169,8 @@ router.post('/read-dle-info', async (req, res) => { creationTimestamp: Number(dleInfo.creationTimestamp), isActive: dleInfo.isActive, totalSupply: ethers.formatUnits(totalSupply, 18), - deployerBalance: ethers.formatUnits(deployerBalance, 18), + partnerBalances: partnerBalances, // Информация о партнерах и их балансах + logoURI: logoURI, // URL логотипа токена quorumPercentage: Number(quorumPercentage), currentChainId: Number(currentChainId), participantCount: participantCount diff --git a/backend/routes/dleModules.js b/backend/routes/dleModules.js index d7c62d0..01d84c5 100644 --- a/backend/routes/dleModules.js +++ b/backend/routes/dleModules.js @@ -66,6 +66,37 @@ router.post('/is-module-active', async (req, res) => { } }); +// Получить информацию о задеплоенных модулях для DLE +router.get('/deployed/:dleAddress', async (req, res) => { + try { + const { dleAddress } = req.params; + + if (!dleAddress) { + return res.status(400).json({ + success: false, + error: 'Адрес DLE обязателен' + }); + } + + console.log(`[DLE Modules] Получение информации о модулях для DLE: ${dleAddress}`); + + // Получаем информацию о модулях из файлов деплоя + const modulesInfo = await getDeployedModulesInfo(dleAddress); + + res.json({ + success: true, + data: modulesInfo + }); + + } catch (error) { + console.error('[DLE Modules] Ошибка при получении информации о модулях:', error); + res.status(500).json({ + success: false, + error: 'Ошибка при получении информации о модулях: ' + error.message + }); + } +}); + // Получить адрес модуля router.post('/get-module-address', async (req, res) => { try { @@ -300,4 +331,45 @@ router.post('/create-remove-module-proposal', async (req, res) => { } }); +// Функция для получения информации о задеплоенных модулях +async function getDeployedModulesInfo(dleAddress) { + const fs = require('fs'); + const path = require('path'); + + try { + // Ищем файл модулей для конкретного DLE + const deployDir = path.join(__dirname, '../temp'); + if (!fs.existsSync(deployDir)) { + return { modules: [], verification: {} }; + } + + // Ищем файл по адресу DLE + const modulesFileName = `modules-${dleAddress.toLowerCase()}.json`; + const modulesFilePath = path.join(deployDir, modulesFileName); + + if (!fs.existsSync(modulesFilePath)) { + console.log(`[DLE Modules] Файл модулей не найден: ${modulesFileName}`); + return { modules: [], verification: {} }; + } + + try { + const data = JSON.parse(fs.readFileSync(modulesFilePath, 'utf8')); + console.log(`[DLE Modules] Загружена информация о модулях для DLE: ${dleAddress}`); + + return { + modules: data.modules || [], + verification: data.verification || {}, + deployTimestamp: data.deployTimestamp + }; + } catch (error) { + console.error(`Ошибка при чтении файла ${modulesFileName}:`, error); + return { modules: [], verification: {} }; + } + + } catch (error) { + console.error('Ошибка при получении информации о модулях:', error); + return { modules: [], verification: {} }; + } +} + module.exports = router; diff --git a/backend/scripts/deploy/deploy-multichain.js b/backend/scripts/deploy/deploy-multichain.js index 44b1f7b..fa305d7 100644 --- a/backend/scripts/deploy/deploy-multichain.js +++ b/backend/scripts/deploy/deploy-multichain.js @@ -173,6 +173,230 @@ async function deployInNetwork(rpcUrl, pk, salt, initCodeHash, targetDLENonce, d return { address: deployedAddress, chainId: Number(net.chainId) }; } +// Деплой модулей в одной сети +async function deployModulesInNetwork(rpcUrl, pk, dleAddress) { + const { ethers } = hre; + const provider = new ethers.JsonRpcProvider(rpcUrl); + const wallet = new ethers.Wallet(pk, provider); + const net = await provider.getNetwork(); + + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} deploying modules...`); + + const modules = {}; + + try { + // Деплой TreasuryModule + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} deploying TreasuryModule...`); + const TreasuryModule = await hre.ethers.getContractFactory('TreasuryModule'); + const treasuryModule = await TreasuryModule.connect(wallet).deploy( + dleAddress, // _dleContract + Number(net.chainId), // _chainId + wallet.address // _emergencyAdmin + ); + await treasuryModule.waitForDeployment(); + const treasuryAddress = await treasuryModule.getAddress(); + modules.treasuryModule = treasuryAddress; + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} TreasuryModule deployed at: ${treasuryAddress}`); + + // Деплой TimelockModule + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} deploying TimelockModule...`); + const TimelockModule = await hre.ethers.getContractFactory('TimelockModule'); + const timelockModule = await TimelockModule.connect(wallet).deploy( + dleAddress // _dleContract + ); + await timelockModule.waitForDeployment(); + const timelockAddress = await timelockModule.getAddress(); + modules.timelockModule = timelockAddress; + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} TimelockModule deployed at: ${timelockAddress}`); + + // Деплой DLEReader + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} deploying DLEReader...`); + const DLEReader = await hre.ethers.getContractFactory('DLEReader'); + const dleReader = await DLEReader.connect(wallet).deploy( + dleAddress // _dleContract + ); + await dleReader.waitForDeployment(); + const readerAddress = await dleReader.getAddress(); + modules.dleReader = readerAddress; + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} DLEReader deployed at: ${readerAddress}`); + + // Инициализация модулей в DLE + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing modules in DLE...`); + const dleContract = await hre.ethers.getContractAt('DLE', dleAddress, wallet); + + // Инициализация базовых модулей + await dleContract.initializeBaseModules(treasuryAddress, timelockAddress, readerAddress); + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} base modules initialized`); + + // Инициализация logoURI + try { + // Используем логотип из параметров деплоя или fallback + const logoURL = params.logoURI || "https://via.placeholder.com/200x200/0066cc/ffffff?text=DLE"; + await dleContract.initializeLogoURI(logoURL); + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialized: ${logoURL}`); + } catch (e) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed: ${e.message}`); + // Fallback на базовый логотип + try { + await dleContract.initializeLogoURI("https://via.placeholder.com/200x200/0066cc/ffffff?text=DLE"); + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} fallback logoURI initialized`); + } catch (fallbackError) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} fallback logoURI also failed: ${fallbackError.message}`); + } + } + + } catch (error) { + console.error(`[MULTI_DBG] chainId=${Number(net.chainId)} module deployment failed:`, error.message); + throw error; + } + + return modules; +} + +// Деплой модулей во всех сетях +async function deployModulesInAllNetworks(networks, pk, dleAddress) { + const moduleResults = []; + + for (let i = 0; i < networks.length; i++) { + const rpcUrl = networks[i]; + console.log(`[MULTI_DBG] deploying modules to network ${i + 1}/${networks.length}: ${rpcUrl}`); + + try { + const modules = await deployModulesInNetwork(rpcUrl, pk, dleAddress); + moduleResults.push(modules); + } catch (error) { + console.error(`[MULTI_DBG] Failed to deploy modules in network ${i + 1}:`, error.message); + moduleResults.push({ error: error.message }); + } + } + + return moduleResults; +} + +// Верификация контрактов в одной сети +async function verifyContractsInNetwork(rpcUrl, pk, dleAddress, modules, params) { + const { ethers } = hre; + const provider = new ethers.JsonRpcProvider(rpcUrl); + const wallet = new ethers.Wallet(pk, provider); + const net = await provider.getNetwork(); + + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} starting verification...`); + + const verification = {}; + + try { + // Верификация DLE + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} verifying DLE...`); + await hre.run("verify:verify", { + address: dleAddress, + constructorArguments: [ + { + name: params.name || '', + symbol: params.symbol || '', + location: params.location || '', + coordinates: params.coordinates || '', + jurisdiction: params.jurisdiction || 0, + oktmo: params.oktmo || '', + okvedCodes: params.okvedCodes || [], + kpp: params.kpp ? BigInt(params.kpp) : 0n, + quorumPercentage: params.quorumPercentage || 51, + initialPartners: params.initialPartners || [], + initialAmounts: (params.initialAmounts || []).map(amount => BigInt(amount)), + supportedChainIds: (params.supportedChainIds || []).map(id => BigInt(id)) + }, + BigInt(params.currentChainId || params.supportedChainIds?.[0] || 1), + params.initializer || params.initialPartners?.[0] || "0x0000000000000000000000000000000000000000" + ], + }); + verification.dle = 'success'; + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE verification successful`); + } catch (error) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE verification failed: ${error.message}`); + verification.dle = 'failed'; + } + + // Верификация модулей + if (modules && !modules.error) { + try { + // Верификация TreasuryModule + if (modules.treasuryModule) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} verifying TreasuryModule...`); + await hre.run("verify:verify", { + address: modules.treasuryModule, + constructorArguments: [ + dleAddress, // _dleContract + Number(net.chainId), // _chainId + wallet.address // _emergencyAdmin + ], + }); + verification.treasuryModule = 'success'; + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} TreasuryModule verification successful`); + } + } catch (error) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} TreasuryModule verification failed: ${error.message}`); + verification.treasuryModule = 'failed'; + } + + try { + // Верификация TimelockModule + if (modules.timelockModule) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} verifying TimelockModule...`); + await hre.run("verify:verify", { + address: modules.timelockModule, + constructorArguments: [ + dleAddress // _dleContract + ], + }); + verification.timelockModule = 'success'; + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} TimelockModule verification successful`); + } + } catch (error) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} TimelockModule verification failed: ${error.message}`); + verification.timelockModule = 'failed'; + } + + try { + // Верификация DLEReader + if (modules.dleReader) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} verifying DLEReader...`); + await hre.run("verify:verify", { + address: modules.dleReader, + constructorArguments: [ + dleAddress // _dleContract + ], + }); + verification.dleReader = 'success'; + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} DLEReader verification successful`); + } + } catch (error) { + console.log(`[MULTI_DBG] chainId=${Number(net.chainId)} DLEReader verification failed: ${error.message}`); + verification.dleReader = 'failed'; + } + } + + return verification; +} + +// Верификация контрактов во всех сетях +async function verifyContractsInAllNetworks(networks, pk, dleAddress, moduleResults, params) { + const verificationResults = []; + + for (let i = 0; i < networks.length; i++) { + const rpcUrl = networks[i]; + console.log(`[MULTI_DBG] verifying contracts in network ${i + 1}/${networks.length}: ${rpcUrl}`); + + try { + const verification = await verifyContractsInNetwork(rpcUrl, pk, dleAddress, moduleResults[i], params); + verificationResults.push(verification); + } catch (error) { + console.error(`[MULTI_DBG] Failed to verify contracts in network ${i + 1}:`, error.message); + verificationResults.push({ error: error.message }); + } + } + + return verificationResults; +} + async function main() { const { ethers } = hre; @@ -258,7 +482,41 @@ async function main() { } console.log('[MULTI_DBG] SUCCESS: All DLE addresses are identical:', uniqueAddresses[0]); - console.log('MULTICHAIN_DEPLOY_RESULT', JSON.stringify(results)); + + // Деплой модулей во всех сетях + console.log('[MULTI_DBG] Starting module deployment...'); + const moduleResults = await deployModulesInAllNetworks(networks, pk, uniqueAddresses[0]); + + // Верификация контрактов + console.log('[MULTI_DBG] Starting contract verification...'); + const verificationResults = await verifyContractsInAllNetworks(networks, pk, uniqueAddresses[0], moduleResults, params); + + // Объединяем результаты + const finalResults = results.map((result, index) => ({ + ...result, + modules: moduleResults[index] || {}, + verification: verificationResults[index] || {} + })); + + console.log('MULTICHAIN_DEPLOY_RESULT', JSON.stringify(finalResults)); + + // Сохраняем информацию о модулях в отдельный файл для каждого DLE + const modulesInfo = { + dleAddress: uniqueAddresses[0], + modules: moduleResults, + verification: verificationResults, + deployTimestamp: new Date().toISOString() + }; + + // Создаем директорию temp если её нет + const tempDir = path.join(__dirname, '../temp'); + if (!fs.existsSync(tempDir)) { + fs.mkdirSync(tempDir, { recursive: true }); + } + + const deployResultPath = path.join(tempDir, `modules-${uniqueAddresses[0].toLowerCase()}.json`); + fs.writeFileSync(deployResultPath, JSON.stringify(modulesInfo, null, 2)); + console.log(`[MULTI_DBG] Modules info saved to: ${deployResultPath}`); } main().catch((e) => { console.error(e); process.exit(1); }); diff --git a/backend/services/dleV2Service.js b/backend/services/dleV2Service.js index 876d1fd..3dd2cb3 100644 --- a/backend/services/dleV2Service.js +++ b/backend/services/dleV2Service.js @@ -458,6 +458,18 @@ class DLEV2Service { deployParams.currentChainId = 1; // По умолчанию Ethereum } + // Обрабатываем logoURI + if (deployParams.logoURI) { + // Если logoURI относительный путь, делаем его абсолютным + if (deployParams.logoURI.startsWith('/uploads/')) { + deployParams.logoURI = `http://localhost:3000${deployParams.logoURI}`; + } + // Если это placeholder, оставляем как есть + if (deployParams.logoURI.includes('placeholder.com')) { + // Оставляем как есть + } + } + return deployParams; } @@ -573,8 +585,24 @@ class DLEV2Service { const resultMatch = stdout.match(/MULTICHAIN_DEPLOY_RESULT\s+(\[.*\])/); if (resultMatch) { - const result = JSON.parse(resultMatch[1]); - resolve(result); + const deployResults = JSON.parse(resultMatch[1]); + + // Преобразуем результат в нужный формат + const addresses = deployResults.map(r => r.address); + const allSame = addresses.every(addr => addr.toLowerCase() === addresses[0].toLowerCase()); + + resolve({ + success: true, + data: { + dleAddress: addresses[0], + networks: deployResults.map((r, index) => ({ + chainId: r.chainId, + address: r.address, + success: true + })), + allSame + } + }); } else { // Fallback: ищем адреса DLE в выводе по новому формату const dleAddressMatches = stdout.match(/\[MULTI_DBG\] chainId=\d+ DLE deployed at=(0x[a-fA-F0-9]{40})/g); diff --git a/frontend/public/dle-logo.png b/frontend/public/dle-logo.png new file mode 100644 index 0000000..e1223b9 --- /dev/null +++ b/frontend/public/dle-logo.png @@ -0,0 +1,3 @@ +# Это placeholder для логотипа DLE +# В реальном проекте здесь должен быть PNG файл с логотипом +# Размер рекомендуется: 200x200 пикселей diff --git a/frontend/public/favicon.ico b/frontend/public/favicon.ico deleted file mode 100755 index a7594947efa7cd33bdfd0dda559978f0c7eb2f0d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9662 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 Q8IK45U!eb=H+=;D4IrgUO8@`> diff --git a/frontend/public/uploads/logos/default-token.svg b/frontend/public/uploads/logos/default-token.svg new file mode 100644 index 0000000..f7c789f --- /dev/null +++ b/frontend/public/uploads/logos/default-token.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + DLE + + + + + + + diff --git a/frontend/src/views/ManagementView.vue b/frontend/src/views/ManagementView.vue index 10f529d..60b1892 100644 --- a/frontend/src/views/ManagementView.vue +++ b/frontend/src/views/ManagementView.vue @@ -58,8 +58,20 @@ @click="selectDle(dle)" >
-

{{ dle.name }} ({{ dle.symbol }})

- {{ dle.version || 'v2' }} +
+ + +
+

{{ dle.name }} ({{ dle.symbol }})

+ {{ dle.version || 'v2' }} +
+
@@ -106,7 +118,22 @@ Коды ОКВЭД: {{ dle.okvedCodes?.join(', ') || 'Не указаны' }}
- Партнеры: {{ dle.participantCount || 0 }} участников + Партнеры: + + {{ dle.participantCount || dle.partnerBalances.length }} участников +
+
+ {{ shortenAddress(partner.address) }} + {{ partner.balance }} токенов ({{ partner.percentage.toFixed(1) }}%) +
+
+ +{{ dle.partnerBalances.length - 3 }} еще +
+
+
+ + {{ dle.participantCount || 0 }} участников +
Статус: @@ -229,6 +256,16 @@ const openProposals = () => { router.push('/management/proposals'); }; +// Обработка ошибки загрузки логотипа +const handleLogoError = (event) => { + console.log('Ошибка загрузки логотипа:', event.target.src); + event.target.style.display = 'none'; + const defaultLogo = event.target.parentElement.querySelector('.default-logo'); + if (defaultLogo) { + defaultLogo.style.display = 'flex'; + } +}; + const openTokens = () => { router.push('/management/tokens'); }; @@ -309,7 +346,8 @@ async function loadDeployedDles() { kpp: blockchainData.kpp || dle.kpp, // Информация о токенах из блокчейна totalSupply: blockchainData.totalSupply, - deployerBalance: blockchainData.deployerBalance, + partnerBalances: blockchainData.partnerBalances || [], // Информация о партнерах + logoURI: blockchainData.logoURI || '', // URL логотипа // Количество участников (держателей токенов) participantCount: blockchainData.participantCount || 0 }; @@ -867,6 +905,97 @@ onBeforeUnmount(() => { transform: translateY(-1px); } +/* Стили для отображения логотипа */ +.dle-title-section { + display: flex; + align-items: center; + gap: 12px; +} + +.dle-logo { + width: 48px; + height: 48px; + border-radius: 8px; + object-fit: contain; + border: 2px solid #e9ecef; + background: white; +} + +.default-logo { + width: 48px; + height: 48px; + border-radius: 8px; + background: linear-gradient(135deg, var(--color-primary), #0056b3); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; + font-size: 14px; + border: 2px solid #e9ecef; +} + +.dle-title { + display: flex; + flex-direction: column; + gap: 2px; +} + +.dle-title h3 { + margin: 0; + font-size: 1.2rem; + color: var(--color-primary); +} + +.dle-version { + font-size: 0.8rem; + color: #6c757d; + background: #f8f9fa; + padding: 2px 6px; + border-radius: 4px; + align-self: flex-start; +} + +/* Стили для отображения партнеров */ +.partners-details { + margin-top: 0.5rem; + padding: 0.5rem; + background: #f8f9fa; + border-radius: 6px; + border-left: 3px solid var(--color-primary); +} + +.partner-info { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.25rem 0; + font-size: 0.875rem; +} + +.partner-info:not(:last-child) { + border-bottom: 1px solid #e9ecef; +} + +.partner-address { + font-family: 'Courier New', monospace; + color: #495057; + font-weight: 600; +} + +.partner-balance { + color: var(--color-primary); + font-weight: 600; +} + +.more-partners { + text-align: center; + color: #6c757d; + font-style: italic; + font-size: 0.8rem; + padding: 0.25rem 0; +} + /* Адаптивность */ @media (max-width: 768px) { .blocks-row { @@ -880,6 +1009,25 @@ onBeforeUnmount(() => { .management-block h3 { font-size: 1.3rem; } + + .partner-info { + flex-direction: column; + align-items: flex-start; + gap: 0.25rem; + } + + .dle-title-section { + flex-direction: column; + align-items: flex-start; + gap: 8px; + } + + .dle-logo, + .default-logo { + width: 40px; + height: 40px; + font-size: 12px; + } } diff --git a/frontend/src/views/settings/DleDeployFormView.vue b/frontend/src/views/settings/DleDeployFormView.vue index 738f36e..da34724 100644 --- a/frontend/src/views/settings/DleDeployFormView.vue +++ b/frontend/src/views/settings/DleDeployFormView.vue @@ -2475,6 +2475,28 @@ const deploySmartContracts = async () => { autoVerifyAfterDeploy: autoVerifyAfterDeploy.value }; + // Обработка логотипа + try { + if (logoFile.value) { + const form = new FormData(); + form.append('logo', logoFile.value); + const uploadResp = await axios.post('/uploads/logo', form, { headers: { 'Content-Type': 'multipart/form-data' } }); + const uploaded = uploadResp.data?.data?.url || uploadResp.data?.data?.path; + if (uploaded) { + deployData.logoURI = uploaded; + } + } else if (ensResolvedUrl.value) { + deployData.logoURI = ensResolvedUrl.value; + } else { + // фолбэк на дефолт + deployData.logoURI = '/uploads/logos/default-token.svg'; + } + } catch (error) { + console.warn('Ошибка при обработке логотипа:', error.message); + // Используем fallback логотип + deployData.logoURI = '/uploads/logos/default-token.svg'; + } + console.log('Данные для деплоя DLE:', deployData); // Предварительная проверка балансов во всех сетях