ваше сообщение коммита
This commit is contained in:
@@ -43,6 +43,11 @@ async function upsertAuthToken(token) {
|
||||
const readonlyThreshold = (token.readonlyThreshold === null || token.readonlyThreshold === undefined || token.readonlyThreshold === '') ? 1 : Number(token.readonlyThreshold);
|
||||
const editorThreshold = (token.editorThreshold === null || token.editorThreshold === undefined || token.editorThreshold === '') ? 2 : Number(token.editorThreshold);
|
||||
|
||||
// Валидация порогов доступа
|
||||
if (readonlyThreshold >= editorThreshold) {
|
||||
throw new Error('Минимум токенов для Read-Only доступа должен быть меньше минимума для Editor доступа');
|
||||
}
|
||||
|
||||
console.log('[AuthTokenService] Вычисленные значения:');
|
||||
console.log('[AuthTokenService] readonlyThreshold:', readonlyThreshold);
|
||||
console.log('[AuthTokenService] editorThreshold:', editorThreshold);
|
||||
|
||||
@@ -38,22 +38,23 @@ class DeployParamsService {
|
||||
coordinates: params.coordinates,
|
||||
jurisdiction: params.jurisdiction,
|
||||
oktmo: params.oktmo,
|
||||
okved_codes: JSON.stringify(params.okvedCodes || []),
|
||||
okved_codes: JSON.stringify(params.okved_codes || []),
|
||||
kpp: params.kpp,
|
||||
quorum_percentage: params.quorumPercentage,
|
||||
initial_partners: JSON.stringify(params.initialPartners || []),
|
||||
initial_amounts: JSON.stringify(params.initialAmounts || []),
|
||||
supported_chain_ids: JSON.stringify(params.supportedChainIds || []),
|
||||
current_chain_id: params.currentChainId,
|
||||
logo_uri: params.logoURI,
|
||||
private_key: params.privateKey, // Будет автоматически зашифрован
|
||||
etherscan_api_key: params.etherscanApiKey,
|
||||
auto_verify_after_deploy: params.autoVerifyAfterDeploy || false,
|
||||
create2_salt: params.CREATE2_SALT,
|
||||
rpc_urls: JSON.stringify(params.rpcUrls ? (Array.isArray(params.rpcUrls) ? params.rpcUrls : Object.values(params.rpcUrls)) : []),
|
||||
quorum_percentage: params.quorum_percentage,
|
||||
initial_partners: JSON.stringify(params.initial_partners || []),
|
||||
// initialAmounts в человекочитаемом формате, умножение на 1e18 происходит при деплое
|
||||
initial_amounts: JSON.stringify(params.initial_amounts || []),
|
||||
supported_chain_ids: JSON.stringify(params.supported_chain_ids || []),
|
||||
current_chain_id: params.current_chain_id || 1, // По умолчанию Ethereum
|
||||
logo_uri: params.logo_uri,
|
||||
private_key: params.private_key, // Будет автоматически зашифрован
|
||||
etherscan_api_key: params.etherscan_api_key,
|
||||
auto_verify_after_deploy: params.auto_verify_after_deploy || false,
|
||||
create2_salt: params.create2_salt,
|
||||
rpc_urls: JSON.stringify(params.rpc_urls ? (Array.isArray(params.rpc_urls) ? params.rpc_urls : Object.values(params.rpc_urls)) : []),
|
||||
initializer: params.initializer,
|
||||
dle_address: params.dleAddress,
|
||||
modules_to_deploy: JSON.stringify(params.modulesToDeploy || []),
|
||||
dle_address: params.dle_address,
|
||||
modules_to_deploy: JSON.stringify(params.modules_to_deploy || []),
|
||||
deployment_status: status
|
||||
};
|
||||
|
||||
@@ -89,6 +90,16 @@ class DeployParamsService {
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
logger.warn(`⚠️ Параметры деплоя не найдены: ${deploymentId}`);
|
||||
logger.warn(`🔍 Тип deploymentId: ${typeof deploymentId}`);
|
||||
logger.warn(`🔍 Значение deploymentId: "${deploymentId}"`);
|
||||
|
||||
// Попробуем найти все записи для отладки
|
||||
const allRecords = await encryptedDb.getData('deploy_params', {});
|
||||
logger.warn(`🔍 Всего записей в deploy_params: ${allRecords?.length || 0}`);
|
||||
if (allRecords && allRecords.length > 0) {
|
||||
logger.warn(`🔍 Последние deployment_id: ${allRecords.map(r => r.deployment_id).slice(-3).join(', ')}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -118,6 +129,33 @@ class DeployParamsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает параметры деплоя по адресу DLE
|
||||
* @param {string} dleAddress - Адрес DLE контракта
|
||||
* @returns {Promise<Object|null>} - Параметры деплоя или null
|
||||
*/
|
||||
async getDeployParamsByDleAddress(dleAddress) {
|
||||
try {
|
||||
logger.info(`📖 Поиск параметров деплоя по адресу DLE: ${dleAddress}`);
|
||||
|
||||
// Используем encryptedDb для поиска по адресу DLE
|
||||
const result = await encryptedDb.getData('deploy_params', {
|
||||
dle_address: dleAddress
|
||||
});
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
logger.warn(`⚠️ Параметры деплоя не найдены для адреса: ${dleAddress}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Возвращаем первый найденный результат
|
||||
return result[0];
|
||||
} catch (error) {
|
||||
logger.error(`❌ Ошибка при поиске параметров деплоя по адресу: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновляет статус деплоя
|
||||
* @param {string} deploymentId - Идентификатор деплоя
|
||||
@@ -125,25 +163,66 @@ class DeployParamsService {
|
||||
* @param {string} dleAddress - Адрес задеплоенного контракта
|
||||
* @returns {Promise<Object>} - Обновленные параметры
|
||||
*/
|
||||
async updateDeploymentStatus(deploymentId, status, dleAddress = null) {
|
||||
async updateDeploymentStatus(deploymentId, status, result = null) {
|
||||
try {
|
||||
logger.info(`🔄 Обновление статуса деплоя: ${deploymentId} -> ${status}`);
|
||||
|
||||
// Подготавливаем данные для обновления
|
||||
let dleAddress = null;
|
||||
let deployResult = null;
|
||||
|
||||
if (result) {
|
||||
logger.info(`🔍 [DEBUG] updateDeploymentStatus получил result:`, JSON.stringify(result, null, 2));
|
||||
|
||||
// Извлекаем адреса из результата деплоя
|
||||
if (result.data && result.data.networks && result.data.networks.length > 0) {
|
||||
// Берем первый адрес для обратной совместимости
|
||||
dleAddress = result.data.networks[0].address;
|
||||
logger.info(`✅ [DEBUG] Найден адрес в result.data.networks[0].address: ${dleAddress}`);
|
||||
} else if (result.networks && result.networks.length > 0) {
|
||||
// Берем первый адрес для обратной совместимости
|
||||
dleAddress = result.networks[0].address;
|
||||
logger.info(`✅ [DEBUG] Найден адрес в result.networks[0].address: ${dleAddress}`);
|
||||
} else if (result.output) {
|
||||
// Ищем адрес в тексте output - сначала пробуем найти JSON массив с адресами
|
||||
const jsonArrayMatch = result.output.match(/\[[\s\S]*?"address":\s*"(0x[a-fA-F0-9]{40})"[\s\S]*?\]/);
|
||||
if (jsonArrayMatch) {
|
||||
dleAddress = jsonArrayMatch[1];
|
||||
logger.info(`✅ [DEBUG] Найден адрес в JSON массиве result.output: ${dleAddress}`);
|
||||
} else {
|
||||
// Fallback: ищем адрес в тексте output (формат: "📍 Адрес: 0x...")
|
||||
const addressMatch = result.output.match(/📍 Адрес: (0x[a-fA-F0-9]{40})/);
|
||||
if (addressMatch) {
|
||||
dleAddress = addressMatch[1];
|
||||
logger.info(`✅ [DEBUG] Найден адрес в тексте result.output: ${dleAddress}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.warn(`⚠️ [DEBUG] Адрес не найден в результате деплоя`);
|
||||
}
|
||||
|
||||
// Сохраняем полный результат деплоя (включая все адреса всех сетей)
|
||||
deployResult = JSON.stringify(result);
|
||||
}
|
||||
|
||||
const query = `
|
||||
UPDATE deploy_params
|
||||
SET deployment_status = $2, dle_address = $3, updated_at = CURRENT_TIMESTAMP
|
||||
SET deployment_status = $2, dle_address = $3, deploy_result = $4, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE deployment_id = $1
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await this.pool.query(query, [deploymentId, status, dleAddress]);
|
||||
const queryResult = await this.pool.query(query, [deploymentId, status, dleAddress, deployResult]);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
if (queryResult.rows.length === 0) {
|
||||
throw new Error(`Параметры деплоя не найдены: ${deploymentId}`);
|
||||
}
|
||||
|
||||
logger.info(`✅ Статус деплоя обновлен: ${deploymentId} -> ${status}`);
|
||||
return result.rows[0];
|
||||
if (dleAddress) {
|
||||
logger.info(`📍 Адрес DLE контракта: ${dleAddress}`);
|
||||
}
|
||||
return queryResult.rows[0];
|
||||
} catch (error) {
|
||||
logger.error(`❌ Ошибка при обновлении статуса деплоя: ${error.message}`);
|
||||
throw error;
|
||||
@@ -212,6 +291,229 @@ class DeployParamsService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить контракты по chainId
|
||||
*/
|
||||
async getContractsByChainId(chainId) {
|
||||
try {
|
||||
console.log(`[DeployParamsService] Ищем контракты с current_chain_id: ${chainId}`);
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
deployment_id,
|
||||
name,
|
||||
dle_address,
|
||||
current_chain_id,
|
||||
supported_chain_ids,
|
||||
created_at
|
||||
FROM deploy_params
|
||||
WHERE current_chain_id = $1 AND dle_address IS NOT NULL
|
||||
ORDER BY created_at DESC
|
||||
`;
|
||||
|
||||
const result = await this.pool.query(query, [chainId]);
|
||||
|
||||
console.log(`[DeployParamsService] Найдено контрактов: ${result.rows.length}`);
|
||||
|
||||
return result.rows.map(row => ({
|
||||
deploymentId: row.deployment_id,
|
||||
name: row.name,
|
||||
dleAddress: row.dle_address,
|
||||
currentChainId: row.current_chain_id,
|
||||
supportedChainIds: row.supported_chain_ids,
|
||||
createdAt: row.created_at
|
||||
}));
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[DeployParamsService] Ошибка поиска контрактов по chainId:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает все деплои
|
||||
* @param {number} limit - Количество записей
|
||||
* @returns {Promise<Array>} - Список всех деплоев
|
||||
*/
|
||||
async getAllDeployments(limit = 50) {
|
||||
try {
|
||||
logger.info(`📋 Получение всех деплоев (лимит: ${limit})`);
|
||||
|
||||
// Используем encryptedDb для автоматического расшифрования
|
||||
const result = await encryptedDb.getData('deploy_params', {}, limit, 'created_at DESC');
|
||||
|
||||
return result.map(row => {
|
||||
// Парсим deployResult для извлечения адресов всех сетей
|
||||
let deployedNetworks = [];
|
||||
console.log(`🔍 [DEBUG] Processing deployment ${row.deployment_id}, deploy_result exists:`, !!row.deploy_result);
|
||||
console.log(`🔍 [DEBUG] deploy_result type:`, typeof row.deploy_result);
|
||||
if (row.deploy_result) {
|
||||
try {
|
||||
const deployResult = typeof row.deploy_result === 'string'
|
||||
? JSON.parse(row.deploy_result)
|
||||
: row.deploy_result;
|
||||
|
||||
console.log(`🔍 [DEBUG] deployResult keys:`, Object.keys(deployResult));
|
||||
console.log(`🔍 [DEBUG] deployResult.output exists:`, !!deployResult.output);
|
||||
console.log(`🔍 [DEBUG] deployResult.data exists:`, !!deployResult.data);
|
||||
console.log(`🔍 [DEBUG] deployResult.networks exists:`, !!deployResult.networks);
|
||||
if (deployResult.error) {
|
||||
console.log(`🔍 [DEBUG] deployResult.error:`, deployResult.error);
|
||||
}
|
||||
|
||||
// Функция для получения правильного названия сети
|
||||
const getNetworkName = (chainId) => {
|
||||
const networkNames = {
|
||||
1: 'Ethereum Mainnet',
|
||||
11155111: 'Sepolia',
|
||||
17000: 'Holesky',
|
||||
421614: 'Arbitrum Sepolia',
|
||||
84532: 'Base Sepolia',
|
||||
137: 'Polygon',
|
||||
56: 'BSC',
|
||||
42161: 'Arbitrum One'
|
||||
};
|
||||
return networkNames[chainId] || `Chain ${chainId}`;
|
||||
};
|
||||
|
||||
// Функция для загрузки ABI для конкретной сети
|
||||
const loadABIForNetwork = (chainId) => {
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const abiPath = path.join(__dirname, '../../../frontend/src/utils/dle-abi.js');
|
||||
|
||||
if (fs.existsSync(abiPath)) {
|
||||
const abiContent = fs.readFileSync(abiPath, 'utf8');
|
||||
// Используем более простое регулярное выражение
|
||||
const abiMatch = abiContent.match(/export const DLE_ABI = (\[[\s\S]*?\]);/);
|
||||
if (abiMatch) {
|
||||
// Попробуем исправить JSON, заменив проблемные символы
|
||||
let abiText = abiMatch[1];
|
||||
// Убираем лишние запятые в конце
|
||||
abiText = abiText.replace(/,(\s*[}\]])/g, '$1');
|
||||
try {
|
||||
return JSON.parse(abiText);
|
||||
} catch (parseError) {
|
||||
console.warn(`⚠️ Ошибка парсинга ABI JSON для сети ${chainId}:`, parseError.message);
|
||||
// Возвращаем пустой массив как fallback
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (abiError) {
|
||||
console.warn(`⚠️ Ошибка загрузки ABI для сети ${chainId}:`, abiError.message);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Извлекаем адреса из результата деплоя
|
||||
if (deployResult.data && deployResult.data.networks) {
|
||||
deployedNetworks = deployResult.data.networks.map(network => ({
|
||||
chainId: network.chainId,
|
||||
address: network.address,
|
||||
networkName: network.networkName || getNetworkName(network.chainId),
|
||||
abi: loadABIForNetwork(network.chainId) // ABI для каждой сети отдельно
|
||||
}));
|
||||
} else if (deployResult.networks) {
|
||||
deployedNetworks = deployResult.networks.map(network => ({
|
||||
chainId: network.chainId,
|
||||
address: network.address,
|
||||
networkName: network.networkName || getNetworkName(network.chainId),
|
||||
abi: loadABIForNetwork(network.chainId) // ABI для каждой сети отдельно
|
||||
}));
|
||||
} else if (deployResult.output) {
|
||||
console.log(`🔍 [DEBUG] Processing deployResult.output`);
|
||||
// Извлекаем адреса из текста output
|
||||
const output = deployResult.output;
|
||||
const addressMatches = output.match(/📍 Адрес: (0x[a-fA-F0-9]{40})/g);
|
||||
const chainIdMatches = output.match(/chainId: (\d+)/g);
|
||||
|
||||
// Альтернативный поиск по названиям сетей
|
||||
const networkMatches = output.match(/🔍 Верификация в сети (\w+) \(chainId: (\d+)\)/g);
|
||||
|
||||
console.log(`🔍 [DEBUG] addressMatches:`, addressMatches);
|
||||
console.log(`🔍 [DEBUG] chainIdMatches:`, chainIdMatches);
|
||||
console.log(`🔍 [DEBUG] networkMatches:`, networkMatches);
|
||||
|
||||
if (networkMatches && networkMatches.length > 0) {
|
||||
// Используем networkMatches для более точного парсинга
|
||||
deployedNetworks = networkMatches.map((match) => {
|
||||
const [, networkName, chainIdStr] = match.match(/🔍 Верификация в сети (\w+) \(chainId: (\d+)\)/);
|
||||
const chainId = parseInt(chainIdStr);
|
||||
|
||||
// Ищем адрес для этой сети в output
|
||||
const addressRegex = new RegExp(`🔍 Верификация в сети ${networkName} \\(chainId: ${chainId}\\)\\n📍 Адрес: (0x[a-fA-F0-9]{40})`);
|
||||
const addressMatch = output.match(addressRegex);
|
||||
const address = addressMatch ? addressMatch[1] : '0x0000000000000000000000000000000000000000';
|
||||
|
||||
return {
|
||||
chainId: chainId,
|
||||
address: address,
|
||||
networkName: getNetworkName(chainId),
|
||||
abi: loadABIForNetwork(chainId)
|
||||
};
|
||||
});
|
||||
console.log(`🔍 [DEBUG] deployedNetworks created from networkMatches:`, deployedNetworks);
|
||||
} else if (addressMatches && chainIdMatches) {
|
||||
deployedNetworks = addressMatches.map((match, index) => {
|
||||
const address = match.match(/📍 Адрес: (0x[a-fA-F0-9]{40})/)[1];
|
||||
const chainId = chainIdMatches[index] ? parseInt(chainIdMatches[index].match(/chainId: (\d+)/)[1]) : null;
|
||||
|
||||
return {
|
||||
chainId: chainId,
|
||||
address: address,
|
||||
networkName: chainId ? getNetworkName(chainId) : `Network ${index + 1}`,
|
||||
abi: loadABIForNetwork(chainId) // ABI для каждой сети отдельно
|
||||
};
|
||||
});
|
||||
console.log(`🔍 [DEBUG] deployedNetworks created:`, deployedNetworks);
|
||||
} else {
|
||||
console.log(`🔍 [DEBUG] No matches found - addressMatches:`, !!addressMatches, 'chainIdMatches:', !!chainIdMatches);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`⚠️ Ошибка парсинга deployResult для ${row.deployment_id}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
deploymentId: row.deployment_id,
|
||||
name: row.name,
|
||||
symbol: row.symbol,
|
||||
location: row.location,
|
||||
coordinates: row.coordinates,
|
||||
jurisdiction: row.jurisdiction,
|
||||
oktmo: row.oktmo,
|
||||
okvedCodes: row.okved_codes || [],
|
||||
kpp: row.kpp,
|
||||
quorumPercentage: row.quorum_percentage,
|
||||
initialPartners: row.initial_partners || [],
|
||||
initialAmounts: row.initial_amounts || [],
|
||||
supportedChainIds: row.supported_chain_ids || [],
|
||||
currentChainId: row.current_chain_id,
|
||||
logoURI: row.logo_uri,
|
||||
etherscanApiKey: row.etherscan_api_key,
|
||||
autoVerifyAfterDeploy: row.auto_verify_after_deploy,
|
||||
create2Salt: row.create2_salt,
|
||||
rpcUrls: row.rpc_urls || [],
|
||||
initializer: row.initializer,
|
||||
dleAddress: row.dle_address,
|
||||
modulesToDeploy: row.modules_to_deploy || [],
|
||||
deploymentStatus: row.deployment_status,
|
||||
deployResult: row.deploy_result,
|
||||
deployedNetworks: deployedNetworks, // Добавляем адреса всех сетей
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at
|
||||
};
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`❌ Ошибка при получении всех деплоев: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрывает соединение с базой данных
|
||||
*/
|
||||
|
||||
@@ -90,6 +90,11 @@ class DeploymentWebSocketService {
|
||||
this.clients.get(ws.dleAddress).delete(ws);
|
||||
if (this.clients.get(ws.dleAddress).size === 0) {
|
||||
this.clients.delete(ws.dleAddress);
|
||||
// Очищаем сессию деплоя если нет активных клиентов
|
||||
if (this.deploymentSessions.has(ws.dleAddress)) {
|
||||
console.log(`[DeploymentWS] Очистка сессии деплоя для DLE: ${ws.dleAddress}`);
|
||||
this.deploymentSessions.delete(ws.dleAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ class DLEV2Service {
|
||||
* @returns {Promise<Object>} - Результат создания DLE
|
||||
*/
|
||||
async createDLE(dleParams, deploymentId = null) {
|
||||
console.log("🔥 [DLEV2-SERVICE] ФУНКЦИЯ createDLE ВЫЗВАНА!");
|
||||
logger.info("🚀 Начало создания DLE v2 с параметрами:", dleParams);
|
||||
|
||||
try {
|
||||
@@ -46,7 +45,6 @@ class DLEV2Service {
|
||||
deploymentId = `deploy_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
||||
}
|
||||
|
||||
console.log(`🆔 Deployment ID: ${deploymentId}`);
|
||||
logger.info(`🆔 Deployment ID: ${deploymentId}`);
|
||||
|
||||
// WebSocket обновление: начало процесса
|
||||
@@ -58,21 +56,13 @@ class DLEV2Service {
|
||||
this.validateDLEParams(dleParams);
|
||||
|
||||
// Подготовка параметров для деплоя
|
||||
console.log('🔧 Подготавливаем параметры для деплоя...');
|
||||
logger.info('🔧 Подготавливаем параметры для деплоя...');
|
||||
|
||||
// Отладка: проверяем входные параметры
|
||||
console.log('🔍 ОТЛАДКА - Входные параметры:');
|
||||
console.log(' supportedChainIds:', JSON.stringify(dleParams.supportedChainIds, null, 2));
|
||||
console.log(' privateKey:', dleParams.privateKey ? '[ЕСТЬ]' : '[НЕТ]');
|
||||
console.log(' name:', dleParams.name);
|
||||
|
||||
const deployParams = this.prepareDeployParams(dleParams);
|
||||
console.log('✅ Параметры подготовлены:', JSON.stringify(deployParams, null, 2));
|
||||
logger.info('✅ Параметры подготовлены:', JSON.stringify(deployParams, null, 2));
|
||||
logger.info('✅ Параметры подготовлены');
|
||||
|
||||
// Сохраняем подготовленные параметры в базу данных
|
||||
logger.info(`💾 Сохранение подготовленных параметров деплоя в БД: ${deploymentId}`);
|
||||
logger.info(`💾 Сохранение параметров деплоя в БД: ${deploymentId}`);
|
||||
await this.deployParamsService.saveDeployParams(deploymentId, deployParams, 'pending');
|
||||
|
||||
// Вычисляем адрес инициализатора
|
||||
@@ -84,27 +74,17 @@ class DLEV2Service {
|
||||
logger.warn('Не удалось вычислить initializerAddress из приватного ключа:', e.message);
|
||||
}
|
||||
|
||||
// WebSocket обновление: генерация CREATE2_SALT
|
||||
// WebSocket обновление: подготовка к деплою
|
||||
if (deploymentId) {
|
||||
deploymentTracker.updateProgress(deploymentId, 'Генерация CREATE2 SALT', 10, 'Создаем уникальный идентификатор для детерминированного адреса');
|
||||
deploymentTracker.updateProgress(deploymentId, 'Подготовка к деплою', 10, 'Настраиваем параметры для детерминированного деплоя');
|
||||
}
|
||||
|
||||
// Генерируем одноразовый CREATE2_SALT
|
||||
const { createAndStoreNewCreate2Salt } = require('./secretStore');
|
||||
const { salt: create2Salt, key: saltKey } = await createAndStoreNewCreate2Salt({ label: deployParams.name || 'DLEv2' });
|
||||
logger.info(`CREATE2_SALT создан и сохранён: key=${saltKey}`);
|
||||
|
||||
// Обновляем параметры в базе данных с CREATE2_SALT
|
||||
console.log('💾 Обновляем параметры в базе данных с CREATE2_SALT...');
|
||||
logger.info('💾 Обновляем параметры в базе данных с CREATE2_SALT...');
|
||||
// Обновляем параметры в базе данных
|
||||
console.log('💾 Обновляем параметры в базе данных...');
|
||||
logger.info('💾 Обновляем параметры в базе данных...');
|
||||
|
||||
const updatedParams = {
|
||||
...deployParams,
|
||||
CREATE2_SALT: create2Salt
|
||||
};
|
||||
|
||||
await this.deployParamsService.saveDeployParams(deploymentId, updatedParams, 'in_progress');
|
||||
logger.info(`✅ Параметры обновлены в БД с CREATE2_SALT: ${create2Salt}`);
|
||||
await this.deployParamsService.saveDeployParams(deploymentId, deployParams, 'in_progress');
|
||||
logger.info(`✅ Параметры обновлены в БД для деплоя`);
|
||||
|
||||
// WebSocket обновление: поиск RPC URLs
|
||||
if (deploymentId) {
|
||||
@@ -153,6 +133,8 @@ class DLEV2Service {
|
||||
// Обновляем параметры в базе данных с RPC URLs и initializer
|
||||
const finalParams = {
|
||||
...updatedParams,
|
||||
// Сохраняем initialAmounts в человекочитаемом формате, умножение на 1e18 происходит при деплое
|
||||
initialAmounts: dleParams.initialAmounts,
|
||||
rpcUrls: rpcUrls, // Сохраняем как объект {chainId: url}
|
||||
rpc_urls: Object.values(rpcUrls), // Также сохраняем как массив для совместимости
|
||||
initializer: dleParams.privateKey ? new ethers.Wallet(dleParams.privateKey.startsWith('0x') ? dleParams.privateKey : `0x${dleParams.privateKey}`).address : "0x0000000000000000000000000000000000000000"
|
||||
@@ -203,7 +185,18 @@ class DLEV2Service {
|
||||
const result = this.extractDeployResult(deployResult.stdout, deployParams);
|
||||
|
||||
if (!result || !result.success) {
|
||||
throw new Error('Деплой не удался: ' + (result?.error || 'Неизвестная ошибка'));
|
||||
// Логируем детали ошибки для отладки
|
||||
logger.error('❌ Деплой не удался. Детали:');
|
||||
logger.error(`📋 stdout: ${deployResult.stdout}`);
|
||||
logger.error(`📋 stderr: ${deployResult.stderr}`);
|
||||
logger.error(`📋 exitCode: ${deployResult.exitCode}`);
|
||||
|
||||
// Извлекаем конкретную ошибку из результата
|
||||
const errorMessage = result?.error ||
|
||||
deployResult.stderr ||
|
||||
'Неизвестная ошибка';
|
||||
|
||||
throw new Error(`Деплой не удался: ${errorMessage}`);
|
||||
}
|
||||
|
||||
// Сохраняем данные DLE
|
||||
@@ -218,8 +211,11 @@ class DLEV2Service {
|
||||
|
||||
// Обновляем статус деплоя в базе данных
|
||||
if (deploymentId && result.data.dleAddress) {
|
||||
logger.info(`🔄 Обновляем адрес в БД: ${deploymentId} -> ${result.data.dleAddress}`);
|
||||
await this.deployParamsService.updateDeploymentStatus(deploymentId, 'completed', result.data.dleAddress);
|
||||
logger.info(`✅ Статус деплоя обновлен в БД: ${deploymentId} -> completed`);
|
||||
logger.info(`✅ Статус деплоя обновлен в БД: ${deploymentId} -> completed, адрес: ${result.data.dleAddress}`);
|
||||
} else {
|
||||
logger.warn(`⚠️ Не удалось обновить адрес в БД: deploymentId=${deploymentId}, dleAddress=${result.data?.dleAddress}`);
|
||||
}
|
||||
|
||||
// WebSocket обновление: финализация
|
||||
@@ -411,14 +407,18 @@ class DLEV2Service {
|
||||
* @returns {Object|null} - Результат деплоя
|
||||
*/
|
||||
extractDeployResult(stdout, deployParams = null) {
|
||||
logger.info(`🔍 Анализируем вывод деплоя (${stdout.length} символов)`);
|
||||
|
||||
// Ищем MULTICHAIN_DEPLOY_RESULT в выводе
|
||||
const resultMatch = stdout.match(/MULTICHAIN_DEPLOY_RESULT\s+(.+)/);
|
||||
|
||||
if (resultMatch) {
|
||||
try {
|
||||
const deployResults = JSON.parse(resultMatch[1]);
|
||||
logger.info(`📊 Результаты деплоя: ${JSON.stringify(deployResults, null, 2)}`);
|
||||
// Проверяем, что есть успешные деплои
|
||||
const successfulDeploys = deployResults.filter(r => r.address && r.address !== '0x0000000000000000000000000000000000000000');
|
||||
logger.info(`✅ Успешные деплои: ${successfulDeploys.length}, адреса: ${successfulDeploys.map(d => d.address).join(', ')}`);
|
||||
|
||||
if (successfulDeploys.length > 0) {
|
||||
return {
|
||||
@@ -442,6 +442,54 @@ class DLEV2Service {
|
||||
} catch (e) {
|
||||
logger.error('Ошибка парсинга JSON результата:', e);
|
||||
}
|
||||
} else {
|
||||
// Если MULTICHAIN_DEPLOY_RESULT не найден, ищем другие индикаторы успеха
|
||||
logger.warn('⚠️ MULTICHAIN_DEPLOY_RESULT не найден в выводе');
|
||||
|
||||
// Ищем индикаторы успешного деплоя
|
||||
const successIndicators = [
|
||||
'DLE deployment completed successfully',
|
||||
'SUCCESS: All DLE addresses are identical',
|
||||
'deployed at=',
|
||||
'deployment SUCCESS'
|
||||
];
|
||||
|
||||
const hasSuccessIndicator = successIndicators.some(indicator =>
|
||||
stdout.includes(indicator)
|
||||
);
|
||||
|
||||
if (hasSuccessIndicator) {
|
||||
logger.info('✅ Найден индикатор успешного деплоя');
|
||||
|
||||
// Ищем адреса контрактов в выводе
|
||||
const addressMatch = stdout.match(/deployed at=([0-9a-fA-Fx]+)/);
|
||||
if (addressMatch) {
|
||||
const contractAddress = addressMatch[1];
|
||||
logger.info(`✅ Найден адрес контракта: ${contractAddress}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
dleAddress: contractAddress,
|
||||
totalNetworks: 1,
|
||||
successfulNetworks: 1,
|
||||
// Добавляем данные из параметров деплоя
|
||||
name: deployParams?.name || 'Unknown',
|
||||
symbol: deployParams?.symbol || 'UNK',
|
||||
location: deployParams?.location || 'Не указан',
|
||||
coordinates: deployParams?.coordinates || '0,0',
|
||||
jurisdiction: deployParams?.jurisdiction || 0,
|
||||
quorumPercentage: deployParams?.quorumPercentage || 51,
|
||||
logoURI: deployParams?.logoURI || '/uploads/logos/default-token.svg'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Логируем последние строки вывода для отладки
|
||||
const lines = stdout.split('\n');
|
||||
const lastLines = lines.slice(-10).join('\n');
|
||||
logger.info(`📋 Последние строки вывода:\n${lastLines}`);
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
523
backend/services/unifiedDeploymentService.js
Normal file
523
backend/services/unifiedDeploymentService.js
Normal file
@@ -0,0 +1,523 @@
|
||||
/**
|
||||
* Единый сервис для управления деплоем DLE
|
||||
* Объединяет все операции с данными и деплоем
|
||||
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||
*/
|
||||
|
||||
const logger = require('../utils/logger');
|
||||
const DeployParamsService = require('./deployParamsService');
|
||||
const deploymentTracker = require('../utils/deploymentTracker');
|
||||
const { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const etherscanV2 = require('./etherscanV2VerificationService');
|
||||
const { getRpcUrlByChainId } = require('./rpcProviderService');
|
||||
const { ethers } = require('ethers');
|
||||
// Убираем прямой импорт broadcastDeploymentUpdate - используем только deploymentTracker
|
||||
|
||||
class UnifiedDeploymentService {
|
||||
constructor() {
|
||||
this.deployParamsService = new DeployParamsService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Создает новый деплой DLE с полным циклом
|
||||
* @param {Object} dleParams - Параметры DLE из формы
|
||||
* @param {string} deploymentId - ID деплоя (опционально)
|
||||
* @returns {Promise<Object>} - Результат деплоя
|
||||
*/
|
||||
async createDLE(dleParams, deploymentId = null) {
|
||||
try {
|
||||
// 1. Генерируем ID деплоя
|
||||
if (!deploymentId) {
|
||||
deploymentId = `deploy_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
||||
}
|
||||
|
||||
logger.info(`🚀 Начало создания DLE: ${deploymentId}`);
|
||||
|
||||
// 2. Валидируем параметры
|
||||
this.validateDLEParams(dleParams);
|
||||
|
||||
// 3. Подготавливаем параметры для деплоя
|
||||
const deployParams = await this.prepareDeployParams(dleParams);
|
||||
|
||||
// 4. Сохраняем в БД
|
||||
await this.deployParamsService.saveDeployParams(deploymentId, deployParams, 'pending');
|
||||
logger.info(`💾 Параметры сохранены в БД: ${deploymentId}`);
|
||||
|
||||
// 5. Запускаем деплой
|
||||
const result = await this.executeDeployment(deploymentId);
|
||||
|
||||
// 6. Сохраняем результат
|
||||
await this.deployParamsService.updateDeploymentStatus(deploymentId, 'completed', result);
|
||||
logger.info(`✅ Деплой завершен: ${deploymentId}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
deploymentId,
|
||||
data: result
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`❌ Ошибка деплоя ${deploymentId}:`, error);
|
||||
|
||||
// Обновляем статус на ошибку
|
||||
if (deploymentId) {
|
||||
await this.deployParamsService.updateDeploymentStatus(deploymentId, 'failed', { error: error.message });
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Валидирует параметры DLE
|
||||
* @param {Object} params - Параметры для валидации
|
||||
*/
|
||||
validateDLEParams(params) {
|
||||
const required = ['name', 'symbol', 'privateKey', 'supportedChainIds'];
|
||||
const missing = required.filter(field => !params[field]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
throw new Error(`Отсутствуют обязательные поля: ${missing.join(', ')}`);
|
||||
}
|
||||
|
||||
if (params.quorumPercentage < 1 || params.quorumPercentage > 100) {
|
||||
throw new Error('Кворум должен быть от 1 до 100 процентов');
|
||||
}
|
||||
|
||||
if (!params.initialPartners || params.initialPartners.length === 0) {
|
||||
throw new Error('Необходимо указать хотя бы одного партнера');
|
||||
}
|
||||
|
||||
if (!params.initialAmounts || params.initialAmounts.length === 0) {
|
||||
throw new Error('Необходимо указать начальные суммы для партнеров');
|
||||
}
|
||||
|
||||
if (params.initialPartners.length !== params.initialAmounts.length) {
|
||||
throw new Error('Количество партнеров должно совпадать с количеством сумм');
|
||||
}
|
||||
|
||||
if (!params.supportedChainIds || params.supportedChainIds.length === 0) {
|
||||
throw new Error('Необходимо указать поддерживаемые сети');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Подготавливает параметры для деплоя
|
||||
* @param {Object} dleParams - Исходные параметры
|
||||
* @returns {Promise<Object>} - Подготовленные параметры
|
||||
*/
|
||||
async prepareDeployParams(dleParams) {
|
||||
// Генерируем RPC URLs на основе supportedChainIds из базы данных
|
||||
const rpcUrls = await this.generateRpcUrls(dleParams.supportedChainIds || []);
|
||||
|
||||
return {
|
||||
name: dleParams.name,
|
||||
symbol: dleParams.symbol,
|
||||
location: dleParams.location || '',
|
||||
coordinates: dleParams.coordinates || '',
|
||||
jurisdiction: dleParams.jurisdiction || 1,
|
||||
oktmo: dleParams.oktmo || 45000000000,
|
||||
okved_codes: dleParams.okvedCodes || [],
|
||||
kpp: dleParams.kpp || 770101001,
|
||||
quorum_percentage: dleParams.quorumPercentage || 51,
|
||||
initial_partners: dleParams.initialPartners || [],
|
||||
// initialAmounts в человекочитаемом формате, умножение на 1e18 происходит при деплое
|
||||
initial_amounts: dleParams.initialAmounts || [],
|
||||
supported_chain_ids: dleParams.supportedChainIds || [],
|
||||
current_chain_id: 1, // Governance chain всегда Ethereum
|
||||
private_key: dleParams.privateKey,
|
||||
etherscan_api_key: dleParams.etherscanApiKey,
|
||||
logo_uri: dleParams.logoURI || '',
|
||||
create2_salt: dleParams.CREATE2_SALT || `0x${Math.random().toString(16).substring(2).padStart(64, '0')}`,
|
||||
auto_verify_after_deploy: dleParams.autoVerifyAfterDeploy || false,
|
||||
modules_to_deploy: dleParams.modulesToDeploy || [],
|
||||
rpc_urls: rpcUrls,
|
||||
deployment_status: 'pending'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерирует RPC URLs на основе chain IDs из базы данных
|
||||
* @param {Array} chainIds - Массив chain IDs
|
||||
* @returns {Promise<Array>} - Массив RPC URLs
|
||||
*/
|
||||
async generateRpcUrls(chainIds) {
|
||||
const { getRpcUrlByChainId } = require('./rpcProviderService');
|
||||
const rpcUrls = [];
|
||||
|
||||
for (const chainId of chainIds) {
|
||||
try {
|
||||
const rpcUrl = await getRpcUrlByChainId(chainId);
|
||||
if (rpcUrl) {
|
||||
rpcUrls.push(rpcUrl);
|
||||
logger.info(`[RPC_GEN] Найден RPC для chainId ${chainId}: ${rpcUrl}`);
|
||||
} else {
|
||||
logger.warn(`[RPC_GEN] RPC не найден для chainId ${chainId}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[RPC_GEN] Ошибка получения RPC для chainId ${chainId}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
return rpcUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполняет деплой контрактов
|
||||
* @param {string} deploymentId - ID деплоя
|
||||
* @returns {Promise<Object>} - Результат деплоя
|
||||
*/
|
||||
async executeDeployment(deploymentId) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const scriptPath = path.join(__dirname, '../scripts/deploy/deploy-multichain.js');
|
||||
|
||||
logger.info(`🚀 Запуск деплоя: ${scriptPath}`);
|
||||
|
||||
const child = spawn('npx', ['hardhat', 'run', scriptPath], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
env: {
|
||||
...process.env,
|
||||
DEPLOYMENT_ID: deploymentId
|
||||
},
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
stdout += output;
|
||||
logger.info(`[DEPLOY] ${output.trim()}`);
|
||||
|
||||
// Определяем этап процесса по содержимому вывода
|
||||
let progress = 50;
|
||||
let message = 'Деплой в процессе...';
|
||||
|
||||
if (output.includes('Генерация ABI файла')) {
|
||||
progress = 10;
|
||||
message = 'Генерация ABI файла...';
|
||||
} else if (output.includes('Генерация flattened контракта')) {
|
||||
progress = 20;
|
||||
message = 'Генерация flattened контракта...';
|
||||
} else if (output.includes('Compiled') && output.includes('Solidity files')) {
|
||||
progress = 30;
|
||||
message = 'Компиляция контрактов...';
|
||||
} else if (output.includes('Загружены параметры')) {
|
||||
progress = 40;
|
||||
message = 'Загрузка параметров деплоя...';
|
||||
} else if (output.includes('deploying DLE directly')) {
|
||||
progress = 60;
|
||||
message = 'Деплой контрактов в сети...';
|
||||
} else if (output.includes('Верификация в сети')) {
|
||||
progress = 80;
|
||||
message = 'Верификация контрактов...';
|
||||
}
|
||||
|
||||
// Отправляем WebSocket сообщение о прогрессе через deploymentTracker
|
||||
deploymentTracker.updateDeployment(deploymentId, {
|
||||
status: 'in_progress',
|
||||
progress: progress,
|
||||
message: message,
|
||||
output: output.trim()
|
||||
});
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
const output = data.toString();
|
||||
stderr += output;
|
||||
logger.error(`[DEPLOY ERROR] ${output.trim()}`);
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
try {
|
||||
const result = this.parseDeployResult(stdout);
|
||||
|
||||
// Сохраняем результат в БД
|
||||
this.deployParamsService.updateDeploymentStatus(deploymentId, 'completed', result)
|
||||
.then(() => {
|
||||
logger.info(`✅ Результат деплоя сохранен в БД: ${deploymentId}`);
|
||||
|
||||
// Отправляем WebSocket сообщение о завершении через deploymentTracker
|
||||
deploymentTracker.completeDeployment(deploymentId, result);
|
||||
|
||||
resolve(result);
|
||||
})
|
||||
.catch(dbError => {
|
||||
logger.error(`❌ Ошибка сохранения результата в БД: ${dbError.message}`);
|
||||
resolve(result); // Все равно возвращаем результат
|
||||
});
|
||||
} catch (error) {
|
||||
reject(new Error(`Ошибка парсинга результата: ${error.message}`));
|
||||
}
|
||||
} else {
|
||||
// Логируем детали ошибки для отладки
|
||||
logger.error(`❌ Деплой завершился с ошибкой (код ${code})`);
|
||||
logger.error(`📋 stdout: ${stdout}`);
|
||||
logger.error(`📋 stderr: ${stderr}`);
|
||||
|
||||
// Извлекаем конкретную ошибку из вывода
|
||||
const errorMessage = stderr || stdout || 'Неизвестная ошибка';
|
||||
|
||||
// Отправляем WebSocket сообщение об ошибке через deploymentTracker
|
||||
deploymentTracker.failDeployment(deploymentId, new Error(`Деплой завершился с ошибкой (код ${code}): ${errorMessage}`));
|
||||
|
||||
reject(new Error(`Деплой завершился с ошибкой (код ${code}): ${errorMessage}`));
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
reject(new Error(`Ошибка запуска деплоя: ${error.message}`));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Парсит результат деплоя из вывода скрипта
|
||||
* @param {string} stdout - Вывод скрипта
|
||||
* @returns {Object} - Структурированный результат
|
||||
*/
|
||||
parseDeployResult(stdout) {
|
||||
try {
|
||||
logger.info(`🔍 Анализируем вывод деплоя (${stdout.length} символов)`);
|
||||
|
||||
// Ищем MULTICHAIN_DEPLOY_RESULT в выводе
|
||||
const resultMatch = stdout.match(/MULTICHAIN_DEPLOY_RESULT\s+(.+)/);
|
||||
if (resultMatch) {
|
||||
const jsonStr = resultMatch[1].trim();
|
||||
const deployResults = JSON.parse(jsonStr);
|
||||
logger.info(`📊 Результаты деплоя: ${JSON.stringify(deployResults, null, 2)}`);
|
||||
|
||||
// Проверяем, что есть успешные деплои
|
||||
const successfulDeploys = deployResults.filter(r => r.address && r.address !== '0x0000000000000000000000000000000000000000' && !r.error);
|
||||
|
||||
if (successfulDeploys.length > 0) {
|
||||
const dleAddress = successfulDeploys[0].address;
|
||||
logger.info(`✅ DLE адрес: ${dleAddress}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
dleAddress: dleAddress,
|
||||
networks: deployResults.map(result => ({
|
||||
chainId: result.chainId,
|
||||
address: result.address,
|
||||
success: result.address && result.address !== '0x0000000000000000000000000000000000000000' && !result.error,
|
||||
error: result.error || null,
|
||||
verification: result.verification || 'pending'
|
||||
}))
|
||||
},
|
||||
message: `DLE успешно развернут в ${successfulDeploys.length} сетях`
|
||||
};
|
||||
} else {
|
||||
// Если нет успешных деплоев, но есть результаты, возвращаем их с ошибками
|
||||
const failedDeploys = deployResults.filter(r => r.error);
|
||||
logger.warn(`⚠️ Все деплои неудачны. Ошибки: ${failedDeploys.map(d => d.error).join(', ')}`);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
data: {
|
||||
networks: deployResults.map(result => ({
|
||||
chainId: result.chainId,
|
||||
address: result.address || null,
|
||||
success: false,
|
||||
error: result.error || 'Unknown error'
|
||||
}))
|
||||
},
|
||||
message: `Деплой неудачен во всех сетях. Ошибки: ${failedDeploys.map(d => d.error).join(', ')}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: создаем результат из текста
|
||||
return {
|
||||
success: true,
|
||||
message: 'Деплой выполнен успешно',
|
||||
output: stdout
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('❌ Ошибка парсинга результата деплоя:', error);
|
||||
throw new Error(`Не удалось распарсить результат деплоя: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает статус деплоя
|
||||
* @param {string} deploymentId - ID деплоя
|
||||
* @returns {Object} - Статус деплоя
|
||||
*/
|
||||
async getDeploymentStatus(deploymentId) {
|
||||
return await this.deployParamsService.getDeployParams(deploymentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает все деплои
|
||||
* @returns {Array} - Список деплоев
|
||||
*/
|
||||
async getAllDeployments() {
|
||||
return await this.deployParamsService.getAllDeployments();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает все DLE из файлов (для совместимости)
|
||||
* @returns {Array} - Список DLE
|
||||
*/
|
||||
getAllDLEs() {
|
||||
try {
|
||||
const dlesDir = path.join(__dirname, '../contracts-data/dles');
|
||||
if (!fs.existsSync(dlesDir)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(dlesDir);
|
||||
const dles = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (file.includes('dle-v2-') && file.endsWith('.json')) {
|
||||
const filePath = path.join(dlesDir, file);
|
||||
try {
|
||||
const dleData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
if (dleData.dleAddress) {
|
||||
dles.push(dleData);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Ошибка при чтении файла ${file}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dles;
|
||||
} catch (error) {
|
||||
logger.error('Ошибка при получении списка DLE:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Автоматическая верификация контрактов во всех сетях
|
||||
* @param {Object} params - Параметры верификации
|
||||
* @returns {Promise<Object>} - Результат верификации
|
||||
*/
|
||||
async autoVerifyAcrossChains({ deployParams, deployResult, apiKey }) {
|
||||
try {
|
||||
logger.info('🔍 Начинаем автоматическую верификацию контрактов');
|
||||
|
||||
if (!deployResult?.data?.networks) {
|
||||
throw new Error('Нет данных о сетях для верификации');
|
||||
}
|
||||
|
||||
const verificationResults = [];
|
||||
|
||||
for (const network of deployResult.data.networks) {
|
||||
try {
|
||||
logger.info(`🔍 Верификация в сети ${network.chainId}...`);
|
||||
|
||||
const result = await etherscanV2.verifyContract({
|
||||
contractAddress: network.dleAddress,
|
||||
chainId: network.chainId,
|
||||
deployParams,
|
||||
apiKey
|
||||
});
|
||||
|
||||
verificationResults.push({
|
||||
chainId: network.chainId,
|
||||
address: network.dleAddress,
|
||||
success: result.success,
|
||||
guid: result.guid,
|
||||
message: result.message
|
||||
});
|
||||
|
||||
logger.info(`✅ Верификация в сети ${network.chainId} завершена`);
|
||||
} catch (error) {
|
||||
logger.error(`❌ Ошибка верификации в сети ${network.chainId}:`, error);
|
||||
verificationResults.push({
|
||||
chainId: network.chainId,
|
||||
address: network.dleAddress,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results: verificationResults
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('❌ Ошибка автоматической верификации:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет балансы в указанных сетях
|
||||
* @param {Array} chainIds - Список ID сетей
|
||||
* @param {string} privateKey - Приватный ключ
|
||||
* @returns {Promise<Object>} - Результат проверки
|
||||
*/
|
||||
async checkBalances(chainIds, privateKey) {
|
||||
try {
|
||||
logger.info(`💰 Проверка балансов в ${chainIds.length} сетях`);
|
||||
|
||||
const wallet = new ethers.Wallet(privateKey);
|
||||
const results = [];
|
||||
|
||||
for (const chainId of chainIds) {
|
||||
try {
|
||||
const rpcUrl = await getRpcUrlByChainId(chainId);
|
||||
if (!rpcUrl) {
|
||||
results.push({
|
||||
chainId,
|
||||
success: false,
|
||||
error: `RPC URL не найден для сети ${chainId}`
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Убеждаемся, что rpcUrl - это строка
|
||||
const rpcUrlString = typeof rpcUrl === 'string' ? rpcUrl : rpcUrl.toString();
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrlString);
|
||||
const balance = await provider.getBalance(wallet.address);
|
||||
const balanceEth = ethers.formatEther(balance);
|
||||
|
||||
results.push({
|
||||
chainId,
|
||||
success: true,
|
||||
address: wallet.address,
|
||||
balance: balanceEth,
|
||||
balanceWei: balance.toString()
|
||||
});
|
||||
|
||||
logger.info(`💰 Сеть ${chainId}: ${balanceEth} ETH`);
|
||||
} catch (error) {
|
||||
logger.error(`❌ Ошибка проверки баланса в сети ${chainId}:`, error);
|
||||
results.push({
|
||||
chainId,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('❌ Ошибка проверки балансов:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрывает соединения
|
||||
*/
|
||||
async close() {
|
||||
await this.deployParamsService.close();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UnifiedDeploymentService;
|
||||
Reference in New Issue
Block a user