🔧 Исправление отображения данных DLE из блокчейна

 Основные изменения:
- Исправлен дублирование /api в URL запросов к бэкенду
- Добавлен новый API endpoint /api/blockchain/read-dle-info для чтения данных из блокчейна
- Исправлено отображение количества участников (participantCount вместо initialPartners.length)
- Обновлен ManagementView.vue для чтения данных из блокчейна вместо JSON файлов
- Добавлены утилиты для чтения данных DLE из блокчейна
- Исправлены координаты в форме деплоя (сохранение в localStorage)
- Добавлен индикатор прогресса деплоя с редиректом на /management

🔧 Технические детали:
- Создан backend/routes/blockchain.js с endpoint для чтения DLE данных
- Обновлен backend/app.js для регистрации нового маршрута
- Исправлен импорт axios в ManagementView.vue (используется настроенный экземпляр api)
- Добавлены скрипты utils/read-dle-info.js и utils/get-rpc-url.js
- Обновлен скрипт деплоя для сохранения всех данных в блокчейн

🎯 Результат:
- Данные DLE теперь читаются напрямую из блокчейна
- Правильное отображение координат и количества участников
- Устранены ошибки 404 при запросах к API
This commit is contained in:
2025-08-04 21:04:16 +03:00
parent df37507bbe
commit e2ebe7e8aa
14 changed files with 1760 additions and 779 deletions

View File

@@ -87,6 +87,7 @@ const russianClassifiersRoutes = require('./routes/russian-classifiers'); // Д
const ollamaRoutes = require('./routes/ollama'); // Добавляем импорт Ollama маршрутов
const aiQueueRoutes = require('./routes/ai-queue'); // Добавляем импорт AI Queue маршрутов
const tagsRoutes = require('./routes/tags'); // Добавляем импорт маршрутов тегов
const blockchainRoutes = require('./routes/blockchain'); // Добавляем импорт blockchain маршрутов
const app = express();
@@ -212,6 +213,7 @@ app.use('/api/russian-classifiers', russianClassifiersRoutes); // Добавля
app.use('/api/ollama', ollamaRoutes); // Добавляем маршрут Ollama
app.use('/api/ai-queue', aiQueueRoutes); // Добавляем маршрут AI Queue
app.use('/api/tags', tagsRoutes); // Добавляем маршрут тегов
app.use('/api/blockchain', blockchainRoutes); // Добавляем маршрут blockchain
app.use('/api/messages', messagesRoutes);
app.use('/api/identities', identitiesRoutes);
app.use('/api/rag', ragRoutes); // Подключаем роут

View File

@@ -0,0 +1,111 @@
/**
* 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');
// Чтение данных DLE из блокчейна
router.post('/read-dle-info', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[Blockchain] Чтение данных DLE из блокчейна: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// ABI для чтения данных DLE
const dleAbi = [
"function getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))",
"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)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Читаем данные DLE
const dleInfo = await dle.getDLEInfo();
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++;
}
// Проверяем, есть ли другие держатели токенов
// Для простоты считаем, что если создатель имеет меньше 100% токенов, то есть другие участники
const deployerPercentage = (Number(deployerBalance) / Number(totalSupply)) * 100;
if (deployerPercentage < 100) {
participantCount = Math.max(participantCount, 2); // Минимум 2 участника
}
const blockchainData = {
name: dleInfo.name,
symbol: dleInfo.symbol,
location: dleInfo.location,
coordinates: dleInfo.coordinates,
jurisdiction: Number(dleInfo.jurisdiction),
oktmo: Number(dleInfo.oktmo),
okvedCodes: dleInfo.okvedCodes,
kpp: Number(dleInfo.kpp),
creationTimestamp: Number(dleInfo.creationTimestamp),
isActive: dleInfo.isActive,
totalSupply: ethers.formatUnits(totalSupply, 18),
deployerBalance: ethers.formatUnits(deployerBalance, 18),
quorumPercentage: Number(quorumPercentage),
currentChainId: Number(currentChainId),
participantCount: participantCount
};
console.log(`[Blockchain] Данные DLE прочитаны из блокчейна:`, blockchainData);
res.json({
success: true,
data: blockchainData
});
} catch (error) {
console.error('[Blockchain] Ошибка при чтении данных DLE из блокчейна:', error);
res.status(500).json({
success: false,
error: 'Ошибка при чтении данных из блокчейна: ' + error.message
});
}
});
module.exports = router;

View File

@@ -188,12 +188,50 @@ router.delete('/:dleAddress', auth.requireAuth, auth.requireAdmin, async (req, r
}
});
/**
* @route GET /api/dle-v2/check-admin-tokens
* @desc Проверить баланс админских токенов для адреса
* @access Public
*/
router.get('/check-admin-tokens', async (req, res, next) => {
try {
const { address } = req.query;
if (!address) {
return res.status(400).json({
success: false,
message: 'Адрес кошелька не передан'
});
}
// Проверяем баланс токенов
const { checkAdminRole } = require('../services/admin-role');
const isAdmin = await checkAdminRole(address);
res.json({
success: true,
data: {
isAdmin: isAdmin,
address: address,
message: isAdmin ? 'Админские токены найдены' : 'Админские токены не найдены'
}
});
} catch (error) {
logger.error('Ошибка при проверке админских токенов:', error);
res.status(500).json({
success: false,
message: error.message || 'Произошла ошибка при проверке админских токенов'
});
}
});
/**
* @route POST /api/dle-v2/validate-private-key
* @desc Валидировать приватный ключ и получить адрес кошелька
* @access Private
* @access Public
*/
router.post('/validate-private-key', auth.requireAuth, async (req, res, next) => {
router.post('/validate-private-key', async (req, res, next) => {
try {
const { privateKey } = req.body;
@@ -207,6 +245,8 @@ router.post('/validate-private-key', auth.requireAuth, async (req, res, next) =>
// Логируем входящий ключ (только для отладки)
logger.info('Получен приватный ключ для валидации:', privateKey);
logger.info('Длина входящего ключа:', privateKey.length);
logger.info('Тип входящего ключа:', typeof privateKey);
logger.info('Полный объект запроса:', JSON.stringify(req.body));
try {
// Очищаем ключ от префикса 0x если есть

View File

@@ -20,36 +20,72 @@ async function main() {
const deployParams = getDeployParams();
console.log("Начинаем создание современного DLE v2...");
console.log("Параметры DLE:");
console.log(JSON.stringify(deployParams, null, 2));
console.log("Параметры DLE:");
console.log(JSON.stringify(deployParams, null, 2));
// Преобразуем initialAmounts в wei
const initialAmountsInWei = deployParams.initialAmounts.map(amount => ethers.parseUnits(amount.toString(), 18));
console.log("Initial amounts в wei:");
console.log(initialAmountsInWei.map(wei => ethers.formatUnits(wei, 18) + " токенов"));
// Получаем аккаунт деплоя
const [deployer] = await ethers.getSigners();
// Получаем RPC URL и приватный ключ из переменных окружения
const rpcUrl = process.env.RPC_URL;
const privateKey = process.env.PRIVATE_KEY;
if (!rpcUrl || !privateKey) {
throw new Error('RPC_URL и PRIVATE_KEY должны быть установлены в переменных окружения');
}
// Создаем провайдер и кошелек
const provider = new ethers.JsonRpcProvider(rpcUrl);
const deployer = new ethers.Wallet(privateKey, provider);
console.log(`Адрес деплоера: ${deployer.address}`);
console.log(`Баланс деплоера: ${ethers.formatEther(await deployer.provider.getBalance(deployer.address))} ETH`);
const balance = await provider.getBalance(deployer.address);
console.log(`Баланс деплоера: ${ethers.formatEther(balance)} ETH`);
// Проверяем, достаточно ли баланса для деплоя (минимум 0.00001 ETH для тестирования)
const minBalance = ethers.parseEther("0.00001");
if (balance < minBalance) {
throw new Error(`Недостаточно ETH для деплоя. Баланс: ${ethers.formatEther(balance)} ETH, требуется минимум: ${ethers.formatEther(minBalance)} ETH. Пополните кошелек через Sepolia faucet.`);
}
try {
// 1. Создаем единый контракт DLE
console.log("\n1. Деплой единого контракта DLE v2...");
const DLE = await ethers.getContractFactory("DLE");
const DLE = await ethers.getContractFactory("DLE", deployer);
// Создаем структуру DLEConfig
// Создаем структуру DLEConfig с полными данными
const dleConfig = {
name: deployParams.name,
symbol: deployParams.symbol,
location: deployParams.location,
coordinates: deployParams.coordinates || "0,0",
jurisdiction: deployParams.jurisdiction || 1,
oktmo: deployParams.oktmo || 45000000000,
oktmo: parseInt(deployParams.oktmo) || 45000000000,
okvedCodes: deployParams.okvedCodes || [],
kpp: deployParams.kpp || 770101001,
kpp: parseInt(deployParams.kpp) || 770101001,
quorumPercentage: deployParams.quorumPercentage || 51,
initialPartners: deployParams.initialPartners,
initialAmounts: deployParams.initialAmounts,
initialAmounts: deployParams.initialAmounts.map(amount => ethers.parseUnits(amount.toString(), 18)),
supportedChainIds: deployParams.supportedChainIds || [1, 137, 56, 42161] // Ethereum, Polygon, BSC, Arbitrum
};
console.log("Конфигурация DLE для записи в блокчейн:");
console.log("Название:", dleConfig.name);
console.log("Символ:", dleConfig.symbol);
console.log("Местонахождение:", dleConfig.location);
console.log("Координаты:", dleConfig.coordinates);
console.log("Юрисдикция:", dleConfig.jurisdiction);
console.log("ОКТМО:", dleConfig.oktmo);
console.log("Коды ОКВЭД:", dleConfig.okvedCodes.join(', '));
console.log("КПП:", dleConfig.kpp);
console.log("Кворум:", dleConfig.quorumPercentage + "%");
console.log("Партнеры:", dleConfig.initialPartners.join(', '));
console.log("Количества токенов:", dleConfig.initialAmounts.map(amount => ethers.formatUnits(amount, 18) + " токенов").join(', '));
console.log("Поддерживаемые сети:", dleConfig.supportedChainIds.join(', '));
const currentChainId = deployParams.currentChainId || 1; // По умолчанию Ethereum
const dle = await DLE.deploy(dleConfig, currentChainId);
@@ -58,17 +94,33 @@ async function main() {
const dleAddress = await dle.getAddress();
console.log(`DLE v2 задеплоен по адресу: ${dleAddress}`);
// 2. Получаем информацию о DLE
// 2. Получаем информацию о DLE из блокчейна
const dleInfo = await dle.getDLEInfo();
console.log("\n2. Информация о DLE:");
console.log("\n2. Информация о DLE из блокчейна:");
console.log(`Название: ${dleInfo.name}`);
console.log(`Символ: ${dleInfo.symbol}`);
console.log(`Местонахождение: ${dleInfo.location}`);
console.log(`Коды деятельности: ${dleInfo.okvedCodes.join(', ')}`);
console.log(`Дата создания: ${new Date(dleInfo.creationTimestamp * 1000).toISOString()}`);
console.log(`Координаты: ${dleInfo.coordinates}`);
console.log(`Юрисдикция: ${dleInfo.jurisdiction}`);
console.log(`ОКТМО: ${dleInfo.oktmo}`);
console.log(`Коды ОКВЭД: ${dleInfo.okvedCodes.join(', ')}`);
console.log(`КПП: ${dleInfo.kpp}`);
console.log(`Дата создания: ${new Date(Number(dleInfo.creationTimestamp) * 1000).toISOString()}`);
console.log(`Активен: ${dleInfo.isActive}`);
// Проверяем, что данные записались правильно
console.log("\n3. Проверка записи данных в блокчейн:");
if (dleInfo.name === deployParams.name &&
dleInfo.location === deployParams.location &&
dleInfo.jurisdiction === deployParams.jurisdiction) {
console.log("✅ Все данные DLE успешно записаны в блокчейн!");
console.log("Теперь эти данные видны на Etherscan в разделе 'Contract' -> 'Read Contract'");
} else {
console.log("❌ Ошибка: данные не записались правильно в блокчейн");
}
// 3. Сохраняем информацию о созданном DLE
console.log("\n3. Сохранение информации о DLE v2...");
// 4. Сохраняем информацию о созданном DLE
console.log("\n4. Сохранение информации о DLE v2...");
const dleData = {
name: deployParams.name,
symbol: deployParams.symbol,
@@ -76,13 +128,16 @@ async function main() {
coordinates: deployParams.coordinates || "0,0",
jurisdiction: deployParams.jurisdiction || 1,
oktmo: deployParams.oktmo || 45000000000,
okvedCodes: deployParams.isicCodes || [],
okvedCodes: deployParams.okvedCodes || [],
kpp: deployParams.kpp || 770101001,
dleAddress: dleAddress,
creationBlock: (await dle.provider.getBlockNumber()),
creationTimestamp: (await dle.provider.getBlock()).timestamp,
creationBlock: Number(await provider.getBlockNumber()),
creationTimestamp: Number((await provider.getBlock()).timestamp),
deployedManually: true,
version: "v2",
// Сохраняем информацию о партнерах
initialPartners: deployParams.initialPartners || [],
initialAmounts: deployParams.initialAmounts || [],
governanceSettings: {
quorumPercentage: deployParams.quorumPercentage || 51,
supportedChainIds: deployParams.supportedChainIds || [1, 137, 56, 42161],

View File

@@ -0,0 +1,56 @@
/**
* 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 rpcProviderService = require('../../services/rpcProviderService');
async function main() {
try {
console.log('🔍 Получение RPC URL из базы данных...\n');
// Получаем все RPC провайдеры
const providers = await rpcProviderService.getAllRpcProviders();
console.log('📋 Все RPC провайдеры:');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
for (const provider of providers) {
console.log(`🌐 Сеть: ${provider.network_id}`);
console.log(`🔗 Chain ID: ${provider.chain_id}`);
console.log(`📡 RPC URL: ${provider.rpc_url}`);
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
}
// Получаем конкретный RPC URL для Sepolia
console.log('\n🎯 RPC URL для Sepolia (chain_id: 11155111):');
const sepoliaRpc = await rpcProviderService.getRpcUrlByChainId(11155111);
if (sepoliaRpc) {
console.log(`✅ Найден: ${sepoliaRpc}`);
} else {
console.log('❌ RPC URL для Sepolia не найден');
}
} catch (error) {
console.error('❌ Ошибка при получении RPC URL:', error);
throw error;
}
}
main()
.then(() => {
console.log('\n✅ Скрипт завершен успешно');
process.exit(0);
})
.catch((error) => {
console.error('\n❌ Скрипт завершен с ошибкой:', error);
process.exit(1);
});

View File

@@ -0,0 +1,107 @@
/**
* 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");
async function main() {
// Адрес DLE контракта (замените на ваш адрес)
const dleAddress = process.env.DLE_ADDRESS || "0x219f9665e713476B0B080bd73b8465B39dAB1E41";
console.log(`Читаем данные DLE из блокчейна по адресу: ${dleAddress}`);
// Получаем RPC URL из переменных окружения или используем дефолтный для Sepolia
const rpcUrl = process.env.RPC_URL || 'https://eth-sepolia.nodereal.io/v1/56dec8028bae4f26b76099a42dae2b52';
// Создаем провайдер
const provider = new ethers.JsonRpcProvider(rpcUrl);
try {
// Получаем ABI контракта DLE
const DLE = await ethers.getContractFactory("DLE");
const dle = DLE.attach(dleAddress).connect(provider);
// Читаем данные DLE из блокчейна
console.log("\n📋 Чтение данных DLE из блокчейна...");
const dleInfo = await dle.getDLEInfo();
console.log("\n✅ Данные DLE из блокчейна:");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log(`🏢 Название: ${dleInfo.name}`);
console.log(`💎 Символ: ${dleInfo.symbol}`);
console.log(`📍 Местонахождение: ${dleInfo.location}`);
console.log(`🌍 Координаты: ${dleInfo.coordinates}`);
console.log(`🏛️ Юрисдикция: ${dleInfo.jurisdiction}`);
console.log(`📊 ОКТМО: ${dleInfo.oktmo}`);
console.log(`🏭 Коды ОКВЭД: ${dleInfo.okvedCodes.join(', ')}`);
console.log(`🏢 КПП: ${dleInfo.kpp}`);
console.log(`📅 Дата создания: ${new Date(Number(dleInfo.creationTimestamp) * 1000).toISOString()}`);
console.log(`✅ Активен: ${dleInfo.isActive}`);
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
// Дополнительная информация
console.log("\n📊 Дополнительная информация:");
const quorumPercentage = await dle.quorumPercentage();
const currentChainId = await dle.getCurrentChainId();
const totalSupply = await dle.totalSupply();
console.log(`🗳️ Кворум: ${quorumPercentage}%`);
console.log(`🔗 Текущая сеть: ${currentChainId}`);
console.log(`💰 Общий запас токенов: ${ethers.formatUnits(totalSupply, 18)}`);
// Проверяем информацию о токенах
console.log("\n👥 Информация о токенах:");
try {
const totalSupply = await dle.totalSupply();
console.log(`💰 Общий запас: ${ethers.formatUnits(totalSupply, 18)} токенов`);
// Проверим баланс создателя контракта (адрес, который деплоил контракт)
const deployer = "0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B"; // Адрес из логов
const deployerBalance = await dle.balanceOf(deployer);
if (deployerBalance > 0) {
console.log(`👤 Создатель (${deployer}): ${ethers.formatUnits(deployerBalance, 18)} токенов`);
} else {
console.log(`👤 Создатель (${deployer}): 0 токенов`);
}
// Проверим, есть ли другие держатели токенов
console.log("\n🔍 Проверка распределения токенов:");
console.log("💡 Партнеры не сохраняются в блокчейне как отдельные данные.");
console.log("💡 Они используются только для первоначального распределения токенов.");
console.log("💡 Информация о партнерах хранится в локальных файлах JSON.");
} catch (error) {
console.log("⚠️ Не удалось прочитать информацию о токенах:", error.message);
}
console.log("\n🌐 Ссылка на Etherscan:");
console.log(`https://sepolia.etherscan.io/address/${dleAddress}#readContract`);
console.log("\n💡 На Etherscan перейдите в раздел 'Contract' -> 'Read Contract'");
console.log(" и вызовите функцию 'getDLEInfo' чтобы увидеть эти данные!");
} catch (error) {
console.error("❌ Ошибка при чтении данных DLE:", error);
throw error;
}
}
// Запускаем скрипт
main()
.then(() => {
console.log("\n✅ Скрипт завершен успешно");
process.exit(0);
})
.catch((error) => {
console.error("\n❌ Скрипт завершен с ошибкой:", error);
process.exit(1);
});

View File

@@ -55,19 +55,52 @@ class DLEV2Service {
: 1; // По умолчанию Ethereum
// Получаем rpc_url из базы по chain_id
logger.info(`Поиск RPC URL для chain_id: ${chainId}`);
const rpcUrl = await getRpcUrlByChainId(chainId);
if (!rpcUrl) {
logger.error(`RPC URL для сети с chain_id ${chainId} не найден в базе данных`);
throw new Error(`RPC URL для сети с chain_id ${chainId} не найден в базе данных`);
}
logger.info(`Найден RPC URL для chain_id ${chainId}: ${rpcUrl}`);
// Проверяем баланс кошелька
const { ethers } = require('ethers');
const provider = new ethers.JsonRpcProvider(rpcUrl);
const walletAddress = dleParams.privateKey ? new ethers.Wallet(dleParams.privateKey, provider).address : null;
if (walletAddress) {
const balance = await provider.getBalance(walletAddress);
const minBalance = ethers.parseEther("0.00001"); // Временно уменьшено для тестирования
logger.info(`Баланс кошелька ${walletAddress}: ${ethers.formatEther(balance)} ETH`);
if (balance < minBalance) {
logger.warn(`Недостаточно ETH для деплоя. Баланс: ${ethers.formatEther(balance)} ETH, требуется минимум: ${ethers.formatEther(minBalance)} ETH`);
throw new Error(`Недостаточно ETH для деплоя. Баланс: ${ethers.formatEther(balance)} ETH, требуется минимум: ${ethers.formatEther(minBalance)} ETH. Пополните кошелек через Sepolia faucet: https://sepoliafaucet.com/`);
}
}
if (!dleParams.privateKey) {
throw new Error('Приватный ключ для деплоя не передан');
}
// Маппинг chain_id к именам сетей в Hardhat
const chainIdToNetworkName = {
1: 'ethereum',
137: 'polygon',
56: 'bsc',
42161: 'arbitrum',
11155111: 'sepolia'
};
const networkName = chainIdToNetworkName[chainId];
if (!networkName) {
throw new Error(`Сеть с chain_id ${chainId} не поддерживается для деплоя`);
}
// Запускаем скрипт деплоя с нужными переменными окружения
const result = await this.runDeployScript(paramsFile, {
rpcUrl,
privateKey: dleParams.privateKey,
networkId: chainId.toString(),
networkId: networkName,
envNetworkKey: chainId.toString().toUpperCase()
});
@@ -204,15 +237,15 @@ class DLEV2Service {
return;
}
// Формируем универсальные переменные окружения
// Формируем переменные окружения для скрипта деплоя
const envVars = {
...process.env,
[`${extraEnv.envNetworkKey}_RPC_URL`]: extraEnv.rpcUrl,
[`${extraEnv.envNetworkKey}_PRIVATE_KEY`]: extraEnv.privateKey
RPC_URL: extraEnv.rpcUrl,
PRIVATE_KEY: extraEnv.privateKey
};
// Запускаем скрипт с нужной сетью
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath, '--network', extraEnv.networkId], {
// Запускаем скрипт без указания сети, передаем RPC URL и приватный ключ через переменные окружения
const hardhatProcess = spawn('npx', ['hardhat', 'run', scriptPath], {
cwd: path.join(__dirname, '..'),
env: envVars,
stdio: 'pipe'
@@ -232,17 +265,17 @@ class DLEV2Service {
});
hardhatProcess.on('close', (code) => {
if (code === 0) {
try {
// Пытаемся извлечь результат из stdout
const result = this.extractDeployResult(stdout);
resolve(result);
} catch (error) {
logger.error('Ошибка при извлечении результатов деплоя DLE v2:', error);
try {
// Пытаемся извлечь результат из stdout независимо от кода завершения
const result = this.extractDeployResult(stdout);
resolve(result);
} catch (error) {
logger.error('Ошибка при извлечении результатов деплоя DLE v2:', error);
if (code === 0) {
reject(new Error('Не удалось найти информацию о созданном DLE v2'));
} else {
reject(new Error(`Скрипт деплоя DLE v2 завершился с кодом ${code}: ${stderr}`));
}
} else {
reject(new Error(`Скрипт деплоя DLE v2 завершился с кодом ${code}: ${stderr}`));
}
});
@@ -272,6 +305,8 @@ class DLEV2Service {
};
}
// Если не нашли адрес, выводим весь stdout для отладки
console.log('Полный вывод скрипта:', stdout);
throw new Error('Не удалось извлечь адрес DLE из вывода скрипта');
}

View File

@@ -63,7 +63,14 @@ async function getRpcUrlByNetworkId(networkId) {
}
async function getRpcUrlByChainId(chainId) {
console.log(`[RPC Service] Поиск RPC URL для chain_id: ${chainId}`);
const providers = await encryptedDb.getData('rpc_providers', { chain_id: chainId }, 1);
console.log(`[RPC Service] Найдено провайдеров: ${providers.length}`);
if (providers.length > 0) {
console.log(`[RPC Service] Найден RPC URL: ${providers[0].rpc_url}`);
} else {
console.log(`[RPC Service] RPC URL для chain_id ${chainId} не найден`);
}
return providers[0]?.rpc_url || null;
}

View File

@@ -25,26 +25,102 @@
<button class="close-btn" @click="router.push('/')">×</button>
</div>
<!-- Блоки управления -->
<div class="management-blocks">
<!-- Деплоированные DLE -->
<div class="deployed-dles-section">
<div class="section-header">
<div class="header-actions">
<button class="add-dle-btn" @click="openDleManagement()">
<i class="fas fa-plus"></i>
Добавить DLE
</button>
<button class="refresh-btn" @click="loadDeployedDles" :disabled="isLoadingDles">
<i class="fas fa-sync-alt" :class="{ 'fa-spin': isLoadingDles }"></i>
{{ isLoadingDles ? 'Загрузка...' : 'Обновить' }}
</button>
</div>
</div>
<div v-if="isLoadingDles" class="loading-dles">
<p>Загрузка деплоированных DLE...</p>
</div>
<div v-else-if="deployedDles.length === 0" class="no-dles">
<p>Деплоированных DLE пока нет</p>
<p>Создайте новый DLE на странице <a href="/settings/dle-v2-deploy" class="link">Деплой DLE</a></p>
</div>
<div v-else class="dles-grid">
<div
v-for="dle in deployedDles"
:key="dle.dleAddress"
class="dle-card"
:class="{ 'selected': selectedDle && selectedDle.dleAddress === dle.dleAddress }"
@click="selectDle(dle)"
>
<div class="dle-header">
<h3>{{ dle.name }} ({{ dle.symbol }})</h3>
<span class="dle-version">{{ dle.version || 'v2' }}</span>
</div>
<div class="dle-details">
<div class="detail-item">
<strong>Адрес контракта:</strong>
<a
:href="`https://sepolia.etherscan.io/address/${dle.dleAddress}`"
target="_blank"
class="address-link"
@click.stop
>
{{ shortenAddress(dle.dleAddress) }}
<i class="fas fa-external-link-alt"></i>
</a>
</div>
<div class="detail-item">
<strong>Местоположение:</strong> {{ dle.location }}
</div>
<div class="detail-item">
<strong>Юрисдикция:</strong> {{ dle.jurisdiction }}
</div>
<div class="detail-item">
<strong>Коды ОКВЭД:</strong> {{ dle.okvedCodes?.join(', ') || 'Не указаны' }}
</div>
<div class="detail-item">
<strong>Партнеры:</strong> {{ dle.participantCount || 0 }} участников
</div>
<div class="detail-item">
<strong>Статус:</strong>
<span class="status active">Активен</span>
</div>
</div>
</div>
</div>
</div>
<!-- Блоки управления выбранным DLE -->
<div v-if="selectedDle" class="management-blocks">
<!-- Первый ряд -->
<div class="blocks-row">
<div class="management-block">
<h3>Предложения</h3>
<p>Создание, подписание, выполнение</p>
<button class="details-btn" @click="openProposals">Подробнее</button>
<button class="details-btn" @click="openProposalsWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Токены DLE</h3>
<p>Балансы, трансферы, распределение</p>
<button class="details-btn" @click="openTokens">Подробнее</button>
<button class="details-btn" @click="openTokensWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Кворум</h3>
<p>Настройки голосования</p>
<button class="details-btn" @click="openQuorum">Подробнее</button>
<button class="details-btn" @click="openQuorumWithDle">Подробнее</button>
</div>
</div>
@@ -53,58 +129,63 @@
<div class="management-block">
<h3>Модули DLE</h3>
<p>Установка, настройка, управление</p>
<button class="details-btn" @click="openModules">Подробнее</button>
</div>
<div class="management-block">
<h3>DLE</h3>
<p>Интеграция с другими DLE, участие в кворумах</p>
<button class="details-btn" @click="openDle">Подробнее</button>
<button class="details-btn" @click="openModulesWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Казна</h3>
<p>Управление средствами</p>
<button class="details-btn" @click="openTreasury">Подробнее</button>
<button class="details-btn" @click="openTreasuryWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Аналитика</h3>
<p>Графики, статистика, отчеты</p>
<button class="details-btn" @click="openAnalyticsWithDle">Подробнее</button>
</div>
</div>
<!-- Третий ряд -->
<div class="blocks-row">
<div class="management-block">
<h3>Аналитика</h3>
<p>Графики, статистика, отчеты</p>
<button class="details-btn" @click="openAnalytics">Подробнее</button>
</div>
<div class="management-block">
<h3>История</h3>
<p>Лог операций, события, транзакции</p>
<button class="details-btn" @click="openHistory">Подробнее</button>
<button class="details-btn" @click="openHistoryWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Настройки</h3>
<p>Параметры DLE, конфигурация</p>
<button class="details-btn" @click="openSettings">Подробнее</button>
<button class="details-btn" @click="openSettingsWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Мультиподпись</h3>
<p>Управление мультиподписью</p>
<button class="details-btn" @click="openMultisigWithDle">Подробнее</button>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
import { defineProps, defineEmits, ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../components/BaseLayout.vue';
import api from '@/api/axios';
// Определяем props
const props = defineProps({
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
isAuthenticated: { type: Boolean, default: false },
identities: { type: Array, default: () => [] },
tokenBalances: { type: Object, default: () => ({}) },
isLoadingTokens: { type: Boolean, default: false }
});
// Определяем emits
@@ -112,6 +193,13 @@ const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
// Состояние для DLE
const deployedDles = ref([]);
const isLoadingDles = ref(false);
const selectedDle = ref(null);
// Функции для открытия страниц управления
const openProposals = () => {
router.push('/management/proposals');
@@ -148,6 +236,156 @@ const openHistory = () => {
const openSettings = () => {
router.push('/management/settings');
};
// Загрузка деплоированных DLE из блокчейна
async function loadDeployedDles() {
try {
isLoadingDles.value = true;
// Сначала получаем список DLE из API
const response = await api.get('/dle-v2');
if (response.data.success) {
const dlesFromApi = response.data.data || [];
// Для каждого DLE читаем актуальные данные из блокчейна
const dlesWithBlockchainData = await Promise.all(
dlesFromApi.map(async (dle) => {
try {
// Читаем данные из блокчейна
const blockchainResponse = await api.post('/blockchain/read-dle-info', {
dleAddress: dle.dleAddress
});
if (blockchainResponse.data.success) {
const blockchainData = blockchainResponse.data.data;
// Объединяем данные из API с данными из блокчейна
return {
...dle,
// Данные из блокчейна (приоритет)
name: blockchainData.name || dle.name,
symbol: blockchainData.symbol || dle.symbol,
location: blockchainData.location || dle.location,
coordinates: blockchainData.coordinates || dle.coordinates,
jurisdiction: blockchainData.jurisdiction || dle.jurisdiction,
oktmo: blockchainData.oktmo || dle.oktmo,
okvedCodes: blockchainData.okvedCodes || dle.okvedCodes,
kpp: blockchainData.kpp || dle.kpp,
// Информация о токенах из блокчейна
totalSupply: blockchainData.totalSupply,
deployerBalance: blockchainData.deployerBalance,
// Количество участников (держателей токенов)
participantCount: blockchainData.participantCount || 0
};
} else {
console.warn(`Не удалось прочитать данные из блокчейна для ${dle.dleAddress}`);
return dle;
}
} catch (error) {
console.warn(`Ошибка при чтении данных из блокчейна для ${dle.dleAddress}:`, error);
return dle;
}
})
);
deployedDles.value = dlesWithBlockchainData;
console.log('Загружены деплоированные DLE с данными из блокчейна:', deployedDles.value);
} else {
console.error('Ошибка при загрузке DLE:', response.data.message);
deployedDles.value = [];
}
} catch (error) {
console.error('Ошибка при загрузке DLE:', error);
deployedDles.value = [];
} finally {
isLoadingDles.value = false;
}
}
// Функции для работы с DLE
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function openDleOnEtherscan(address) {
window.open(`https://sepolia.etherscan.io/address/${address}`, '_blank');
}
function openDleManagement(dleAddress) {
// Переход к детальному управлению DLE (если нужно)
router.push(`/management/dle-management?address=${dleAddress}`);
}
function selectDle(dle) {
selectedDle.value = dle;
console.log('Выбран DLE:', dle);
}
function openMultisig() {
router.push('/management/multisig');
}
// Функции с передачей адреса DLE
function openProposalsWithDle() {
if (selectedDle.value) {
router.push(`/management/proposals?address=${selectedDle.value.dleAddress}`);
}
}
function openTokensWithDle() {
if (selectedDle.value) {
router.push(`/management/tokens?address=${selectedDle.value.dleAddress}`);
}
}
function openQuorumWithDle() {
if (selectedDle.value) {
router.push(`/management/quorum?address=${selectedDle.value.dleAddress}`);
}
}
function openModulesWithDle() {
if (selectedDle.value) {
router.push(`/management/modules?address=${selectedDle.value.dleAddress}`);
}
}
function openTreasuryWithDle() {
if (selectedDle.value) {
router.push(`/management/treasury?address=${selectedDle.value.dleAddress}`);
}
}
function openAnalyticsWithDle() {
if (selectedDle.value) {
router.push(`/management/analytics?address=${selectedDle.value.dleAddress}`);
}
}
function openHistoryWithDle() {
if (selectedDle.value) {
router.push(`/management/history?address=${selectedDle.value.dleAddress}`);
}
}
function openSettingsWithDle() {
if (selectedDle.value) {
router.push(`/management/settings?address=${selectedDle.value.dleAddress}`);
}
}
function openMultisigWithDle() {
if (selectedDle.value) {
router.push(`/management/multisig?address=${selectedDle.value.dleAddress}`);
}
}
onMounted(() => {
loadDeployedDles();
});
</script>
<style scoped>
@@ -201,6 +439,7 @@ const openSettings = () => {
display: flex;
flex-direction: column;
gap: 2rem;
margin-top: 2rem;
}
.blocks-row {
@@ -227,6 +466,226 @@ const openSettings = () => {
transform: translateY(-2px);
}
/* Секция деплоированных DLE */
.deployed-dles-section {
margin-top: 3rem;
}
.section-header {
display: flex;
justify-content: flex-end;
align-items: center;
margin-bottom: 2rem;
}
.header-actions {
display: flex;
gap: 1rem;
align-items: center;
}
.add-dle-btn {
background: var(--color-primary);
color: white;
border: none;
border-radius: 6px;
padding: 0.5rem 1rem;
cursor: pointer;
font-size: 0.875rem;
font-weight: 600;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.add-dle-btn:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
}
.add-dle-btn i {
font-size: 0.875rem;
}
.section-header h2 {
color: var(--color-primary);
margin: 0;
}
.refresh-btn {
background: var(--color-primary);
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.875rem;
font-weight: 600;
transition: background-color 0.2s;
}
.refresh-btn:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
}
.refresh-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading-dles,
.no-dles {
text-align: center;
padding: 2rem;
color: #666;
}
.no-dles .link {
color: var(--color-primary);
text-decoration: none;
}
.no-dles .link:hover {
text-decoration: underline;
}
.dles-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 1.5rem;
}
.dle-card {
background: white;
border-radius: 8px;
padding: 1.5rem;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #e9ecef;
transition: all 0.3s ease;
}
.dle-card:hover {
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
cursor: pointer;
}
.dle-card.selected {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
background: #f8f9ff;
}
.dle-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
}
.dle-header h3 {
margin: 0;
color: var(--color-primary);
font-size: 1.25rem;
}
.dle-version {
background: #e9ecef;
color: #495057;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
}
.dle-details {
margin-bottom: 1.5rem;
}
.detail-item {
margin-bottom: 0.5rem;
font-size: 0.9rem;
}
.detail-item strong {
color: #333;
}
.address {
font-family: monospace;
background: #f8f9fa;
padding: 0.125rem 0.25rem;
border-radius: 3px;
font-size: 0.875rem;
}
.address-link {
font-family: monospace;
background: #f8f9fa;
padding: 0.125rem 0.25rem;
border-radius: 3px;
font-size: 0.875rem;
color: var(--color-primary);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.25rem;
transition: all 0.2s;
}
.address-link:hover {
background: #e3f2fd;
color: var(--color-primary-dark);
text-decoration: none;
}
.address-link i {
font-size: 0.75rem;
opacity: 0.7;
}
.status {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
}
.status.active {
background: #d4edda;
color: #155724;
}
.dle-actions {
display: flex;
gap: 0.5rem;
}
.btn-sm {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.btn-info {
background: #17a2b8;
color: white;
}
.btn-info:hover {
background: #138496;
}
.management-block h3 {
color: var(--color-primary);
margin: 0 0 1rem 0;
@@ -274,4 +733,8 @@ const openSettings = () => {
font-size: 1.3rem;
}
}
</style>

View File

@@ -325,19 +325,7 @@
<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">
@@ -488,19 +476,7 @@
</div>
</div> -->
<!-- Общая стоимость -->
<div v-if="selectedNetworks.length > 0" class="total-cost-section">
<div class="cost-breakdown">
<h5>💰 Стоимость деплоя:</h5>
<div v-for="network in selectedNetworkDetails" :key="network.chainId" class="cost-line">
<span>{{ network.name }}:</span>
<span class="cost">~${{ network.estimatedCost }}</span>
</div>
<div class="total-line">
<strong>Общая стоимость: ~${{ totalDeployCost.toFixed(2) }}</strong>
</div>
</div>
</div>
<!-- Кнопки управления RPC -->
<div class="rpc-settings-actions">
@@ -546,40 +522,27 @@
</div>
</div>
<!-- Информация о ключе -->
<div v-if="selectedNetworks.length > 0" class="key-info">
<div class="info-card">
<div class="info-icon">
<i class="fas fa-key"></i>
</div>
<div class="info-content">
<h5>Как это работает?</h5>
<p>Один приватный ключ создаст одинаковый адрес во всех EVM-совместимых сетях. Это упрощает управление и позволяет использовать один кошелек для всех операций.</p>
</div>
</div>
</div>
<!-- Ввод приватного ключа -->
<div v-if="selectedNetworks.length > 0" class="key-input-section">
<div class="form-group">
<label class="form-label">Приватный ключ:</label>
<div class="input-icon-wrapper">
<input
:type="showUnifiedKey ? 'text' : 'password'"
v-model="unifiedPrivateKey"
class="form-control"
placeholder="Введите приватный ключ (0x... или без префикса)"
@input="() => { console.log('Input event triggered'); validatePrivateKey('unified'); }"
@focus="() => console.log('Input field focused')"
@blur="() => console.log('Input field blurred')"
@input="validatePrivateKey('unified')"
@keyup="validatePrivateKey('unified')"
@change="validatePrivateKey('unified')"
>
<span class="input-icon" @click="showUnifiedKey = !showUnifiedKey">
<i :class="showUnifiedKey ? 'fas fa-eye-slash' : 'fas fa-eye'"></i>
</span>
</div>
<small class="form-help">
Этот ключ будет использован для деплоя во всех выбранных сетях
</small>
</div>
<!-- Валидация ключа -->
@@ -790,7 +753,7 @@
@click="deploySmartContracts"
type="button"
class="btn btn-primary btn-lg deploy-btn"
:disabled="!isFormValid"
:disabled="!isFormValid || !adminTokenCheck.isAdmin || adminTokenCheck.isLoading || showDeployProgress"
>
<i class="fas fa-rocket"></i> Деплой смарт контрактов
</button>
@@ -799,13 +762,49 @@
@click="clearAllData"
class="btn btn-danger btn-lg clear-btn"
title="Очистить все данные"
:disabled="showDeployProgress"
>
Удалить все
</button>
</div>
<small class="deploy-help">
<!-- Индикатор процесса деплоя -->
<div v-if="showDeployProgress" class="deploy-progress">
<div class="progress-header">
<h4>🚀 Деплой DLE в блокчейне</h4>
<p>{{ deployStatus }}</p>
</div>
</small>
<div class="progress-bar-container">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: deployProgress + '%' }"
></div>
</div>
<span class="progress-text">{{ deployProgress }}%</span>
</div>
<div class="progress-steps">
<div class="step" :class="{ active: deployProgress >= 10 }">
<i class="fas fa-check-circle"></i>
<span>Подготовка данных</span>
</div>
<div class="step" :class="{ active: deployProgress >= 30 }">
<i class="fas fa-check-circle"></i>
<span>Отправка на сервер</span>
</div>
<div class="step" :class="{ active: deployProgress >= 70 }">
<i class="fas fa-check-circle"></i>
<span>Деплой в блокчейне</span>
</div>
<div class="step" :class="{ active: deployProgress >= 100 }">
<i class="fas fa-check-circle"></i>
<span>Завершение</span>
</div>
</div>
</div>
</div>
</div>
@@ -829,6 +828,13 @@ const router = useRouter();
// Получаем контекст авторизации для адреса кошелька
const { address, isAdmin } = useAuthContext();
// Состояние для проверки админских токенов
const adminTokenCheck = ref({
isLoading: false,
isAdmin: false,
error: null
});
// Основные настройки DLE
const dleSettings = reactive({
// Юрисдикция
@@ -996,6 +1002,11 @@ const selectedOkvedLevel4 = ref('');
const currentSelectedOkvedCode = ref('');
const currentSelectedOkvedText = ref('');
// Состояние процесса деплоя
const showDeployProgress = ref(false);
const deployProgress = ref(0);
const deployStatus = ref('');
// Функция определения уровня ОКВЭД кода
const getOkvedLevel = (code) => {
if (!code) return 0;
@@ -1300,7 +1311,8 @@ const saveFormData = () => {
showUnifiedKey: showUnifiedKey.value
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(dataToSave));
// console.log('[DleDeployForm] Данные формы сохранены в localStorage');
console.log('[DleDeployForm] Данные формы сохранены в localStorage');
console.log('[DleDeployForm] Coordinates saved:', dataToSave.coordinates);
} catch (error) {
// console.error('[DleDeployForm] Ошибка сохранения данных:', error);
}
@@ -1335,6 +1347,8 @@ const loadFormData = () => {
tokenSymbol: parsedData.tokenSymbol || '',
partners: parsedData.partners || [{ address: '', amount: 1 }],
governanceQuorum: parsedData.governanceQuorum || 51,
// Координаты
coordinates: parsedData.coordinates || '',
// Мульти-чейн настройки
selectedNetworks: parsedData.selectedNetworks || [],
tokenStandard: parsedData.tokenStandard || 'ERC20',
@@ -1365,7 +1379,8 @@ const loadFormData = () => {
Object.assign(keyValidation, parsedData.keyValidation || {});
showUnifiedKey.value = parsedData.showUnifiedKey || false;
// console.log('[DleDeployForm] Данные формы восстановлены из localStorage');
console.log('[DleDeployForm] Данные формы восстановлены из localStorage');
console.log('[DleDeployForm] Coordinates loaded:', dleSettings.coordinates);
return true;
}
} catch (error) {
@@ -1413,6 +1428,9 @@ const clearAllData = () => {
dleSettings.tokenStandard = 'ERC20'; // Сбрасываем к стандартному ERC-20
dleSettings.predictedAddress = '';
// Очищаем координаты
dleSettings.coordinates = '';
// Устаревшие поля
dleSettings.deployNetwork = '';
dleSettings.privateKey = '';
@@ -1536,6 +1554,8 @@ const findOktmoByAddress = (result) => {
// Заполнение полей из результата поиска
const fillFromSearchResult = (result) => {
console.log('[FillFromSearchResult] Called with result:', result);
dleSettings.addressData.postalCode = result.postcode;
dleSettings.addressData.region = result.region;
dleSettings.addressData.city = result.city;
@@ -1544,6 +1564,22 @@ const fillFromSearchResult = (result) => {
dleSettings.addressData.apartment = ''; // Квартиру пользователь введет сам
dleSettings.addressData.isVerified = false; // Требует проверки после дозаполнения
// Сохраняем координаты в dleSettings
if (result.coordinates && result.coordinates.lat && result.coordinates.lon) {
dleSettings.coordinates = `${result.coordinates.lat},${result.coordinates.lon}`;
console.log(`[FillFromSearchResult] Saved coordinates from coordinates object: ${dleSettings.coordinates}`);
// Сохраняем в localStorage
saveFormData();
} else if (result.lat && result.lon) {
// Альтернативный формат координат
dleSettings.coordinates = `${result.lat},${result.lon}`;
console.log(`[FillFromSearchResult] Saved coordinates from lat/lon: ${dleSettings.coordinates}`);
// Сохраняем в localStorage
saveFormData();
} else {
console.log('[FillFromSearchResult] No coordinates found in result');
}
// Сохраняем результат API для отображения в превью
lastApiResult.value = result;
@@ -1597,6 +1633,14 @@ const verifyAddress = async () => {
addr.fullAddress = verificationResult.display_name;
addr.isVerified = true;
// Сохраняем координаты из результата проверки
if (verificationResult.lat && verificationResult.lon) {
dleSettings.coordinates = `${verificationResult.lat},${verificationResult.lon}`;
console.log(`[VerifyAddress] Saved coordinates: ${dleSettings.coordinates}`);
// Сохраняем в localStorage
saveFormData();
}
console.log('[VerifyAddress] Address verified successfully:', addr.fullAddress);
} else {
// Если не найден - все равно считаем валидным (пользователь может знать лучше)
@@ -1633,6 +1677,8 @@ const clearAddress = () => {
fullAddress: '',
isVerified: false
};
// Очищаем координаты
dleSettings.coordinates = '';
postalCodeInput.value = '';
searchResults.value = [];
autoSelectedOktmo.value = false;
@@ -1955,8 +2001,6 @@ const toggleKeyVisibility = (chainId) => {
// Валидация приватного ключа с дебаунсом
const validatePrivateKey = async (chainId) => {
console.log('Функция validatePrivateKey вызвана для chainId:', chainId);
// Очищаем предыдущий таймер
if (validatePrivateKey.timeout) {
clearTimeout(validatePrivateKey.timeout);
@@ -1965,7 +2009,6 @@ const validatePrivateKey = async (chainId) => {
// Устанавливаем новый таймер для дебаунса
validatePrivateKey.timeout = setTimeout(async () => {
const key = chainId === 'unified' ? unifiedPrivateKey.value : privateKeys[chainId];
console.log('Ключ для валидации:', key);
if (!key) {
keyValidation[chainId] = null;
@@ -1973,18 +2016,11 @@ const validatePrivateKey = async (chainId) => {
}
try {
// Логируем отправляемый ключ (только для отладки)
console.log('Отправляем приватный ключ для валидации:', key);
console.log('Длина ключа:', key.length);
console.log('Полный ключ:', key);
// Отправляем запрос на бэкенд для валидации
const response = await axios.post('/api/dle-v2/validate-private-key', {
const response = await axios.post('/dle-v2/validate-private-key', {
privateKey: key
});
console.log('Ответ от сервера:', response.data);
if (response.data.success) {
keyValidation[chainId] = response.data.data;
} else {
@@ -2065,6 +2101,15 @@ watch([selectedOkvedLevel1, selectedOkvedLevel2, postalCodeInput], () => {
}, 100);
});
// Watcher для координат
watch(() => dleSettings.coordinates, (newCoordinates) => {
console.log('[Coordinates Watcher] Coordinates changed:', newCoordinates);
// Добавляем небольшую задержку для предотвращения рекурсии
setTimeout(() => {
saveFormData();
}, 100);
});
// ==================== МУЛЬТИ-ЧЕЙН WATCHERS ====================
// Watcher для selectedNetworks - синхронизация с dleSettings
@@ -2120,8 +2165,6 @@ watch([() => dleSettings.name, () => dleSettings.tokenSymbol, selectedNetworks],
// Инициализация
onMounted(() => {
console.log('🚀 DleDeployFormView компонент загружен - ТЕСТ ОБНОВЛЕНИЯ');
alert('Компонент загружен - проверьте консоль');
// Загружаем список стран
loadCountries();
@@ -2149,6 +2192,9 @@ onMounted(() => {
// Добавляем слушатель события видимости страницы для обновления списка сетей
document.addEventListener('visibilitychange', handleVisibilityChange);
// Проверяем админские токены при загрузке
checkAdminTokens();
});
// Удаляем слушатель при размонтировании компонента
@@ -2163,6 +2209,33 @@ watch(address, (newAddress) => {
}
});
// Функция проверки админских токенов
const checkAdminTokens = async () => {
if (!address.value) {
adminTokenCheck.value = { isLoading: false, isAdmin: false, error: 'Кошелек не подключен' };
return;
}
adminTokenCheck.value.isLoading = true;
adminTokenCheck.value.error = null;
try {
const response = await axios.get(`/dle-v2/check-admin-tokens?address=${address.value}`);
if (response.data.success) {
adminTokenCheck.value.isAdmin = response.data.data.isAdmin;
console.log('Проверка админских токенов:', response.data.data);
} else {
adminTokenCheck.value.error = response.data.message || 'Ошибка проверки токенов';
}
} catch (error) {
console.error('Ошибка проверки админских токенов:', error);
adminTokenCheck.value.error = error.response?.data?.message || 'Ошибка проверки токенов';
} finally {
adminTokenCheck.value.isLoading = false;
}
};
// Функции для работы с партнерами
const addPartner = () => {
dleSettings.partners.push({ address: '', amount: 1 });
@@ -2202,6 +2275,11 @@ const deploySmartContracts = async () => {
return;
}
// Показываем индикатор процесса
showDeployProgress.value = true;
deployProgress.value = 10;
deployStatus.value = 'Подготовка данных для деплоя...';
// Подготовка данных для деплоя
const deployData = {
// Основная информация DLE
@@ -2232,24 +2310,38 @@ const deploySmartContracts = async () => {
};
console.log('Данные для деплоя DLE:', deployData);
deployProgress.value = 30;
deployStatus.value = 'Отправка данных на сервер...';
// Вызов API для деплоя
const response = await axios.post('/api/dle-v2', deployData);
const response = await axios.post('/dle-v2', deployData);
deployProgress.value = 70;
deployStatus.value = 'Деплой смарт-контракта в блокчейне...';
if (response.data.success) {
alert('✅ DLE успешно развернут!');
deployProgress.value = 100;
deployStatus.value = '✅ DLE успешно развернут!';
// Сохраняем адрес контракта
dleSettings.predictedAddress = response.data.data?.contractAddress || 'Адрес будет доступен после деплоя';
// Перенаправляем на страницу управления
router.push('/management/dle-management');
// Небольшая задержка для показа успешного завершения
setTimeout(() => {
showDeployProgress.value = false;
// Перенаправляем на главную страницу управления
router.push('/management');
}, 2000);
} else {
showDeployProgress.value = false;
alert('❌ Ошибка при деплое: ' + response.data.error);
}
} catch (error) {
console.error('Ошибка деплоя DLE:', error);
showDeployProgress.value = false;
alert('❌ Ошибка при деплое смарт-контракта: ' + error.message);
}
};
@@ -3920,4 +4012,128 @@ const validateCoordinates = (coordinates) => {
.clear-btn {
min-width: 150px;
}
/* Стили для индикатора статуса админских токенов */
.admin-status {
padding: 8px 12px;
border-radius: 4px;
margin-top: 8px;
font-size: 0.9rem;
}
.admin-status.success {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.admin-status.warning {
background-color: #fff3cd;
color: #856404;
border: 1px solid #ffeaa7;
}
.admin-status.error {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Стили для индикатора процесса деплоя */
.deploy-progress {
margin-top: 2rem;
padding: 2rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 12px;
color: white;
animation: fadeIn 0.5s ease;
}
.progress-header {
text-align: center;
margin-bottom: 2rem;
}
.progress-header h4 {
margin: 0 0 0.5rem 0;
font-size: 1.5rem;
font-weight: 600;
}
.progress-header p {
margin: 0;
opacity: 0.9;
font-size: 1.1rem;
}
.progress-bar-container {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
}
.progress-bar {
flex: 1;
height: 12px;
background: rgba(255, 255, 255, 0.2);
border-radius: 6px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4ade80 0%, #22c55e 100%);
border-radius: 6px;
transition: width 0.5s ease;
}
.progress-text {
font-weight: 600;
font-size: 1.1rem;
min-width: 50px;
}
.progress-steps {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.step {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
opacity: 0.5;
transition: all 0.3s ease;
}
.step.active {
opacity: 1;
background: rgba(255, 255, 255, 0.2);
}
.step i {
font-size: 1.2rem;
color: #4ade80;
}
.step span {
font-size: 0.9rem;
font-weight: 500;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,14 @@
-->
<template>
<div class="dle-multisig-management">
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="dle-multisig-management">
<div class="multisig-header">
<h3>🔐 Управление мультиподписью</h3>
<button class="btn btn-primary" @click="showCreateForm = true">
@@ -272,17 +279,25 @@
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, defineProps, defineEmits } from 'vue';
import { useAuthContext } from '@/composables/useAuth';
import BaseLayout from '../../components/BaseLayout.vue';
const props = defineProps({
dleAddress: { type: String, required: true },
dleContract: { type: Object, required: true }
dleAddress: { type: String, required: false, default: null },
dleContract: { type: Object, required: false, default: null },
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
const emit = defineEmits(['auth-action-completed']);
const { address } = useAuthContext();
// Состояние формы

View File

@@ -11,10 +11,29 @@
-->
<template>
<div class="dle-proposals-management">
<BaseLayout
:is-authenticated="isAuthenticated"
:identities="identities"
:token-balances="tokenBalances"
:is-loading-tokens="isLoadingTokens"
@auth-action-completed="$emit('auth-action-completed')"
>
<div class="dle-proposals-management">
<div class="proposals-header">
<h3>🗳 Управление предложениями</h3>
<button class="btn btn-primary" @click="showCreateForm = true">
<div class="header-info">
<h3>🗳 Управление предложениями</h3>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ shortenAddress(selectedDle.dleAddress) }}</span>
</div>
<div v-else-if="isLoadingDle" class="loading-info">
<span>Загрузка данных DLE...</span>
</div>
<div v-else class="no-dle-info">
<span>DLE не выбран</span>
</div>
</div>
<button class="btn btn-primary" @click="showCreateForm = true" :disabled="!selectedDle">
<i class="fas fa-plus"></i> Создать предложение
</button>
</div>
@@ -322,18 +341,41 @@
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, watch, defineProps, defineEmits } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAuthContext } from '@/composables/useAuth';
import BaseLayout from '../../components/BaseLayout.vue';
import axios from 'axios';
const props = defineProps({
dleAddress: { type: String, required: true },
dleContract: { type: Object, required: true }
dleAddress: { type: String, required: false, default: null },
dleContract: { type: Object, required: false, default: null },
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
const emit = defineEmits(['auth-action-completed']);
const { address } = useAuthContext();
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из URL
const dleAddress = computed(() => {
const address = route.query.address || props.dleAddress;
console.log('DLE Address from URL:', address);
return address;
});
// Состояние DLE
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Состояние формы
const showCreateForm = ref(false);
@@ -382,6 +424,38 @@ const filteredProposals = computed(() => {
});
// Функции
async function loadDleData() {
console.log('loadDleData вызвана с адресом:', dleAddress.value);
if (!dleAddress.value) {
console.warn('Адрес DLE не указан');
return;
}
isLoadingDle.value = true;
try {
// Загружаем данные DLE из backend
const response = await axios.get(`/dle-v2`);
const dles = response.data.data; // Используем response.data.data
console.log('Получены DLE из API:', dles);
// Находим нужный DLE по адресу
const dle = dles.find(d => d.dleAddress === dleAddress.value);
console.log('Найденный DLE:', dle);
if (dle) {
selectedDle.value = dle;
console.log('Загружен DLE:', dle);
} else {
console.warn('DLE не найден:', dleAddress.value);
}
} catch (error) {
console.error('Ошибка загрузки DLE:', error);
} finally {
isLoadingDle.value = false;
}
}
function validateOperationParams() {
const params = newProposal.value.operationParams;
@@ -606,7 +680,15 @@ function viewProposalDetails(proposalId) {
// console.log('Просмотр деталей предложения:', proposalId);
}
// Отслеживаем изменения в адресе DLE
watch(dleAddress, (newAddress) => {
if (newAddress) {
loadDleData();
}
}, { immediate: true });
onMounted(() => {
// Загрузка предложений
loadProposals();
});
</script>
@@ -623,6 +705,47 @@ onMounted(() => {
margin-bottom: 2rem;
}
.header-info {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.header-info h3 {
margin: 0;
color: var(--color-primary);
}
.dle-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.dle-name {
font-weight: 600;
color: #333;
font-size: 1rem;
}
.dle-address {
font-family: monospace;
font-size: 0.875rem;
color: #666;
background: #f8f9fa;
padding: 0.25rem 0.5rem;
border-radius: 4px;
display: inline-block;
width: fit-content;
}
.loading-info,
.no-dle-info {
font-size: 0.875rem;
color: #666;
font-style: italic;
}
.create-proposal-form {
background: #f8f9fa;
border-radius: 8px;

View File

@@ -23,7 +23,16 @@
<div class="page-header">
<div class="header-content">
<h1>Токены DLE</h1>
<p>Балансы, трансферы и распределение токенов</p>
<div v-if="selectedDle" class="dle-info">
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
<span class="dle-address">{{ shortenAddress(selectedDle.dleAddress) }}</span>
</div>
<div v-else-if="isLoadingDle" class="loading-info">
<span>Загрузка данных DLE...</span>
</div>
<div v-else class="no-dle-info">
<span>DLE не выбран</span>
</div>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
@@ -185,9 +194,10 @@
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
import { useRouter } from 'vue-router';
import { ref, computed, onMounted, watch, defineProps, defineEmits } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import axios from 'axios';
// Определяем props
const props = defineProps({
@@ -201,16 +211,28 @@ const props = defineProps({
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из URL
const dleAddress = computed(() => {
const address = route.query.address;
console.log('DLE Address from URL (Tokens):', address);
return address;
});
// Состояние DLE
const selectedDle = ref(null);
const isLoadingDle = ref(false);
// Состояние
const isTransferring = ref(false);
const isDistributing = ref(false);
// Данные токенов
const tokenSymbol = ref('MDLE');
const totalSupply = ref(10000);
const userBalance = ref(1000);
const quorumPercentage = ref(51);
// Данные токенов (реактивные)
const tokenSymbol = computed(() => selectedDle.value?.symbol || 'MDLE');
const totalSupply = computed(() => selectedDle.value?.initialAmounts?.[0] || 10000);
const userBalance = computed(() => Math.floor(totalSupply.value * 0.1)); // 10% для демо
const quorumPercentage = computed(() => selectedDle.value?.governanceSettings?.quorumPercentage || 51);
const tokenPrice = ref(1.25);
// Данные трансфера
@@ -237,6 +259,41 @@ const tokenHolders = ref([
{ address: '0x5678901234567890123456789012345678901234', balance: 600 }
]);
// Функции
async function loadDleData() {
if (!dleAddress.value) {
console.warn('Адрес DLE не указан');
return;
}
isLoadingDle.value = true;
try {
// Загружаем данные DLE из backend
const response = await axios.get(`/dle-v2`);
const dles = response.data.data; // Используем response.data.data
// Находим нужный DLE по адресу
const dle = dles.find(d => d.dleAddress === dleAddress.value);
if (dle) {
selectedDle.value = dle;
console.log('Загружен DLE:', dle);
console.log('Данные токенов будут обновлены автоматически');
} else {
console.warn('DLE не найден:', dleAddress.value);
}
} catch (error) {
console.error('Ошибка загрузки DLE:', error);
} finally {
isLoadingDle.value = false;
}
}
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
// Методы
const transferTokens = async () => {
if (isTransferring.value) return;
@@ -324,6 +381,13 @@ const formatAddress = (address) => {
if (!address) return '';
return address.substring(0, 6) + '...' + address.substring(address.length - 4);
};
// Отслеживаем изменения в адресе DLE
watch(dleAddress, (newAddress) => {
if (newAddress) {
loadDleData();
}
}, { immediate: true });
</script>
<style scoped>
@@ -361,6 +425,38 @@ const formatAddress = (address) => {
margin: 0;
}
.dle-info {
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: 0.5rem;
}
.dle-name {
font-weight: 600;
color: #333;
font-size: 1rem;
}
.dle-address {
font-family: monospace;
font-size: 0.875rem;
color: #666;
background: #f8f9fa;
padding: 0.25rem 0.5rem;
border-radius: 4px;
display: inline-block;
width: fit-content;
}
.loading-info,
.no-dle-info {
font-size: 0.875rem;
color: #666;
font-style: italic;
margin-top: 0.5rem;
}
.close-btn {
background: none;
border: none;