🔧 Исправление отображения данных 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:
@@ -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); // Подключаем роут
|
||||
|
||||
111
backend/routes/blockchain.js
Normal file
111
backend/routes/blockchain.js
Normal 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;
|
||||
@@ -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 если есть
|
||||
|
||||
@@ -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) + " токенов"));
|
||||
|
||||
// Получаем 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);
|
||||
|
||||
// Получаем аккаунт деплоя
|
||||
const [deployer] = await ethers.getSigners();
|
||||
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}`);
|
||||
|
||||
// 3. Сохраняем информацию о созданном DLE
|
||||
console.log("\n3. Сохранение информации о DLE v2...");
|
||||
// Проверяем, что данные записались правильно
|
||||
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("❌ Ошибка: данные не записались правильно в блокчейн");
|
||||
}
|
||||
|
||||
// 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],
|
||||
|
||||
56
backend/scripts/utils/get-rpc-url.js
Normal file
56
backend/scripts/utils/get-rpc-url.js
Normal 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);
|
||||
});
|
||||
107
backend/scripts/utils/read-dle-info.js
Normal file
107
backend/scripts/utils/read-dle-info.js
Normal 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);
|
||||
});
|
||||
@@ -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 из вывода скрипта');
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
</small>
|
||||
<!-- Индикатор процесса деплоя -->
|
||||
<div v-if="showDeployProgress" class="deploy-progress">
|
||||
<div class="progress-header">
|
||||
<h4>🚀 Деплой DLE в блокчейне</h4>
|
||||
<p>{{ deployStatus }}</p>
|
||||
</div>
|
||||
|
||||
<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
|
||||
@@ -2233,23 +2311,37 @@ 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
@@ -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();
|
||||
|
||||
// Состояние формы
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user