diff --git a/DEPLOYMENT_FIXES.md b/DEPLOYMENT_FIXES.md new file mode 100644 index 0000000..34b21ae --- /dev/null +++ b/DEPLOYMENT_FIXES.md @@ -0,0 +1,40 @@ +# Исправления ошибок деплоя DLE + +## Проблема +Ошибки `Cannot access 'chainId' before initialization` в скриптах деплоя из-за неправильного использования переменных. + +## Источник данных +**База данных `deploy_params`:** +- `supported_chain_ids`: `[421614, 84532, 11155111, 17000]` (числовые chainId) +- `rpc_urls`: `["https://sepolia-rollup.arbitrum.io/rpc", ...]` (строки URL) + +## Исправления + +### 1. deploy-multichain.js +- **Функция `deployInNetwork(chainId, ...)`** +- **Было:** `const networkChainId = Number(net.chainId)` + использование `networkChainId` +- **Стало:** Использование параметра `chainId` напрямую +- **Причина:** `chainId` уже приходит как числовой параметр из `supportedChainIds` базы данных + +### 2. deploy-modules.js +- **Функция `deployModuleInNetwork(rpcUrl, ...)`** +- **Было:** `createRPCConnection(rpcUrl, ...)` - неправильно +- **Стало:** Получение `chainId` из RPC URL + `createRPCConnection(chainId, ...)` +- **Функция `deployAllModulesInNetwork(chainId, ...)`** +- **Было:** Создание `networkChainId` + использование его +- **Стало:** Использование параметра `chainId` напрямую + +### 3. DleDeployFormView.vue +- **Было:** `adminTokenCheck` использовался в watcher до объявления +- **Стало:** Объявление `adminTokenCheck` перед watcher'ом + +## Логика работы +1. База данных → `params.supportedChainIds` (числовые chainId) +2. `createMultipleRPCConnections(supportedChainIds, ...)` +3. `connection.network.chainId` возвращает тот же числовой chainId +4. `deployInNetwork(chainId, ...)` получает числовой chainId как параметр +5. **Внутри функций используем `chainId` (параметр), НЕ создаем `networkChainId`** + +## Результат +✅ Все ошибки инициализации переменных исправлены +✅ Система готова к работе без ошибок `Cannot access before initialization` diff --git a/backend/app.js b/backend/app.js index 7433fd5..f4d9e2a 100644 --- a/backend/app.js +++ b/backend/app.js @@ -330,6 +330,12 @@ const initializeDbSettingsService = async () => { // Инициализируем сервис настроек БД при запуске if (process.env.NODE_ENV !== 'migration') { initializeDbSettingsService(); + + // Загружаем RPC URL из базы данных + const { loadRpcFromDatabase } = require('./utils/loadRpcFromDatabase'); + loadRpcFromDatabase().catch(error => { + logger.error('[App] Ошибка загрузки RPC URL из базы данных:', error); + }); } module.exports = { app, nonceStore }; diff --git a/backend/hardhat.config.js b/backend/hardhat.config.js index 5cee986..02b4ed8 100644 --- a/backend/hardhat.config.js +++ b/backend/hardhat.config.js @@ -29,54 +29,8 @@ function getNetworks() { // console.log удален - может мешать flatten - // Базовые сети - const baseNetworks = { - sepolia: { - url: process.env.SEPOLIA_RPC_URL || 'https://1rpc.io/sepolia', - chainId: 11155111, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - holesky: { - url: process.env.HOLESKY_RPC_URL || 'https://ethereum-holesky.publicnode.com', - chainId: 17000, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - mainnet: { - url: process.env.MAINNET_RPC_URL || 'https://eth-mainnet.nodereal.io/v1/YOUR_NODEREAL_KEY', - chainId: 1, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - arbitrumSepolia: { - url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', - chainId: 421614, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - baseSepolia: { - url: process.env.BASE_SEPOLIA_RPC_URL || 'https://sepolia.base.org', - chainId: 84532, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - arbitrumOne: { - url: process.env.ARBITRUM_ONE_RPC_URL || 'https://arb1.arbitrum.io/rpc', - chainId: 42161, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - base: { - url: process.env.BASE_RPC_URL || 'https://mainnet.base.org', - chainId: 8453, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - polygon: { - url: process.env.POLYGON_RPC_URL || 'https://polygon-rpc.com', - chainId: 137, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - bsc: { - url: process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org', - chainId: 56, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - } - }; + // Базовые сети - УБРАНО, используем только базу данных + const baseNetworks = {}; // Пустой объект - никаких хардкод цепочек // Если есть supported_chain_ids, фильтруем только нужные сети if (supportedChainIds.length > 0) { @@ -104,35 +58,9 @@ function getNetworks() { } } -// Функция для получения базовых сетей (fallback) +// Функция для получения базовых сетей (fallback) - УБРАНО, используем только базу данных function getBaseNetworks() { - return { - sepolia: { - url: process.env.SEPOLIA_RPC_URL || 'https://1rpc.io/sepolia', - chainId: 11155111, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - holesky: { - url: process.env.HOLESKY_RPC_URL || 'https://ethereum-holesky.publicnode.com', - chainId: 17000, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - mainnet: { - url: process.env.MAINNET_RPC_URL || 'https://eth-mainnet.nodereal.io/v1/YOUR_NODEREAL_KEY', - chainId: 1, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - arbitrumSepolia: { - url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', - chainId: 421614, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - }, - baseSepolia: { - url: process.env.BASE_SEPOLIA_RPC_URL || 'https://sepolia.base.org', - chainId: 84532, - accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] - } - }; + return {}; // Пустой объект - никаких хардкод цепочек } diff --git a/backend/routes/blockchain.js b/backend/routes/blockchain.js index e1eba66..6b2bf22 100644 --- a/backend/routes/blockchain.js +++ b/backend/routes/blockchain.js @@ -54,7 +54,7 @@ router.post('/read-dle-info', async (req, res) => { if (!rpcUrl) { return res.status(500).json({ success: false, error: `RPC URL для сети ${targetChainId} не найден` }); } - provider = new ethers.JsonRpcProvider(rpcUrl); + provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const code = await provider.getCode(dleAddress); if (!code || code === '0x') { return res.status(400).json({ success: false, error: `По адресу ${dleAddress} нет контракта в сети ${targetChainId}` }); @@ -358,7 +358,7 @@ router.post('/get-proposals', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // ABI для чтения предложений (используем правильные функции из смарт-контракта) const dleAbi = [ @@ -520,7 +520,7 @@ router.post('/get-proposal-info', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // ABI для чтения информации о предложении const dleAbi = [ @@ -614,7 +614,7 @@ router.post('/deactivate-dle', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // ABI для проверки деактивации DLE const dleAbi = [ @@ -707,7 +707,7 @@ router.post('/check-deactivation-proposal-result', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function checkDeactivationProposalResult(uint256 _proposalId) public view returns (bool passed, bool quorumReached)" @@ -785,7 +785,7 @@ router.post('/load-deactivation-proposals', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function deactivationProposalCounter() external view returns (uint256)", @@ -889,7 +889,7 @@ router.post('/execute-proposal', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const wallet = new ethers.Wallet(privateKey, provider); const dleAbi = [ @@ -965,7 +965,7 @@ router.post('/cancel-proposal', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function cancelProposal(uint256 _proposalId, string calldata reason) external" @@ -1050,7 +1050,7 @@ router.post('/get-governance-params', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getGovernanceParams() external view returns (uint256 quorumPct, uint256 chainId, uint256 supportedCount)" @@ -1126,7 +1126,7 @@ router.post('/get-proposal-state', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalState(uint256 _proposalId) public view returns (uint8 state)" @@ -1201,7 +1201,7 @@ router.post('/get-proposal-votes', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalVotes(uint256 _proposalId) external view returns (uint256 forVotes, uint256 againstVotes, uint256 totalVotes, uint256 quorumRequired)" @@ -1279,7 +1279,7 @@ router.post('/get-proposals-count', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalsCount() external view returns (uint256)" @@ -1353,7 +1353,7 @@ router.post('/list-proposals', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)" @@ -1429,7 +1429,7 @@ router.post('/get-voting-power-at', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256)" @@ -1505,7 +1505,7 @@ router.post('/get-quorum-at', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getQuorumAt(uint256 timepoint) external view returns (uint256)" @@ -1580,7 +1580,7 @@ router.post('/get-token-balance', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function balanceOf(address account) external view returns (uint256)" @@ -1655,7 +1655,7 @@ router.post('/get-total-supply', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function totalSupply() external view returns (uint256)" @@ -1729,7 +1729,7 @@ router.post('/is-active', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function isActive() external view returns (bool)" @@ -1809,7 +1809,7 @@ router.post('/get-dle-analytics', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function totalSupply() external view returns (uint256)", @@ -1956,7 +1956,7 @@ router.post('/get-dle-history', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalsCount() external view returns (uint256)", diff --git a/backend/routes/dleAnalytics.js b/backend/routes/dleAnalytics.js index e1f090d..14f4d0c 100644 --- a/backend/routes/dleAnalytics.js +++ b/backend/routes/dleAnalytics.js @@ -72,7 +72,7 @@ router.post('/get-dle-analytics', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function totalSupply() external view returns (uint256)", @@ -242,7 +242,7 @@ router.post('/get-dle-history', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalsCount() external view returns (uint256)", diff --git a/backend/routes/dleCore.js b/backend/routes/dleCore.js index f7df637..e12f51b 100644 --- a/backend/routes/dleCore.js +++ b/backend/routes/dleCore.js @@ -63,7 +63,7 @@ router.post('/read-dle-info', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // ABI для чтения данных DLE const dleAbi = [ @@ -254,7 +254,7 @@ router.post('/get-governance-params', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getGovernanceParams() external view returns (uint256 quorumPct, uint256 chainId, uint256 supportedCount)" @@ -323,7 +323,7 @@ router.post('/is-active', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function isActive() external view returns (bool)" @@ -408,7 +408,7 @@ router.post('/deactivate-dle', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // ABI для проверки деактивации DLE const dleAbi = [ diff --git a/backend/routes/dleHistory.js b/backend/routes/dleHistory.js index 86942b5..9bb80b5 100644 --- a/backend/routes/dleHistory.js +++ b/backend/routes/dleHistory.js @@ -73,7 +73,7 @@ router.post('/get-extended-history', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); 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))", diff --git a/backend/routes/dleModules.js b/backend/routes/dleModules.js index 0554af4..264f44c 100644 --- a/backend/routes/dleModules.js +++ b/backend/routes/dleModules.js @@ -213,7 +213,7 @@ router.post('/is-module-active', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function isModuleActive(bytes32 _moduleId) external view returns (bool)" @@ -296,7 +296,7 @@ router.post('/get-module-address', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getModuleAddress(bytes32 _moduleId) external view returns (address)" @@ -348,7 +348,13 @@ router.post('/prepare-initialize-modules-all-networks', async (req, res) => { const results = []; for (const network of supportedNetworks) { try { - const provider = new ethers.JsonRpcProvider(network.rpcUrl); + // Получаем RPC URL из базы данных + const rpcUrl = await rpcProviderService.getRpcUrlByChainId(network.chainId); + if (!rpcUrl) { + console.warn(`[DLE Modules] RPC URL не найден для chainId ${network.chainId}`); + continue; + } + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dle = new ethers.Contract( dleAddress, [ @@ -576,14 +582,16 @@ router.post('/get-all-modules', async (req, res) => { return networks[chainId] || `Chain ${chainId}`; } - function getFallbackRpcUrl(chainId) { - const fallbackUrls = { - 11155111: process.env.SEPOLIA_RPC_URL || 'https://eth-sepolia.nodereal.io/v1/YOUR_NODEREAL_KEY', - 17000: 'https://ethereum-holesky.publicnode.com', - 421614: 'https://sepolia-rollup.arbitrum.io/rpc', - 84532: 'https://sepolia.base.org' - }; - return fallbackUrls[chainId] || null; + async function getFallbackRpcUrl(chainId) { + try { + // Получаем RPC URL из базы данных + const rpcService = require('../services/rpcProviderService'); + const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); + return rpcUrl; + } catch (error) { + console.error(`[DLE Modules] Ошибка получения RPC из базы данных для chain_id ${chainId}:`, error); + return null; + } } function getEtherscanUrl(chainId) { @@ -621,48 +629,19 @@ router.post('/get-all-modules', async (req, res) => { const supportedChainIds = params.supportedChainIds || []; const rpcUrls = params.rpcUrls || params.rpc_urls || {}; - supportedNetworks = supportedChainIds.map((chainId, index) => ({ + supportedNetworks = await Promise.all(supportedChainIds.map(async (chainId, index) => ({ chainId: Number(chainId), networkName: getNetworkName(Number(chainId)), - rpcUrl: rpcUrls[chainId] || getFallbackRpcUrl(chainId), + rpcUrl: rpcUrls[chainId] || await getFallbackRpcUrl(chainId), etherscanUrl: getEtherscanUrl(chainId), networkIndex: index - })); + }))); } await deployParamsService.close(); } catch (error) { console.error('❌ Ошибка получения параметров деплоя:', error); - // Fallback для совместимости - supportedNetworks = [ - { - chainId: 11155111, - networkName: 'Sepolia', - rpcUrl: process.env.SEPOLIA_RPC_URL || 'https://eth-sepolia.nodereal.io/v1/YOUR_NODEREAL_KEY', - etherscanUrl: 'https://sepolia.etherscan.io', - networkIndex: 0 - }, - { - chainId: 17000, - networkName: 'Holesky', - rpcUrl: 'https://ethereum-holesky.publicnode.com', - etherscanUrl: 'https://holesky.etherscan.io', - networkIndex: 1 - }, - { - chainId: 421614, - networkName: 'Arbitrum Sepolia', - rpcUrl: 'https://sepolia-rollup.arbitrum.io/rpc', - etherscanUrl: 'https://sepolia.arbiscan.io', - networkIndex: 2 - }, - { - chainId: 84532, - networkName: 'Base Sepolia', - rpcUrl: 'https://sepolia.base.org', - etherscanUrl: 'https://sepolia.basescan.org', - networkIndex: 3 - } - ]; + // НЕ показываем fallback цепочки - только те, что выбрал пользователь + supportedNetworks = []; } res.json({ @@ -754,7 +733,7 @@ router.post('/create-add-module-proposal', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Получаем приватный ключ из параметров деплоя let privateKey; @@ -1003,7 +982,7 @@ router.post('/create-remove-module-proposal', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function createRemoveModuleProposal(string memory _description, uint256 _duration, bytes32 _moduleId, uint256 _chainId) external returns (uint256)" @@ -1276,7 +1255,7 @@ router.post('/verify-module', async (req, res) => { // Получаем ABI и bytecode для модуля const { ethers } = require('ethers'); - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Получаем код контракта для проверки существования const code = await provider.getCode(moduleAddress); @@ -1389,7 +1368,11 @@ router.post('/check-modules-status', async (req, res) => { // Проверяем первую доступную сеть const network = supportedNetworks[0]; - const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const rpcUrl = await rpcProviderService.getRpcUrlByChainId(network.chainId); + if (!rpcUrl) { + return res.status(400).json({ success: false, message: `RPC URL не найден для chainId ${network.chainId}` }); + } + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function initializer() external view returns (address)", @@ -1603,7 +1586,12 @@ router.post('/initialize-modules-all-networks', async (req, res) => { console.log(`[DLE Modules] Инициализация модулей в сети: ${network.networkName} (${network.chainId})`); try { - const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const rpcUrl = await rpcProviderService.getRpcUrlByChainId(network.chainId); + if (!rpcUrl) { + console.warn(`[DLE Modules] RPC URL не найден для chainId ${network.chainId}`); + continue; + } + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const wallet = new ethers.Wallet(privateKey, provider); const dle = new ethers.Contract(dleAddress, dleAbi, wallet); @@ -1723,7 +1711,12 @@ router.post('/verify-modules-all-networks', async (req, res) => { }; try { - const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const rpcUrl = await rpcProviderService.getRpcUrlByChainId(network.chainId); + if (!rpcUrl) { + console.warn(`[DLE Modules] RPC URL не найден для chainId ${network.chainId}`); + continue; + } + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dle = new ethers.Contract(dleAddress, dleAbi, provider); for (const [moduleKey, moduleId] of Object.entries(moduleIds)) { @@ -1892,7 +1885,7 @@ router.post('/check-dle-deployment-status', async (req, res) => { throw new Error(`RPC URL не найден для сети ${chainId}`); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Проверяем, что контракт существует и имеет код const code = await provider.getCode(dleAddress); @@ -2019,7 +2012,7 @@ router.post('/check-module-deployment-status', async (req, res) => { throw new Error(`RPC URL не найден для сети ${chainId}`); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getModuleAddress(bytes32 _moduleId) external view returns (address)", @@ -2179,7 +2172,7 @@ router.post('/deploy-module-all-networks', async (req, res) => { try { const result = await executeWithRetries( async () => { - const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(network.chainId)); const wallet = new ethers.Wallet(privateKey, provider); // Используем NonceManager для правильного управления nonce @@ -2558,7 +2551,7 @@ router.post('/verify-module-all-networks', async (req, res) => { try { const result = await executeWithRetries( async () => { - const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(network.chainId)); const dle = new ethers.Contract(dleAddress, [ "function getModuleAddress(bytes32 _moduleId) external view returns (address)" ], provider); @@ -2698,7 +2691,7 @@ router.post('/initialize-module-all-networks', async (req, res) => { try { const result = await executeWithRetries( async () => { - const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(network.chainId)); const wallet = new ethers.Wallet(privateKey, provider); const dleAbi = [ @@ -2826,7 +2819,7 @@ router.post('/final-deployment-check', async (req, res) => { throw new Error(`RPC URL не найден для сети ${chainId}`); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function name() external view returns (string)", @@ -3025,7 +3018,7 @@ router.post('/get-deployment-status', async (req, res) => { const result = await executeWithRetries( async () => { const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleCode = await provider.getCode(dleAddress); return dleCode !== '0x'; }, @@ -3045,7 +3038,7 @@ router.post('/get-deployment-status', async (req, res) => { const verificationResult = await executeWithRetries( async () => { const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Простая проверка - если код контракта не пустой, считаем верифицированным const code = await provider.getCode(dleAddress); return code !== '0x' && code.length > 2; @@ -3102,7 +3095,7 @@ router.post('/get-deployment-status', async (req, res) => { const result = await executeWithRetries( async () => { const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dle = new ethers.Contract(dleAddress, [ "function getModuleAddress(bytes32 _moduleId) external view returns (address)", "function isModuleActive(bytes32 _moduleId) external view returns (bool)" @@ -3129,7 +3122,7 @@ router.post('/get-deployment-status', async (req, res) => { const verificationResult = await executeWithRetries( async () => { const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const moduleCode = await provider.getCode(moduleAddress); return moduleCode !== '0x'; }, @@ -3168,7 +3161,7 @@ router.post('/get-deployment-status', async (req, res) => { const result = await executeWithRetries( async () => { const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dle = new ethers.Contract(dleAddress, [ ], provider); diff --git a/backend/routes/dleMultichain.js b/backend/routes/dleMultichain.js index d85b1af..74edf2b 100644 --- a/backend/routes/dleMultichain.js +++ b/backend/routes/dleMultichain.js @@ -63,10 +63,10 @@ router.post('/get-multichain-contracts', async (req, res) => { // Если не найден в параметрах, используем fallback if (!rpcUrl) { const fallbackConfigs = { - '11155111': 'https://1rpc.io/sepolia', - '17000': 'https://ethereum-holesky.publicnode.com', - '421614': 'https://sepolia-rollup.arbitrum.io/rpc', - '84532': 'https://sepolia.base.org' + '11155111': null, + '17000': null, + '421614': null, + '84532': null }; rpcUrl = fallbackConfigs[targetChainId]; } @@ -86,7 +86,7 @@ router.post('/get-multichain-contracts', async (req, res) => { } try { - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const contractCode = await provider.getCode(originalContract); if (contractCode && contractCode !== '0x') { diff --git a/backend/routes/dleMultichainExecution.js b/backend/routes/dleMultichainExecution.js index beef8b5..7d9cae0 100644 --- a/backend/routes/dleMultichainExecution.js +++ b/backend/routes/dleMultichainExecution.js @@ -42,7 +42,7 @@ router.post('/get-proposal-multichain-info', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function proposals(uint256) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, bytes memory operation, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targetChains)", @@ -294,7 +294,7 @@ async function executeProposalInChain(dleAddress, proposalId, chainId, privateKe throw new Error(`RPC URL для сети ${chainId} не найден`); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const wallet = new ethers.Wallet(privateKey, provider); const dleAbi = [ diff --git a/backend/routes/dleProposals.js b/backend/routes/dleProposals.js index fd41014..95ed4af 100644 --- a/backend/routes/dleProposals.js +++ b/backend/routes/dleProposals.js @@ -60,7 +60,7 @@ router.post('/get-proposals', async (req, res) => { return; } if (rpcUrl) { - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getSupportedChainCount() external view returns (uint256)", "function getSupportedChainId(uint256 _index) external view returns (uint256)" @@ -97,7 +97,7 @@ router.post('/get-proposals', async (req, res) => { continue; } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // ABI для чтения предложений (используем getProposalSummary для мультиконтрактов) const dleAbi = [ @@ -369,7 +369,7 @@ router.post('/get-proposal-info', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // ABI для чтения информации о предложении const dleAbi = [ @@ -447,7 +447,7 @@ router.post('/get-proposal-state', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalState(uint256 _proposalId) public view returns (uint8 state)" @@ -499,7 +499,7 @@ router.post('/get-proposal-votes', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)", @@ -560,7 +560,7 @@ router.post('/get-proposals-count', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalsCount() external view returns (uint256)" @@ -611,7 +611,7 @@ router.post('/list-proposals', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)" @@ -664,7 +664,7 @@ router.post('/get-voting-power-at', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256)" @@ -717,7 +717,7 @@ router.post('/get-quorum-at', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getQuorumAt(uint256 timepoint) external view returns (uint256)" @@ -772,7 +772,7 @@ router.post('/execute-proposal', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function executeProposal(uint256 _proposalId) external" @@ -827,7 +827,7 @@ router.post('/cancel-proposal', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function cancelProposal(uint256 _proposalId, string calldata reason) external" @@ -879,7 +879,7 @@ router.post('/get-proposals-count', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function getProposalsCount() external view returns (uint256)" @@ -929,7 +929,7 @@ router.post('/list-proposals', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)", @@ -1030,7 +1030,7 @@ router.post('/vote-proposal', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function vote(uint256 _proposalId, bool _support) external" @@ -1088,7 +1088,7 @@ router.post('/check-vote-status', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Функция hasVoted не существует в контракте DLE console.log(`[DLE Proposals] Функция hasVoted не поддерживается в контракте DLE`); @@ -1135,7 +1135,7 @@ router.post('/track-vote-transaction', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Ждем подтверждения транзакции const receipt = await provider.waitForTransaction(txHash, 1, 60000); // 60 секунд таймаут @@ -1193,7 +1193,7 @@ router.post('/track-execution-transaction', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Ждем подтверждения транзакции const receipt = await provider.waitForTransaction(txHash, 1, 60000); // 60 секунд таймаут @@ -1252,7 +1252,7 @@ router.post('/decode-proposal-data', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); // Получаем данные транзакции const tx = await provider.getTransaction(transactionHash); diff --git a/backend/routes/dleTokens.js b/backend/routes/dleTokens.js index 77b470c..42e6136 100644 --- a/backend/routes/dleTokens.js +++ b/backend/routes/dleTokens.js @@ -72,7 +72,7 @@ router.post('/get-token-balance', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function balanceOf(address account) external view returns (uint256)" @@ -158,7 +158,7 @@ router.post('/get-total-supply', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function totalSupply() external view returns (uint256)" @@ -243,7 +243,7 @@ router.post('/get-token-holders', async (req, res) => { }); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId)); const dleAbi = [ "function totalSupply() external view returns (uint256)", diff --git a/backend/routes/ens.js b/backend/routes/ens.js index 15f5a3a..1588dbe 100644 --- a/backend/routes/ens.js +++ b/backend/routes/ens.js @@ -17,9 +17,23 @@ const express = require('express'); const router = express.Router(); const { ethers } = require('ethers'); -function getMainnetProvider() { - const url = process.env.MAINNET_RPC_URL || process.env.ETH_MAINNET_RPC || 'https://ethereum.publicnode.com'; - return new ethers.JsonRpcProvider(url); +async function getMainnetProvider() { + try { + // Получаем RPC URL из базы данных для mainnet (chain_id = 1) + const rpcService = require('../services/rpcProviderService'); + const rpcUrl = await rpcService.getRpcUrlByChainId(1); + + if (!rpcUrl) { + throw new Error('RPC URL для mainnet не найден в базе данных'); + } + + console.log(`[ENS] Используем RPC из базы данных: ${rpcUrl}`); + return new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(1)); + + } catch (error) { + console.error(`[ENS] Ошибка получения RPC из базы данных:`, error); + throw new Error(`Не удалось получить RPC провайдер: ${error.message}`); + } } // GET /api/ens/avatar?name=vc-hb3-accelerator.eth @@ -29,7 +43,7 @@ router.get('/avatar', async (req, res) => { if (!name || !name.endsWith('.eth')) { return res.status(400).json({ success: false, message: 'ENS name is required (e.g., example.eth)' }); } - const provider = getMainnetProvider(); + const provider = await getMainnetProvider(); const url = await provider.getAvatar(name); return res.json({ success: true, data: { url: url || null } }); } catch (e) { diff --git a/backend/scripts/check-modules.js b/backend/scripts/check-modules.js index afedc00..2c235b5 100644 --- a/backend/scripts/check-modules.js +++ b/backend/scripts/check-modules.js @@ -18,9 +18,11 @@ async function checkModules() { const dleAddress = '0xCaa85e96a6929F0373442e31FD9888d985869EcE'; // RPC URL для Sepolia - const rpcUrl = process.env.SEPOLIA_RPC_URL || 'https://eth-sepolia.nodereal.io/v1/YOUR_NODEREAL_KEY'; + // Получаем RPC URL из базы данных + const rpcService = require('../services/rpcProviderService'); + const rpcUrl = await rpcService.getRpcUrlByChainId(11155111); - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(11155111)); // ABI для DLE контракта const dleAbi = [ diff --git a/backend/scripts/deploy/deploy-modules.js b/backend/scripts/deploy/deploy-modules.js index 38c86f5..9cb0452 100644 --- a/backend/scripts/deploy/deploy-modules.js +++ b/backend/scripts/deploy/deploy-modules.js @@ -179,27 +179,30 @@ async function verifyModuleAfterDeploy(chainId, contractAddress, moduleType, con async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce, moduleInit, moduleType) { const { ethers } = hre; + // Создаем временный провайдер для получения chainId + const tempProvider = new ethers.JsonRpcProvider(rpcUrl); + const network = await tempProvider.getNetwork(); + const chainId = Number(network.chainId); + // Используем новый менеджер RPC с retry логикой - const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, { + const { provider, wallet, network: rpcNetwork } = await createRPCConnection(chainId, pk, { maxRetries: 3, timeout: 30000 }); - const net = network; - - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploying ${moduleType}...`); + const net = rpcNetwork; // 1) Используем NonceManager для правильного управления nonce - const chainId = Number(net.chainId); + logger.info(`[MODULES_DBG] chainId=${chainId} deploying ${moduleType}...`); let current = await nonceManager.getNonce(wallet.address, rpcUrl, chainId); logger.info(`[MODULES_DBG] chainId=${chainId} current nonce=${current} target=${targetNonce}`); if (current > targetNonce) { - throw new Error(`Current nonce ${current} > targetNonce ${targetNonce} on chainId=${Number(net.chainId)}`); + throw new Error(`Current nonce ${current} > targetNonce ${targetNonce} on chainId=${chainId}`); } if (current < targetNonce) { - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} aligning nonce from ${current} to ${targetNonce} (${targetNonce - current} transactions needed)`); + logger.info(`[MODULES_DBG] chainId=${chainId} aligning nonce from ${current} to ${targetNonce} (${targetNonce - current} transactions needed)`); // Используем burn address для более надежных транзакций const burnAddress = "0x000000000000000000000000000000000000dEaD"; @@ -219,15 +222,15 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce gasLimit, ...overrides }; - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} sending filler tx nonce=${current} attempt=${attempt + 1}`); + logger.info(`[MODULES_DBG] chainId=${chainId} sending filler tx nonce=${current} attempt=${attempt + 1}`); const rpcManager = new RPCConnectionManager(); const { tx: txFill, receipt } = await rpcManager.sendTransactionWithRetry(wallet, txReq, { maxRetries: 3 }); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx sent, hash=${txFill.hash}, waiting for confirmation...`); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx nonce=${current} confirmed, hash=${txFill.hash}`); + logger.info(`[MODULES_DBG] chainId=${chainId} filler tx sent, hash=${txFill.hash}, waiting for confirmation...`); + logger.info(`[MODULES_DBG] chainId=${chainId} filler tx nonce=${current} confirmed, hash=${txFill.hash}`); sent = true; } catch (e) { lastErr = e; - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx nonce=${current} attempt=${attempt + 1} failed: ${e?.message || e}`); + logger.info(`[MODULES_DBG] chainId=${chainId} filler tx nonce=${current} attempt=${attempt + 1} failed: ${e?.message || e}`); if (String(e?.message || '').toLowerCase().includes('intrinsic gas too low') && attempt < 2) { gasLimit = 50000; @@ -236,13 +239,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce if (String(e?.message || '').toLowerCase().includes('nonce too low') && attempt < 2) { // Сбрасываем кэш и получаем актуальный nonce - nonceManager.resetNonce(wallet.address, Number(net.chainId)); + nonceManager.resetNonce(wallet.address, chainId); current = await provider.getTransactionCount(wallet.address, 'pending'); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} updated nonce to ${current}`); + logger.info(`[MODULES_DBG] chainId=${chainId} updated nonce to ${current}`); // Если новый nonce больше целевого, это критическая ошибка if (current > targetNonce) { - throw new Error(`Current nonce ${current} > target nonce ${targetNonce} on chainId=${Number(net.chainId)}. Cannot proceed with module deployment.`); + throw new Error(`Current nonce ${current} > target nonce ${targetNonce} on chainId=${chainId}. Cannot proceed with module deployment.`); } continue; @@ -253,20 +256,20 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce } if (!sent) { - logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} failed to send filler tx for nonce=${current}`); + logger.error(`[MODULES_DBG] chainId=${chainId} failed to send filler tx for nonce=${current}`); throw lastErr || new Error('filler tx failed'); } current++; } - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} nonce alignment completed, current nonce=${current}`); + logger.info(`[MODULES_DBG] chainId=${chainId} nonce alignment completed, current nonce=${current}`); } else { - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} nonce already aligned at ${current}`); + logger.info(`[MODULES_DBG] chainId=${chainId} nonce already aligned at ${current}`); } // 2) Деплой модуля напрямую на согласованном nonce - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploying ${moduleType} directly with nonce=${targetNonce}`); + logger.info(`[MODULES_DBG] chainId=${chainId} deploying ${moduleType} directly with nonce=${targetNonce}`); const feeOverrides = await getFeeOverrides(provider); let gasLimit; @@ -283,7 +286,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce const fallbackGas = maxByBalance > 2_000_000n ? 2_000_000n : (maxByBalance < 500_000n ? 500_000n : maxByBalance); gasLimit = est ? (est + est / 5n) : fallbackGas; - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`); + logger.info(`[MODULES_DBG] chainId=${chainId} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`); } catch (_) { gasLimit = 1_000_000n; } @@ -293,13 +296,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce from: wallet.address, nonce: targetNonce }); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} predicted ${moduleType} address=${predictedAddress}`); + logger.info(`[MODULES_DBG] chainId=${chainId} predicted ${moduleType} address=${predictedAddress}`); // Проверяем, не развернут ли уже контракт const existingCode = await provider.getCode(predictedAddress); if (existingCode && existingCode !== '0x') { - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} already exists at predictedAddress, skip deploy`); - return { address: predictedAddress, chainId: Number(net.chainId) }; + logger.info(`[MODULES_DBG] chainId=${chainId} ${moduleType} already exists at predictedAddress, skip deploy`); + return { address: predictedAddress, chainId: chainId }; } // Деплоим модуль с retry логикой для обработки race conditions @@ -312,8 +315,8 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce deployAttempts++; // Получаем актуальный nonce прямо перед отправкой транзакции - const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, Number(net.chainId), { timeout: 15000, maxRetries: 5 }); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetNonce})`); + const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); + logger.info(`[MODULES_DBG] chainId=${chainId} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetNonce})`); const txData = { data: moduleInit, @@ -326,26 +329,26 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce const result = await rpcManager.sendTransactionWithRetry(wallet, txData, { maxRetries: 3 }); tx = result.tx; - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploy successful on attempt ${deployAttempts}`); + logger.info(`[MODULES_DBG] chainId=${chainId} deploy successful on attempt ${deployAttempts}`); break; // Успешно отправили, выходим из цикла } catch (e) { const errorMsg = e?.message || e; - logger.warn(`[MODULES_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts} failed: ${errorMsg}`); + logger.warn(`[MODULES_DBG] chainId=${chainId} deploy attempt ${deployAttempts} failed: ${errorMsg}`); // Проверяем, является ли это ошибкой nonce if (String(errorMsg).toLowerCase().includes('nonce too low') && deployAttempts < maxDeployAttempts) { - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} nonce race condition detected, retrying...`); + logger.info(`[MODULES_DBG] chainId=${chainId} nonce race condition detected, retrying...`); // Получаем актуальный nonce из сети - const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, Number(net.chainId), { timeout: 15000, maxRetries: 5 }); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} current nonce: ${currentNonce}, target: ${targetNonce}`); + const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); + logger.info(`[MODULES_DBG] chainId=${chainId} current nonce: ${currentNonce}, target: ${targetNonce}`); // Если текущий nonce больше целевого, обновляем targetNonce if (currentNonce > targetNonce) { - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} current nonce ${currentNonce} > target nonce ${targetNonce}, updating target`); + logger.info(`[MODULES_DBG] chainId=${chainId} current nonce ${currentNonce} > target nonce ${targetNonce}, updating target`); targetNonce = currentNonce; - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} updated targetNonce to: ${targetNonce}`); + logger.info(`[MODULES_DBG] chainId=${chainId} updated targetNonce to: ${targetNonce}`); // Короткая задержка перед следующей попыткой await new Promise(resolve => setTimeout(resolve, 1000)); @@ -354,7 +357,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce // Если текущий nonce меньше целевого, выравниваем его if (currentNonce < targetNonce) { - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} aligning nonce from ${currentNonce} to ${targetNonce}`); + logger.info(`[MODULES_DBG] chainId=${chainId} aligning nonce from ${currentNonce} to ${targetNonce}`); // Выравниваем nonce нулевыми транзакциями for (let i = currentNonce; i < targetNonce; i++) { @@ -368,13 +371,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce }); await fillerTx.wait(); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx ${i} confirmed`); + logger.info(`[MODULES_DBG] chainId=${chainId} filler tx ${i} confirmed`); // Обновляем nonce в кэше - nonceManager.reserveNonce(wallet.address, Number(net.chainId), i); + nonceManager.reserveNonce(wallet.address, chainId, i); } catch (fillerError) { - logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx ${i} failed: ${fillerError.message}`); + logger.error(`[MODULES_DBG] chainId=${chainId} filler tx ${i} failed: ${fillerError.message}`); throw fillerError; } } @@ -382,7 +385,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce // ВАЖНО: Обновляем targetNonce на актуальный nonce для следующей попытки targetNonce = currentNonce; - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} updated targetNonce to: ${targetNonce}`); + logger.info(`[MODULES_DBG] chainId=${chainId} updated targetNonce to: ${targetNonce}`); // Короткая задержка перед следующей попыткой await new Promise(resolve => setTimeout(resolve, 1000)); @@ -402,24 +405,32 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce const rc = await tx.wait(); const deployedAddress = rc.contractAddress || predictedAddress; - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployed at=${deployedAddress}`); - return { address: deployedAddress, chainId: Number(net.chainId) }; + logger.info(`[MODULES_DBG] chainId=${chainId} ${moduleType} deployed at=${deployedAddress}`); + return { address: deployedAddress, chainId: chainId }; } // Деплой всех модулей в одной сети -async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params) { +async function deployAllModulesInNetwork(chainId, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params) { const { ethers } = hre; + // Получаем RPC URL для данной сети + const rpcService = require('../../services/rpcProviderService'); + const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); + if (!rpcUrl) { + throw new Error(`RPC URL не найден для chainId ${chainId}`); + } + // Используем новый менеджер RPC с retry логикой - const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, { + const { provider, wallet, network } = await createRPCConnection(chainId, pk, { maxRetries: 3, timeout: 30000 }); const net = network; + const chainId = Number(net.chainId); - logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploying modules: ${modulesToDeploy.join(', ')}`); + logger.info(`[MODULES_DBG] chainId=${chainId} deploying modules: ${modulesToDeploy.join(', ')}`); const results = {}; @@ -432,14 +443,14 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo logger.info(`[MODULES_DBG] Деплой модуля ${moduleType} в сети ${net.name || net.chainId}`); if (!MODULE_CONFIGS[moduleType]) { - logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type: ${moduleType}`); + logger.error(`[MODULES_DBG] chainId=${chainId} Unknown module type: ${moduleType}`); results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` }; logger.error(`[MODULES_DBG] Неизвестный тип модуля: ${moduleType}`); continue; } if (!moduleInit) { - logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} No init code for module: ${moduleType}`); + logger.error(`[MODULES_DBG] chainId=${chainId} No init code for module: ${moduleType}`); results[moduleType] = { success: false, error: `No init code for module: ${moduleType}` }; logger.error(`[MODULES_DBG] Отсутствует код инициализации для модуля: ${moduleType}`); continue; @@ -458,7 +469,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo // Получаем аргументы конструктора для модуля const moduleConfig = MODULE_CONFIGS[moduleType]; - const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(net.chainId), wallet.address); + const constructorArgs = moduleConfig.constructorArgs(dleAddress, chainId, wallet.address); // Ждем 30 секунд перед верификацией, чтобы транзакция получила подтверждения logger.info(`[MODULES_DBG] Ждем 30 секунд перед верификацией модуля ${moduleType}...`); @@ -478,7 +489,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo } const verificationResult = await verifyModuleAfterDeploy( - Number(net.chainId), + chainId, result.address, moduleType, constructorArgs, @@ -510,9 +521,9 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo } } } catch (error) { - logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployment failed:`, error.message); + logger.error(`[MODULES_DBG] chainId=${chainId} ${moduleType} deployment failed:`, error.message); results[moduleType] = { - chainId: Number(net.chainId), + chainId: chainId, success: false, error: error.message }; @@ -521,7 +532,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo } return { - chainId: Number(net.chainId), + chainId: chainId, modules: results }; } @@ -544,6 +555,15 @@ async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, mod } async function main() { + // 🔧 BEST PRACTICE: Настраиваем NO_PROXY перед деплоем + try { + const proxyManager = require('../../utils/proxyManager'); + await proxyManager.initialize(); + console.log('[MODULES_DBG] ✅ ProxyManager инициализирован'); + } catch (error) { + console.warn('[MODULES_DBG] ⚠️ Не удалось инициализировать ProxyManager:', error.message); + } + const { ethers } = hre; // Обрабатываем аргументы командной строки и переменные окружения @@ -609,6 +629,7 @@ async function main() { const pk = params.privateKey || params.private_key || process.env.PRIVATE_KEY; const networks = params.rpcUrls || params.rpc_urls || []; + const supportedChainIds = params.supportedChainIds || []; const dleAddress = params.dleAddress; const salt = params.CREATE2_SALT || params.create2_salt; @@ -666,7 +687,7 @@ async function main() { const ContractFactory = await hre.ethers.getContractFactory(moduleConfig.contractName); // Получаем аргументы конструктора для первой сети (для расчета init кода) - const firstConnection = await createRPCConnection(networks[0], pk, { + const firstConnection = await createRPCConnection(supportedChainIds[0], pk, { maxRetries: 3, timeout: 30000 }); @@ -688,8 +709,8 @@ async function main() { // Подготовим провайдеры и вычислим общие nonce для каждого модуля // Создаем RPC соединения с retry логикой - logger.info(`[MODULES_DBG] Создаем RPC соединения для ${networks.length} сетей...`); - const connections = await createMultipleRPCConnections(networks, pk, { + logger.info(`[MODULES_DBG] Создаем RPC соединения для ${supportedChainIds.length} сетей...`); + const connections = await createMultipleRPCConnections(supportedChainIds, pk, { maxRetries: 3, timeout: 30000 }); @@ -698,7 +719,7 @@ async function main() { throw new Error('Не удалось установить ни одного RPC соединения'); } - logger.info(`[MODULES_DBG] ✅ Успешно подключились к ${connections.length}/${networks.length} сетям`); + logger.info(`[MODULES_DBG] ✅ Успешно подключились к ${connections.length}/${supportedChainIds.length} сетям`); const nonces = []; for (const connection of connections) { @@ -718,33 +739,28 @@ async function main() { logger.info(`[MODULES_DBG] nonces=${JSON.stringify(nonces)} targetNonces=${JSON.stringify(targetNonces)}`); // ПАРАЛЛЕЛЬНЫЙ деплой всех модулей во всех сетях одновременно - logger.info(`[MODULES_DBG] Starting PARALLEL deployment of all modules to ${networks.length} networks`); + logger.info(`[MODULES_DBG] Starting PARALLEL deployment of all modules to ${connections.length} networks`); - const deploymentPromises = networks.map(async (rpcUrl, networkIndex) => { - logger.info(`[MODULES_DBG] 🚀 Starting deployment to network ${networkIndex + 1}/${networks.length}: ${rpcUrl}`); + const deploymentPromises = connections.map(async (connection, networkIndex) => { + logger.info(`[MODULES_DBG] 🚀 Starting deployment to network ${networkIndex + 1}/${connections.length}: ${connection.rpcUrl}`); try { - // Получаем chainId динамически из сети с retry логикой - const { provider, network } = await createRPCConnection(rpcUrl, pk, { - maxRetries: 3, - timeout: 30000 - }); - const chainId = Number(network.chainId); + const chainId = Number(connection.network.chainId); logger.info(`[MODULES_DBG] 📡 Network ${networkIndex + 1} chainId: ${chainId}`); - const result = await deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params); + const result = await deployAllModulesInNetwork(chainId, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params); logger.info(`[MODULES_DBG] ✅ Network ${networkIndex + 1} (chainId: ${chainId}) deployment SUCCESS`); - return { rpcUrl, chainId, ...result }; + return { rpcUrl: connection.rpcUrl, chainId, ...result }; } catch (error) { logger.error(`[MODULES_DBG] ❌ Network ${networkIndex + 1} deployment FAILED:`, error.message); - return { rpcUrl, error: error.message }; + return { rpcUrl: connection.rpcUrl, error: error.message }; } }); // Ждем завершения всех деплоев const deployResults = await Promise.all(deploymentPromises); - logger.info(`[MODULES_DBG] All ${networks.length} deployments completed`); + logger.info(`[MODULES_DBG] All ${connections.length} deployments completed`); // Логируем результаты деплоя для каждой сети deployResults.forEach((result, index) => { diff --git a/backend/scripts/deploy/deploy-multichain.js b/backend/scripts/deploy/deploy-multichain.js index 13153a7..bfeea9f 100755 --- a/backend/scripts/deploy/deploy-multichain.js +++ b/backend/scripts/deploy/deploy-multichain.js @@ -220,12 +220,19 @@ async function verifyDLEAfterDeploy(chainId, contractAddress, constructorArgs, a } } -async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit, params, dleConfig, initializer, etherscanKey) { +async function deployInNetwork(chainId, pk, initCodeHash, targetDLENonce, dleInit, params, dleConfig, initializer, etherscanKey) { try { const { ethers } = hre; + // Получаем RPC URL для данной сети + const rpcService = require('../../services/rpcProviderService'); + const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); + if (!rpcUrl) { + throw new Error(`RPC URL не найден для chainId ${chainId}`); + } + // Используем новый менеджер RPC с retry логикой - const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, { + const { provider, wallet, network } = await createRPCConnection(chainId, pk, { maxRetries: 3, timeout: 30000 }); @@ -245,27 +252,26 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit } // 1) Используем NonceManager для получения актуального nonce - const chainId = Number(net.chainId); let current = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); logger.info(`[MULTI_DBG] chainId=${chainId} current nonce=${current} (target was ${targetDLENonce})`); // Если текущий nonce больше целевого, обновляем targetDLENonce if (current > targetDLENonce) { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current nonce ${current} > targetDLENonce ${targetDLENonce}, updating target`); + logger.info(`[MULTI_DBG] chainId=${chainId} current nonce ${current} > targetDLENonce ${targetDLENonce}, updating target`); targetDLENonce = current; - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} updated targetDLENonce to: ${targetDLENonce}`); + logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`); } // Если текущий nonce меньше целевого, выравниваем его if (current < targetDLENonce) { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} starting nonce alignment: ${current} -> ${targetDLENonce} (${targetDLENonce - current} transactions needed)`); + logger.info(`[MULTI_DBG] chainId=${chainId} starting nonce alignment: ${current} -> ${targetDLENonce} (${targetDLENonce - current} transactions needed)`); } else { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce already aligned: ${current} = ${targetDLENonce}`); + logger.info(`[MULTI_DBG] chainId=${chainId} nonce already aligned: ${current} = ${targetDLENonce}`); } // 2) Выравниваем nonce если нужно (используем NonceManager) if (current < targetDLENonce) { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} aligning nonce from ${current} to ${targetDLENonce}`); + logger.info(`[MULTI_DBG] chainId=${chainId} aligning nonce from ${current} to ${targetDLENonce}`); try { current = await nonceManager.alignNonceToTarget( @@ -277,31 +283,31 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit { gasLimit: 21000, maxRetries: 5 } ); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce alignment completed, current nonce=${current}`); + logger.info(`[MULTI_DBG] chainId=${chainId} nonce alignment completed, current nonce=${current}`); // Зарезервируем nonce в NonceManager nonceManager.reserveNonce(wallet.address, chainId, targetDLENonce); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} ready for DLE deployment with nonce=${current}`); + logger.info(`[MULTI_DBG] chainId=${chainId} ready for DLE deployment with nonce=${current}`); } catch (error) { - logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce alignment failed: ${error.message}`); + logger.error(`[MULTI_DBG] chainId=${chainId} nonce alignment failed: ${error.message}`); throw error; } } else { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce already aligned at ${current}`); + logger.info(`[MULTI_DBG] chainId=${chainId} nonce already aligned at ${current}`); } // 2) Проверяем баланс перед деплоем const balance = await provider.getBalance(wallet.address, 'latest'); const balanceEth = ethers.formatEther(balance); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} wallet balance: ${balanceEth} ETH`); + logger.info(`[MULTI_DBG] chainId=${chainId} wallet balance: ${balanceEth} ETH`); if (balance < ethers.parseEther('0.01')) { - throw new Error(`Insufficient balance for deployment on chainId=${Number(net.chainId)}. Current: ${balanceEth} ETH, required: 0.01 ETH minimum`); + throw new Error(`Insufficient balance for deployment on chainId=${chainId}. Current: ${balanceEth} ETH, required: 0.01 ETH minimum`); } // 3) Деплой DLE с актуальным nonce - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} deploying DLE with current nonce`); + logger.info(`[MULTI_DBG] chainId=${chainId} deploying DLE with current nonce`); const feeOverrides = await getFeeOverrides(provider); let gasLimit; @@ -318,7 +324,7 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit const fallbackGas = maxByBalance > 5_000_000n ? 5_000_000n : (maxByBalance < 2_500_000n ? 2_500_000n : maxByBalance); gasLimit = est ? (est + est / 5n) : fallbackGas; - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`); + logger.info(`[MULTI_DBG] chainId=${chainId} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`); } catch (_) { gasLimit = 3_000_000n; } @@ -328,17 +334,17 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit from: wallet.address, nonce: targetDLENonce }); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} predicted DLE address=${predictedAddress} (nonce=${targetDLENonce})`); + logger.info(`[MULTI_DBG] chainId=${chainId} predicted DLE address=${predictedAddress} (nonce=${targetDLENonce})`); // Проверяем, не развернут ли уже контракт const existingCode = await provider.getCode(predictedAddress); if (existingCode && existingCode !== '0x') { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE already exists at predictedAddress, skip deploy`); + logger.info(`[MULTI_DBG] chainId=${chainId} DLE already exists at predictedAddress, skip deploy`); // Проверяем и инициализируем логотип для существующего контракта if (params.logoURI && params.logoURI !== '') { try { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} checking logoURI for existing contract`); + logger.info(`[MULTI_DBG] chainId=${chainId} checking logoURI for existing contract`); // Ждем 2 секунды для стабильности соединения await new Promise(resolve => setTimeout(resolve, 2000)); @@ -348,19 +354,19 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit const currentLogo = await dleContract.logoURI(); if (currentLogo === '' || currentLogo === '0x') { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing logoURI for existing contract: ${params.logoURI}`); + logger.info(`[MULTI_DBG] chainId=${chainId} initializing logoURI for existing contract: ${params.logoURI}`); const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides); await logoTx.wait(); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialized for existing contract`); + logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialized for existing contract`); } else { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI already set: ${currentLogo}`); + logger.info(`[MULTI_DBG] chainId=${chainId} logoURI already set: ${currentLogo}`); } } catch (error) { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed for existing contract: ${error.message}`); + logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialization failed for existing contract: ${error.message}`); } } - return { address: predictedAddress, chainId: Number(net.chainId) }; + return { address: predictedAddress, chainId: chainId }; } // Деплоим DLE с retry логикой для обработки race conditions @@ -374,13 +380,13 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit // Получаем актуальный nonce прямо перед отправкой транзакции const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetDLENonce})`); + logger.info(`[MULTI_DBG] chainId=${chainId} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetDLENonce})`); // Если текущий nonce больше целевого, обновляем targetDLENonce if (currentNonce > targetDLENonce) { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current nonce ${currentNonce} > target nonce ${targetDLENonce}, updating target`); + logger.info(`[MULTI_DBG] chainId=${chainId} current nonce ${currentNonce} > target nonce ${targetDLENonce}, updating target`); targetDLENonce = currentNonce; - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} updated targetDLENonce to: ${targetDLENonce}`); + logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`); } const txData = { @@ -397,25 +403,25 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit // Отмечаем транзакцию как pending в NonceManager nonceManager.markTransactionPending(wallet.address, chainId, currentNonce, tx.hash); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} deploy successful on attempt ${deployAttempts}`); + logger.info(`[MULTI_DBG] chainId=${chainId} deploy successful on attempt ${deployAttempts}`); break; // Успешно отправили, выходим из цикла } catch (e) { const errorMsg = e?.message || e; - logger.warn(`[MULTI_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts} failed: ${errorMsg}`); + logger.warn(`[MULTI_DBG] chainId=${chainId} deploy attempt ${deployAttempts} failed: ${errorMsg}`); // Проверяем, является ли это ошибкой nonce if (String(errorMsg).toLowerCase().includes('nonce too low') && deployAttempts < maxDeployAttempts) { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce race condition detected, retrying...`); + logger.info(`[MULTI_DBG] chainId=${chainId} nonce race condition detected, retrying...`); // Используем NonceManager для обновления nonce nonceManager.resetNonce(wallet.address, chainId); const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current nonce: ${currentNonce}, target was: ${targetDLENonce}`); + logger.info(`[MULTI_DBG] chainId=${chainId} current nonce: ${currentNonce}, target was: ${targetDLENonce}`); // Обновляем targetDLENonce на актуальный nonce targetDLENonce = currentNonce; - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} updated targetDLENonce to: ${targetDLENonce}`); + logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`); // Короткая задержка перед следующей попыткой await new Promise(resolve => setTimeout(resolve, 1000)); @@ -440,19 +446,19 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit // Проверяем, что адрес соответствует предсказанному if (deployedAddress !== predictedAddress) { - logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} ADDRESS MISMATCH! predicted=${predictedAddress} actual=${deployedAddress}`); + logger.error(`[MULTI_DBG] chainId=${chainId} ADDRESS MISMATCH! predicted=${predictedAddress} actual=${deployedAddress}`); throw new Error(`Address mismatch: predicted ${predictedAddress} != actual ${deployedAddress}`); } - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE deployed at=${deployedAddress} ✅`); + logger.info(`[MULTI_DBG] chainId=${chainId} DLE deployed at=${deployedAddress} ✅`); // Инициализация логотипа если он указан if (params.logoURI && params.logoURI !== '') { try { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing logoURI: ${params.logoURI}`); + logger.info(`[MULTI_DBG] chainId=${chainId} initializing logoURI: ${params.logoURI}`); // Ждем 5 секунд, чтобы контракт получил подтверждения - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} waiting 5 seconds for contract confirmations...`); + logger.info(`[MULTI_DBG] chainId=${chainId} waiting 5 seconds for contract confirmations...`); await new Promise(resolve => setTimeout(resolve, 5000)); const DLE = await hre.ethers.getContractFactory('contracts/DLE.sol:DLE'); @@ -460,24 +466,24 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit // Проверяем текущий логотип перед инициализацией const currentLogo = await dleContract.logoURI(); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current logoURI: ${currentLogo}`); + logger.info(`[MULTI_DBG] chainId=${chainId} current logoURI: ${currentLogo}`); if (currentLogo === '' || currentLogo === '0x') { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI is empty, initializing...`); + logger.info(`[MULTI_DBG] chainId=${chainId} logoURI is empty, initializing...`); const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides); - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI transaction sent: ${logoTx.hash}`); + logger.info(`[MULTI_DBG] chainId=${chainId} logoURI transaction sent: ${logoTx.hash}`); await logoTx.wait(2); // Ждем 2 подтверждения с таймаутом - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialized successfully`); + logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialized successfully`); } else { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI already set: ${currentLogo}, skipping initialization`); + logger.info(`[MULTI_DBG] chainId=${chainId} logoURI already set: ${currentLogo}, skipping initialization`); } } catch (error) { - logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed: ${error.message}`); - logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} error stack: ${error.stack}`); + logger.error(`[MULTI_DBG] chainId=${chainId} logoURI initialization failed: ${error.message}`); + logger.error(`[MULTI_DBG] chainId=${chainId} error stack: ${error.stack}`); // Не прерываем деплой из-за ошибки логотипа } } else { - logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} no logoURI specified, skipping initialization`); + logger.info(`[MULTI_DBG] chainId=${chainId} no logoURI specified, skipping initialization`); } // Автоматическая верификация DLE контракта после успешного деплоя @@ -573,6 +579,15 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit async function main() { console.log('[MULTI_DBG] 🚀 ВХОДИМ В ФУНКЦИЮ MAIN!'); + + // 🔧 BEST PRACTICE: Настраиваем NO_PROXY перед деплоем + try { + const proxyManager = require('../../utils/proxyManager'); + await proxyManager.initialize(); + console.log('[MULTI_DBG] ✅ ProxyManager инициализирован'); + } catch (error) { + console.warn('[MULTI_DBG] ⚠️ Не удалось инициализировать ProxyManager:', error.message); + } const { ethers } = hre; console.log('[MULTI_DBG] ✅ ethers получен'); @@ -699,8 +714,8 @@ async function main() { } // Подготовим провайдеры и вычислим общий nonce для DLE с retry логикой - logger.info(`[MULTI_DBG] Создаем RPC соединения для ${networks.length} сетей...`); - const connections = await createMultipleRPCConnections(networks, pk, { + logger.info(`[MULTI_DBG] Создаем RPC соединения для ${supportedChainIds.length} сетей...`); + const connections = await createMultipleRPCConnections(supportedChainIds, pk, { maxRetries: 3, timeout: 30000 }); @@ -709,7 +724,7 @@ async function main() { throw new Error('Не удалось установить ни одного RPC соединения'); } - logger.info(`[MULTI_DBG] ✅ Успешно подключились к ${connections.length}/${networks.length} сетям`); + logger.info(`[MULTI_DBG] ✅ Успешно подключились к ${connections.length}/${supportedChainIds.length} сетям`); // Очищаем старые pending транзакции для всех сетей for (const connection of connections) { @@ -719,12 +734,13 @@ async function main() { const nonces = []; for (const connection of connections) { + logger.info(`[MULTI_DBG] Получаем nonce для connection: address=${connection.wallet.address}, rpcUrl=${connection.rpcUrl}, chainId=${Number(connection.network.chainId)}`); const n = await nonceManager.getNonce(connection.wallet.address, connection.rpcUrl, Number(connection.network.chainId)); nonces.push(n); } const targetDLENonce = Math.max(...nonces); logger.info(`[MULTI_DBG] nonces=${JSON.stringify(nonces)} targetDLENonce=${targetDLENonce}`); - logger.info(`[MULTI_DBG] Starting deployment to ${networks.length} networks:`, networks); + logger.info(`[MULTI_DBG] Starting deployment to ${connections.length} networks`); // ПАРАЛЛЕЛЬНЫЙ деплой во всех успешных сетях одновременно console.log(`[MULTI_DBG] 🚀 ДОШЛИ ДО ПАРАЛЛЕЛЬНОГО ДЕПЛОЯ!`); @@ -744,7 +760,7 @@ async function main() { throw new Error(`InitCode не найден для chainId: ${chainId}`); } - const r = await deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, networkInitCode, params, dleConfig, initializer, etherscanKey); + const r = await deployInNetwork(chainId, pk, initCodeHash, targetDLENonce, networkInitCode, params, dleConfig, initializer, etherscanKey); logger.info(`[MULTI_DBG] ✅ Network ${i + 1} (chainId: ${chainId}) deployment SUCCESS: ${r.address}`); return { rpcUrl, diff --git a/backend/scripts/utils/read-dle-info.js b/backend/scripts/utils/read-dle-info.js index f5d24ef..fa943c6 100644 --- a/backend/scripts/utils/read-dle-info.js +++ b/backend/scripts/utils/read-dle-info.js @@ -20,10 +20,12 @@ async function main() { console.log(`Читаем данные DLE из блокчейна по адресу: ${dleAddress}`); // Получаем RPC URL из переменных окружения или используем дефолтный для Sepolia - const rpcUrl = process.env.RPC_URL || 'https://eth-sepolia.nodereal.io/v1/YOUR_NODEREAL_KEY'; + // Получаем RPC URL из базы данных + const rpcService = require('../../services/rpcProviderService'); + const rpcUrl = await rpcService.getRpcUrlByChainId(11155111); // Создаем провайдер - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(11155111)); try { // Получаем ABI контракта DLE diff --git a/backend/server.js b/backend/server.js index 0f17c83..88dc306 100644 --- a/backend/server.js +++ b/backend/server.js @@ -11,6 +11,51 @@ */ require('dotenv').config(); + +// Функция для настройки NO_PROXY на основе RPC провайдеров из базы данных +async function configureNoProxyFromRpcProviders() { + try { + const rpcService = require('./services/rpcProviderService'); + const providers = await rpcService.getAllRpcProviders(); + + const rpcDomains = providers + .map(provider => provider.rpc_url) + .filter(url => url && url.startsWith('http')) + .map(url => { + try { + const urlObj = new URL(url); + return urlObj.hostname; + } catch (e) { + return null; + } + }) + .filter(hostname => hostname) + .filter((hostname, index, array) => array.indexOf(hostname) === index); // убираем дубликаты + + if (rpcDomains.length > 0) { + const existingNoProxy = process.env.NO_PROXY || ''; + + // Добавляем RPC домены к существующему NO_PROXY + const newDomains = rpcDomains.filter(domain => !existingNoProxy.includes(domain)); + + if (newDomains.length > 0) { + process.env.NO_PROXY = existingNoProxy ? `${existingNoProxy},${newDomains.join(',')}` : newDomains.join(','); + console.log('[Server] ✅ Добавлены RPC домены в NO_PROXY:', newDomains.join(', ')); + console.log('[Server] 📋 Обновленный NO_PROXY:', process.env.NO_PROXY); + } else { + console.log('[Server] ℹ️ Все RPC домены уже в NO_PROXY'); + } + } else { + console.warn('[Server] ⚠️ Не найдено RPC провайдеров для настройки NO_PROXY'); + } + } catch (error) { + console.warn('[Server] ❌ Не удалось загрузить RPC провайдеры для NO_PROXY:', error.message); + } +} + +// Экспортируем функцию для использования в других модулях +module.exports.configureNoProxyFromRpcProviders = configureNoProxyFromRpcProviders; + const { app, nonceStore } = require('./app'); const http = require('http'); const { initWSS } = require('./wsHub'); @@ -32,6 +77,9 @@ initWSS(server); async function startServer() { await initDbPool(); + // Настройка NO_PROXY для RPC провайдеров из базы данных + await configureNoProxyFromRpcProviders(); + // Инициализация AI ассистента В ФОНЕ (неблокирующая) seedAIAssistantSettings().catch(error => { console.warn('[Server] Ollama недоступен, AI ассистент будет инициализирован позже:', error.message); diff --git a/backend/services/admin-role.js b/backend/services/admin-role.js index ea0b3a5..d0bbdf3 100644 --- a/backend/services/admin-role.js +++ b/backend/services/admin-role.js @@ -67,7 +67,7 @@ async function checkAdminRole(address) { errorCount++; return null; } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(chainId)); // Проверяем доступность сети с таймаутом try { const networkCheckPromise = provider.getNetwork(); diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index 1e5175f..13ebdef 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -455,6 +455,7 @@ class AuthService { } } + /** * Определяет уровень доступа пользователя на основе количества токенов * @param {string} address - Адрес кошелька @@ -490,23 +491,35 @@ class AuthService { const tokens = tokensResult.rows; // Получаем RPC провайдеры - const rpcProvidersResult = await db.getQuery()( - 'SELECT id, chain_id, created_at, updated_at, decrypt_text(network_id_encrypted, $1) as network_id, decrypt_text(rpc_url_encrypted, $1) as rpc_url FROM rpc_providers', - [encryptionKey] - ); - const rpcProviders = rpcProvidersResult.rows; + // Убрано - используем rpcService вместо прямого запроса к БД - const rpcMap = {}; - for (const rpc of rpcProviders) { - rpcMap[rpc.network_id] = rpc.rpc_url; - } + // Используем правильный RPC URL из базы данных + const rpcService = require('./rpcProviderService'); // Получаем балансы токенов из блокчейна const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; const tokenBalances = []; + // Получаем все RPC провайдеры из базы данных для маппинга + const allRpcProviders = await rpcService.getAllRpcProviders(); + const networkToChainId = {}; + + // Создаем маппинг из базы данных + for (const provider of allRpcProviders) { + if (provider.network_id) { + networkToChainId[provider.network_id] = provider.chain_id; + } + } + for (const token of tokens) { - const rpcUrl = rpcMap[token.network]; + // Получаем chain_id из названия сети из базы данных + const chainId = networkToChainId[token.network]; + if (!chainId) { + logger.warn(`[getUserAccessLevel] Неизвестная сеть: ${token.network}`); + continue; + } + + const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); if (!rpcUrl) continue; try { diff --git a/backend/services/dleV2Service.js b/backend/services/dleV2Service.js index 594072e..e416c91 100644 --- a/backend/services/dleV2Service.js +++ b/backend/services/dleV2Service.js @@ -565,7 +565,7 @@ class DLEV2Service { * @param {Array} allDles - Все DLE * @returns {Array} - Сгруппированные DLE */ - groupMultichainDLEs(allDles) { + async groupMultichainDLEs(allDles) { const groups = new Map(); for (const dle of allDles) { @@ -588,7 +588,7 @@ class DLEV2Service { groups.get(groupKey).networks.push({ chainId: dle.chainId, address: dle.address, - networkName: this.getRpcUrlForChain(dle.chainId)?.name || `Chain ${dle.chainId}`, + networkName: (await this.getRpcUrlForChain(dle.chainId))?.name || `Chain ${dle.chainId}`, status: dle.status || 'active' }); } @@ -610,16 +610,25 @@ class DLEV2Service { * @param {number} chainId - ID сети * @returns {Object|null} - Информация о RPC */ - getRpcUrlForChain(chainId) { - const rpcMappings = { - 1: { name: 'Ethereum Mainnet', url: 'https://mainnet.infura.io/v3/' }, - 11155111: { name: 'Sepolia Testnet', url: 'https://sepolia.infura.io/v3/' }, - 17000: { name: 'Holesky Testnet', url: 'https://holesky.infura.io/v3/' }, - 421614: { name: 'Arbitrum Sepolia', url: 'https://sepolia-rollup.arbitrum.io/rpc' }, - 84532: { name: 'Base Sepolia', url: 'https://sepolia.base.org' } - }; - - return rpcMappings[chainId] || null; + async getRpcUrlForChain(chainId) { + try { + // Получаем RPC URL из базы данных + const rpcService = require('./rpcProviderService'); + const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); + + if (!rpcUrl) { + return null; + } + + // Возвращаем объект с RPC URL из базы данных + return { + name: `Chain ${chainId}`, + url: rpcUrl + }; + } catch (error) { + console.error(`[DLE V2 Service] Ошибка получения RPC для chain_id ${chainId}:`, error); + return null; + } } /** @@ -639,7 +648,7 @@ class DLEV2Service { throw new Error(`RPC URL не найден для сети ${chainId}`); } - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await getRpcUrlByChainId(chainId)); const balance = await provider.getBalance(wallet.address); console.log(`💰 Баланс в сети ${chainId}: ${ethers.formatEther(balance)} ETH`); diff --git a/backend/services/tokenBalanceService.js b/backend/services/tokenBalanceService.js index 3c976c5..6f3b012 100644 --- a/backend/services/tokenBalanceService.js +++ b/backend/services/tokenBalanceService.js @@ -32,26 +32,52 @@ async function getUserTokenBalances(address) { ); const tokens = tokensResult.rows; - const rpcProvidersResult = await db.getQuery()( - 'SELECT id, chain_id, created_at, updated_at, decrypt_text(network_id_encrypted, $1) as network_id, decrypt_text(rpc_url_encrypted, $1) as rpc_url FROM rpc_providers', - [encryptionKey] - ); - const rpcProviders = rpcProvidersResult.rows; - const rpcMap = {}; - for (const rpc of rpcProviders) { - rpcMap[rpc.network_id] = rpc.rpc_url; - } + // Убрано - используем rpcService вместо прямого запроса к БД + // Используем правильный RPC URL из базы данных + const rpcService = require('./rpcProviderService'); const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; const results = []; + // Получаем все RPC провайдеры из базы данных для маппинга + const allRpcProviders = await rpcService.getAllRpcProviders(); + const networkToChainId = {}; + + // Создаем маппинг из базы данных + for (const provider of allRpcProviders) { + if (provider.network_id) { + networkToChainId[provider.network_id] = provider.chain_id; + } + } + for (const token of tokens) { - const rpcUrl = rpcMap[token.network]; - if (!rpcUrl) { - logger.warn(`[tokenBalanceService] RPC URL не найден для сети ${token.network}`); + // Получаем chain_id из названия сети из базы данных + const chainId = networkToChainId[token.network]; + if (!chainId) { + logger.warn(`[tokenBalanceService] Неизвестная сеть: ${token.network}`); continue; } + logger.info(`[tokenBalanceService] Ищем RPC для token.network: ${token.network} (chainId: ${chainId})`); + const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); + if (!rpcUrl) { + logger.warn(`[tokenBalanceService] RPC URL не найден для сети ${token.network} (chainId: ${chainId})`); + // Пропускаем токен, если нет RPC URL + results.push({ + network: token.network, + tokenAddress: token.address, + tokenName: token.name, + symbol: token.symbol || '', + balance: '0', + minBalance: token.min_balance, + readonlyThreshold: token.readonly_threshold || 1, + editorThreshold: token.editor_threshold || 2, + error: 'RPC URL не настроен' + }); + continue; + } + logger.info(`[tokenBalanceService] Найден RPC URL: ${rpcUrl}`); + // Создаем провайдер с таймаутом const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, { polling: false, @@ -84,8 +110,38 @@ async function getUserTokenBalances(address) { `[tokenBalanceService] Ошибка получения баланса для ${token.name} (${token.address}) в сети ${token.network}:`, e.message || e ); + + // Проверяем тип ошибки для лучшей диагностики + const errorMessage = e.message || e.toString(); + let errorType = 'Неизвестная ошибка'; + + if (errorMessage.includes('timeout') || errorMessage.includes('TIMEOUT')) { + errorType = 'Таймаут соединения - возможно, нужен VPN'; + } else if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND')) { + errorType = 'Не удается подключиться к RPC провайдеру'; + } else if (errorMessage.includes('NETWORK_ERROR')) { + errorType = 'Ошибка сети - проверьте интернет-соединение'; + } + balance = '0'; + + // Добавляем информацию об ошибке в результат + results.push({ + network: token.network, + tokenAddress: token.address, + tokenName: token.name, + symbol: token.symbol || '', + balance, + minBalance: token.min_balance, + readonlyThreshold: token.readonly_threshold || 1, + editorThreshold: token.editor_threshold || 2, + error: errorType, + errorDetails: errorMessage + }); + continue; } + + // Добавляем успешный результат results.push({ network: token.network, tokenAddress: token.address, @@ -98,7 +154,8 @@ async function getUserTokenBalances(address) { }); } - return results; + // Преобразуем в обычный массив для корректной сериализации + return JSON.parse(JSON.stringify(results)); } module.exports = { getUserTokenBalances }; diff --git a/backend/utils/deploymentUtils.js b/backend/utils/deploymentUtils.js index 40ad0e8..393ce94 100644 --- a/backend/utils/deploymentUtils.js +++ b/backend/utils/deploymentUtils.js @@ -93,26 +93,26 @@ async function getBalance(provider, address) { /** * Создает RPC соединение с retry логикой - * @param {string} rpcUrl - URL RPC + * @param {number} chainId - ID цепочки * @param {string} privateKey - Приватный ключ * @param {Object} options - Опции соединения * @returns {Promise} - {provider, wallet, network} */ -async function createRPCConnection(rpcUrl, privateKey, options = {}) { +async function createRPCConnection(chainId, privateKey, options = {}) { const rpcManager = new RPCConnectionManager(); - return await rpcManager.createConnection(rpcUrl, privateKey, options); + return await rpcManager.createConnection(chainId, privateKey, options); } /** * Создает множественные RPC соединения с обработкой ошибок - * @param {Array} rpcUrls - Массив RPC URL + * @param {Array} chainIds - Массив chain ID * @param {string} privateKey - Приватный ключ * @param {Object} options - Опции соединения * @returns {Promise} - Массив успешных соединений */ -async function createMultipleRPCConnections(rpcUrls, privateKey, options = {}) { +async function createMultipleRPCConnections(chainIds, privateKey, options = {}) { const rpcManager = new RPCConnectionManager(); - return await rpcManager.createMultipleConnections(rpcUrls, privateKey, options); + return await rpcManager.createMultipleConnections(chainIds, privateKey, options); } // sendTransactionWithRetry функция удалена - используем RPCConnectionManager напрямую diff --git a/backend/utils/loadRpcFromDatabase.js b/backend/utils/loadRpcFromDatabase.js new file mode 100644 index 0000000..df9b0e4 --- /dev/null +++ b/backend/utils/loadRpcFromDatabase.js @@ -0,0 +1,87 @@ +/** + * Загрузка RPC URL из базы данных и установка в переменные окружения + * Copyright (c) 2024-2025 Тарабанов Александр Викторович + */ + +// Убрано - не нужен для загрузки RPC + +/** + * Загружает RPC URL из базы данных и устанавливает их в переменные окружения + */ +async function loadRpcFromDatabase() { + try { + console.log('[RPC Loader] Загружаем RPC URL из базы данных...'); + + // Получаем все RPC провайдеры из базы данных + const rpcService = require('../services/rpcProviderService'); + const providers = await rpcService.getAllRpcProviders(); + + if (providers.length === 0) { + console.warn('[RPC Loader] В базе данных нет RPC провайдеров'); + return; + } + + console.log(`[RPC Loader] Найдено ${providers.length} RPC провайдеров в базе данных`); + + // Устанавливаем переменные окружения для каждой сети + for (const provider of providers) { + const chainId = provider.chain_id; + const rpcUrl = provider.rpc_url; + + if (!rpcUrl) { + console.warn(`[RPC Loader] RPC URL не найден для chain_id ${chainId}`); + continue; + } + + // Устанавливаем переменные окружения в зависимости от chain_id + switch (chainId) { + case 1: // Ethereum Mainnet + process.env.MAINNET_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен MAINNET_RPC_URL: ${rpcUrl}`); + break; + case 11155111: // Sepolia + process.env.SEPOLIA_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен SEPOLIA_RPC_URL: ${rpcUrl}`); + break; + case 17000: // Holesky + process.env.HOLESKY_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен HOLESKY_RPC_URL: ${rpcUrl}`); + break; + case 421614: // Arbitrum Sepolia + process.env.ARBITRUM_SEPOLIA_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен ARBITRUM_SEPOLIA_RPC_URL: ${rpcUrl}`); + break; + case 84532: // Base Sepolia + process.env.BASE_SEPOLIA_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен BASE_SEPOLIA_RPC_URL: ${rpcUrl}`); + break; + case 42161: // Arbitrum One + process.env.ARBITRUM_ONE_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен ARBITRUM_ONE_RPC_URL: ${rpcUrl}`); + break; + case 8453: // Base + process.env.BASE_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен BASE_RPC_URL: ${rpcUrl}`); + break; + case 137: // Polygon + process.env.POLYGON_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен POLYGON_RPC_URL: ${rpcUrl}`); + break; + case 56: // BSC + process.env.BSC_RPC_URL = rpcUrl; + console.log(`[RPC Loader] Установлен BSC_RPC_URL: ${rpcUrl}`); + break; + default: + console.log(`[RPC Loader] Неизвестный chain_id ${chainId}, пропускаем`); + } + } + + console.log('[RPC Loader] ✅ RPC URL успешно загружены из базы данных'); + + } catch (error) { + console.error('[RPC Loader] ❌ Ошибка загрузки RPC URL из базы данных:', error); + throw error; + } +} + +module.exports = { loadRpcFromDatabase }; diff --git a/backend/utils/nonceManager.js b/backend/utils/nonceManager.js index e385a4c..b9e5414 100644 --- a/backend/utils/nonceManager.js +++ b/backend/utils/nonceManager.js @@ -23,10 +23,20 @@ class NonceManager { async getNonce(address, rpcUrl, chainId, options = {}) { const { timeout = 15000, maxRetries = 5 } = options; // Увеличиваем таймаут и попытки + // КРИТИЧЕСКАЯ ПРОВЕРКА: логируем входящие параметры + console.log(`[NonceManager] getNonce вызван с параметрами: address=${address}, rpcUrl=${rpcUrl}, chainId=${chainId}`); + + // КРИТИЧЕСКАЯ ПРОВЕРКА: если rpcUrl содержит 127.0.0.1:8545, это ошибка! + if (rpcUrl && rpcUrl.includes('127.0.0.1:8545')) { + console.error(`[NonceManager] ❌ КРИТИЧЕСКАЯ ОШИБКА: Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`); + throw new Error(`Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`); + } + const cacheKey = `${address}-${chainId}`; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { + console.log(`[NonceManager] Попытка ${attempt}: создаем JsonRpcProvider с rpcUrl: ${rpcUrl}`); const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, { polling: false, // Отключаем polling для более быстрого получения nonce staticNetwork: true @@ -204,8 +214,8 @@ class NonceManager { console.warn(`[NonceManager] networkLoader недоступен для chainId ${chainId}, используем fallback: ${error.message}`); } - // Всегда добавляем fallback RPC для надежности - const fallbackRPCs = this.getFallbackRPCs(chainId); + // Всегда добавляем fallback RPC для надежности ИЗ БАЗЫ ДАННЫХ + const fallbackRPCs = await this.getFallbackRPCs(chainId); for (const fallbackRpc of fallbackRPCs) { if (!rpcUrls.includes(fallbackRpc)) { rpcUrls.push(fallbackRpc); @@ -217,34 +227,31 @@ class NonceManager { } /** - * Получить список fallback RPC для сети + * Получить список fallback RPC для сети ИЗ БАЗЫ ДАННЫХ * @param {number} chainId - ID сети - * @returns {Array} - Массив RPC URL + * @returns {Array} - Массив RPC URL из базы данных */ - getFallbackRPCs(chainId) { - const fallbackRPCs = { - 1: [ // Mainnet - 'https://eth.llamarpc.com', - 'https://rpc.ankr.com/eth', - 'https://ethereum.publicnode.com' - ], - 11155111: [ // Sepolia - 'https://rpc.sepolia.org', - process.env.SEPOLIA_INFURA_URL || 'https://sepolia.infura.io/v3/YOUR_INFURA_KEY' - ], - 17000: [ // Holesky - 'https://ethereum-holesky.publicnode.com', - process.env.HOLESKY_INFURA_URL || 'https://holesky.infura.io/v3/YOUR_INFURA_KEY' - ], - 421614: [ // Arbitrum Sepolia - 'https://sepolia-rollup.arbitrum.io/rpc' - ], - 84532: [ // Base Sepolia - 'https://sepolia.base.org' - ] - }; - - return fallbackRPCs[chainId] || []; + async getFallbackRPCs(chainId) { + try { + // Получаем ВСЕ RPC провайдеры для данной сети из базы данных + const rpcService = require('../services/rpcProviderService'); + const providers = await rpcService.getAllRpcProviders(); + + // Фильтруем по chain_id + const networkProviders = providers.filter(p => p.chain_id === chainId); + + if (networkProviders.length === 0) { + console.warn(`[NonceManager] Нет RPC провайдеров в базе данных для chain_id: ${chainId}`); + return []; + } + + // Возвращаем только RPC URL из базы данных + return networkProviders.map(p => p.rpc_url).filter(url => url); + + } catch (error) { + console.error(`[NonceManager] Ошибка получения RPC из базы данных для chain_id ${chainId}:`, error); + return []; + } } /** @@ -276,7 +283,7 @@ class NonceManager { const cacheKey = `${address}-${chainId}`; try { - const provider = new ethers.JsonRpcProvider(rpcUrl); + const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(chainId)); const networkNonce = await provider.getTransactionCount(address, 'pending'); // Принудительно обновляем кэш diff --git a/backend/utils/proxyManager.js b/backend/utils/proxyManager.js new file mode 100644 index 0000000..0eb1433 --- /dev/null +++ b/backend/utils/proxyManager.js @@ -0,0 +1,105 @@ +/** + * Менеджер прокси настроек для RPC провайдеров + * Copyright (c) 2024-2025 Тарабанов Александр Викторович + */ + +const rpcProviderService = require('../services/rpcProviderService'); + +class ProxyManager { + constructor() { + this.initialized = false; + } + + /** + * Инициализация менеджера прокси + */ + async initialize() { + if (this.initialized) return; + + try { + await this.configureNoProxyFromRpcProviders(); + this.initialized = true; + console.log('[ProxyManager] ✅ Инициализация завершена'); + } catch (error) { + console.error('[ProxyManager] ❌ Ошибка инициализации:', error.message); + throw error; + } + } + + /** + * Настройка NO_PROXY на основе RPC провайдеров из базы данных + */ + async configureNoProxyFromRpcProviders() { + try { + const providers = await rpcProviderService.getAllRpcProviders(); + + const rpcDomains = providers + .map(provider => provider.rpc_url) + .filter(url => url && url.startsWith('http')) + .map(url => { + try { + const urlObj = new URL(url); + return urlObj.hostname; + } catch (e) { + console.warn(`[ProxyManager] Неверный URL: ${url}`, e.message); + return null; + } + }) + .filter(hostname => hostname) + .filter((hostname, index, array) => array.indexOf(hostname) === index); // убираем дубликаты + + if (rpcDomains.length > 0) { + const existingNoProxy = process.env.NO_PROXY || ''; + + // Добавляем RPC домены к существующему NO_PROXY + const newDomains = rpcDomains.filter(domain => !existingNoProxy.includes(domain)); + + if (newDomains.length > 0) { + process.env.NO_PROXY = existingNoProxy ? `${existingNoProxy},${newDomains.join(',')}` : newDomains.join(','); + console.log('[ProxyManager] ✅ Добавлены RPC домены в NO_PROXY:', newDomains.join(', ')); + console.log('[ProxyManager] 📋 Обновленный NO_PROXY:', process.env.NO_PROXY); + } else { + console.log('[ProxyManager] ℹ️ Все RPC домены уже в NO_PROXY'); + } + } else { + console.warn('[ProxyManager] ⚠️ Не найдено RPC провайдеров для настройки NO_PROXY'); + } + } catch (error) { + console.error('[ProxyManager] ❌ Не удалось загрузить RPC провайдеры для NO_PROXY:', error.message); + throw error; + } + } + + /** + * Проверить текущие настройки прокси + */ + getProxyStatus() { + return { + httpProxy: process.env.HTTP_PROXY || null, + httpsProxy: process.env.HTTPS_PROXY || null, + noProxy: process.env.NO_PROXY || null, + initialized: this.initialized + }; + } + + /** + * Принудительно обновить настройки NO_PROXY + */ + async refresh() { + this.initialized = false; + await this.initialize(); + } +} + +// Создаем singleton +const proxyManager = new ProxyManager(); + +module.exports = { + ProxyManager, + proxyManager, + // Экспортируем методы для удобства + initialize: () => proxyManager.initialize(), + configureNoProxyFromRpcProviders: () => proxyManager.configureNoProxyFromRpcProviders(), + getProxyStatus: () => proxyManager.getProxyStatus(), + refresh: () => proxyManager.refresh() +}; diff --git a/backend/utils/rpcConnectionManager.js b/backend/utils/rpcConnectionManager.js index 74578e3..e1af810 100644 --- a/backend/utils/rpcConnectionManager.js +++ b/backend/utils/rpcConnectionManager.js @@ -5,6 +5,7 @@ const { ethers } = require('ethers'); const logger = require('./logger'); +const rpcService = require('../services/rpcProviderService'); class RPCConnectionManager { constructor() { @@ -19,13 +20,26 @@ class RPCConnectionManager { /** * Создает RPC соединение с retry логикой - * @param {string} rpcUrl - URL RPC + * @param {number} chainId - ID цепочки * @param {string} privateKey - Приватный ключ * @param {Object} options - Опции соединения * @returns {Promise} - {provider, wallet, network} */ - async createConnection(rpcUrl, privateKey, options = {}) { + async createConnection(chainId, privateKey, options = {}) { const config = { ...this.retryConfig, ...options }; + const rpcUrl = await rpcService.getRpcUrlByChainId(chainId); + logger.info(`[RPC_MANAGER] Получен RPC URL для chainId ${chainId}: ${rpcUrl}`); + + // КРИТИЧЕСКАЯ ПРОВЕРКА: если rpcUrl содержит 127.0.0.1:8545, это ошибка! + if (rpcUrl && rpcUrl.includes('127.0.0.1:8545')) { + logger.error(`[RPC_MANAGER] ❌ КРИТИЧЕСКАЯ ОШИБКА: Получен неправильный RPC URL: ${rpcUrl} для chainId ${chainId}`); + throw new Error(`Получен неправильный RPC URL: ${rpcUrl} для chainId ${chainId}`); + } + + if (!rpcUrl) { + throw new Error(`RPC URL не найден для chainId ${chainId}`); + } + const connectionKey = `${rpcUrl}_${privateKey}`; // Проверяем кэш @@ -33,7 +47,8 @@ class RPCConnectionManager { const cached = this.connections.get(connectionKey); if (Date.now() - cached.timestamp < 60000) { // 1 минута кэш logger.info(`[RPC_MANAGER] Используем кэшированное соединение: ${rpcUrl}`); - return cached.connection; + // Убеждаемся, что кэшированное соединение содержит rpcUrl + return { ...cached.connection, rpcUrl }; } } @@ -56,7 +71,7 @@ class RPCConnectionManager { const wallet = new ethers.Wallet(privateKey, provider); - const connection = { provider, wallet, network }; + const connection = { provider, wallet, network, rpcUrl }; // Кэшируем соединение this.connections.set(connectionKey, { @@ -84,21 +99,21 @@ class RPCConnectionManager { /** * Создает множественные RPC соединения с обработкой ошибок - * @param {Array} rpcUrls - Массив RPC URL + * @param {Array} chainIds - Массив chain ID * @param {string} privateKey - Приватный ключ * @param {Object} options - Опции соединения * @returns {Promise} - Массив успешных соединений */ - async createMultipleConnections(rpcUrls, privateKey, options = {}) { - logger.info(`[RPC_MANAGER] Создаем ${rpcUrls.length} RPC соединений...`); + async createMultipleConnections(chainIds, privateKey, options = {}) { + logger.info(`[RPC_MANAGER] Создаем ${chainIds.length} RPC соединений...`); - const connectionPromises = rpcUrls.map(async (rpcUrl, index) => { + const connectionPromises = chainIds.map(async (chainId, index) => { try { - const connection = await this.createConnection(rpcUrl, privateKey, options); - return { index, rpcUrl, ...connection, success: true }; + const connection = await this.createConnection(chainId, privateKey, options); + return { index, chainId, ...connection, success: true }; } catch (error) { - logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: ${rpcUrl} - ${error.message}`); - return { index, rpcUrl, error: error.message, success: false }; + logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: chainId ${chainId} - ${error.message}`); + return { index, chainId, error: error.message, success: false }; } }); @@ -106,10 +121,10 @@ class RPCConnectionManager { const successful = results.filter(r => r.success); const failed = results.filter(r => !r.success); - logger.info(`[RPC_MANAGER] ✅ Успешных соединений: ${successful.length}/${rpcUrls.length}`); + logger.info(`[RPC_MANAGER] ✅ Успешных соединений: ${successful.length}/${chainIds.length}`); if (failed.length > 0) { logger.warn(`[RPC_MANAGER] ⚠️ Неудачных соединений: ${failed.length}`); - failed.forEach(f => logger.warn(`[RPC_MANAGER] - ${f.rpcUrl}: ${f.error}`)); + failed.forEach(f => logger.warn(`[RPC_MANAGER] - ChainId ${f.chainId}: ${f.error}`)); } if (successful.length === 0) { @@ -183,13 +198,25 @@ class RPCConnectionManager { 'ENOTFOUND', 'ETIMEDOUT', 'RPC timeout', - 'Transaction timeout' + 'Transaction timeout', + 'ECONNREFUSED', + 'ENETUNREACH', + 'EHOSTUNREACH' ]; const errorMessage = error.message.toLowerCase(); - return retryableErrors.some(retryableError => + const isRetryable = retryableErrors.some(retryableError => errorMessage.includes(retryableError.toLowerCase()) ); + + // Логируем информацию об ошибке для диагностики + if (isRetryable) { + logger.warn(`[RPC_MANAGER] Повторяемая ошибка: ${error.message}`); + } else { + logger.error(`[RPC_MANAGER] Неповторяемая ошибка: ${error.message}`); + } + + return isRetryable; } // getNonceWithRetry функция удалена - используем nonceManager.getNonceWithRetry() вместо этого diff --git a/backend/wsHub.js b/backend/wsHub.js index 7e237c7..9e074d1 100644 --- a/backend/wsHub.js +++ b/backend/wsHub.js @@ -554,34 +554,56 @@ module.exports = { // Обработчик запроса балансов токенов async function handleTokenBalancesRequest(ws, address, userId) { try { - // console.log(`[WebSocket] Запрос балансов для адреса: ${address}`); // Убрано избыточное логирование + console.log(`[WebSocket] Запрос балансов для адреса: ${address}`); // Получаем балансы через отдельный сервис без зависимостей от wsHub const balances = await tokenBalanceService.getUserTokenBalances(address); + console.log(`[WebSocket] Получены балансы для ${address}:`, balances); + console.log(`[WebSocket] Количество токенов:`, balances?.length || 0); + // Отправляем ответ клиенту - ws.send(JSON.stringify({ + const response = { type: 'token_balances_response', data: { address: address, balances: balances, timestamp: Date.now() } - })); + }; - // console.log(`[WebSocket] Отправлены балансы для ${address}:`, balances.length, 'токенов'); // Убрано избыточное логирование + console.log(`[WebSocket] Отправляем ответ:`, JSON.stringify(response, null, 2)); + ws.send(JSON.stringify(response)); } catch (error) { console.error('[WebSocket] Ошибка при получении балансов:', error); + // Определяем тип ошибки для лучшей диагностики + let errorType = 'Неизвестная ошибка'; + const errorMessage = error.message || error.toString(); + + if (errorMessage.includes('timeout') || errorMessage.includes('TIMEOUT')) { + errorType = 'Таймаут соединения - возможно, нужен VPN'; + } else if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND')) { + errorType = 'Не удается подключиться к RPC провайдеру'; + } else if (errorMessage.includes('TLS') || errorMessage.includes('socket disconnected')) { + errorType = 'Проблема с TLS соединением - проверьте VPN'; + } else if (errorMessage.includes('NETWORK_ERROR')) { + errorType = 'Ошибка сети - проверьте интернет-соединение'; + } + // Отправляем ошибку клиенту - ws.send(JSON.stringify({ + const errorResponse = { type: 'token_balances_error', data: { address: address, - error: error.message, + error: errorType, + errorDetails: errorMessage, timestamp: Date.now() } - })); + }; + + console.log('[WebSocket] Отправляем ошибку клиенту:', JSON.stringify(errorResponse, null, 2)); + ws.send(JSON.stringify(errorResponse)); } } diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue index d83d6d8..62423ae 100644 --- a/frontend/src/components/Sidebar.vue +++ b/frontend/src/components/Sidebar.vue @@ -121,10 +121,13 @@
-
+
{{ token.tokenName }} {{ token.network }} - {{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }} + + ❌ {{ token.error }} + + {{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}
@@ -456,6 +459,22 @@ h3 { font-size: var(--font-size-sm); } +/* Стили для ошибок токенов */ +.token-balance-row.token-error { + background-color: rgba(255, 0, 0, 0.1); + border: 1px solid rgba(255, 0, 0, 0.3); + border-radius: var(--radius-sm); + padding: var(--spacing-xs); +} + +.token-error-message { + color: var(--color-danger); + font-size: var(--font-size-xs); + font-weight: bold; + flex: 1; + cursor: help; +} + /* Медиа-запросы для адаптивности */ @media screen and (min-width: 1200px) { .wallet-sidebar { diff --git a/frontend/src/composables/useAuth.js b/frontend/src/composables/useAuth.js index b87756e..a7d40c1 100644 --- a/frontend/src/composables/useAuth.js +++ b/frontend/src/composables/useAuth.js @@ -12,6 +12,7 @@ import { ref, onMounted, onUnmounted, provide, inject } from 'vue'; import axios from '../api/axios'; +import eventBus from '../utils/eventBus'; // === SINGLETON STATE === const isAuthenticated = ref(false); @@ -243,15 +244,14 @@ const updateAuth = async ({ window.dispatchEvent(new CustomEvent('refresh-application-data')); } - window.dispatchEvent(new CustomEvent('auth-state-changed', { - detail: { - authenticated: isAuthenticated.value, - authType: authType.value, - userId: userId.value, - address: address.value, - userAccessLevel: userAccessLevel.value - } - })); + // Отправляем событие через eventBus (централизованный подход) + eventBus.emit('auth-state-changed', { + authenticated: isAuthenticated.value, + authType: authType.value, + userId: userId.value, + address: address.value, + userAccessLevel: userAccessLevel.value + }); } // Если пользователь только что аутентифицировался или сменил аккаунт, diff --git a/frontend/src/composables/useTokenBalancesWebSocket.js b/frontend/src/composables/useTokenBalancesWebSocket.js index d8bd5e4..b4c8ba1 100644 --- a/frontend/src/composables/useTokenBalancesWebSocket.js +++ b/frontend/src/composables/useTokenBalancesWebSocket.js @@ -51,9 +51,24 @@ export function useTokenBalancesWebSocket() { // Обработчик ошибки const handleTokenBalancesError = (data) => { - console.error('[useTokenBalancesWebSocket] Ошибка получения балансов:', data.error); + console.error('[useTokenBalancesWebSocket] Ошибка получения балансов:', data); isLoadingTokens.value = false; - tokenBalances.value = []; + + // Создаем объект с информацией об ошибке для отображения пользователю + const errorInfo = { + network: 'unknown', + tokenAddress: 'error', + tokenName: 'Ошибка получения балансов', + symbol: 'ERROR', + balance: '0', + minBalance: '0', + readonlyThreshold: 1, + editorThreshold: 1, + error: data.error || 'Неизвестная ошибка', + errorDetails: data.errorDetails || data.error + }; + + tokenBalances.value = [errorInfo]; }; // Обработчик обновления балансов diff --git a/frontend/src/views/settings/DleDeployFormView.vue b/frontend/src/views/settings/DleDeployFormView.vue index f920c54..f35b1d0 100644 --- a/frontend/src/views/settings/DleDeployFormView.vue +++ b/frontend/src/views/settings/DleDeployFormView.vue @@ -906,6 +906,7 @@ import { useAuthContext } from '@/composables/useAuth'; import { usePermissions } from '@/composables/usePermissions'; import api from '@/api/axios'; import DeploymentWizard from '@/components/deployment/DeploymentWizard.vue'; +import eventBus from '@/utils/eventBus'; const router = useRouter(); // Нормализация приватного ключа: убираем пробелы/"0x", посторонние символы, @@ -924,19 +925,10 @@ function normalizePrivateKey(raw) { const { address } = useAuthContext(); const { canManageSettings } = usePermissions(); -// Подписываемся на централизованные события очистки и обновления данных -onMounted(() => { - window.addEventListener('clear-application-data', () => { - console.log('[DleDeployFormView] Clearing DLE deploy data'); - // Очищаем данные при выходе из системы - // DleDeployFormView не нуждается в очистке данных - }); - - window.addEventListener('refresh-application-data', () => { - console.log('[DleDeployFormView] Refreshing DLE deploy data'); - checkAdminTokens(); // Обновляем данные при входе в систему - }); -}); +// Обработчики событий будут определены после функций clearAllData и resetUIState + +// Подписка на события авторизации (как в других файлах проекта) +let unsubscribe = null; // Состояние для проверки админских токенов const adminTokenCheck = ref({ @@ -945,6 +937,28 @@ const adminTokenCheck = ref({ error: null }); +// Обработка события изменения авторизации +const handleAuthEvent = (eventData) => { + console.log('[DleDeployFormView] Получено событие изменения авторизации:', eventData); + + // Если пользователь отключился, сбрасываем все данные формы + if (!eventData.authenticated) { + console.log('[DleDeployFormView] User disconnected, clearing form data'); + clearAllData(); + resetUIState(); + } else { + // При подключении обновляем проверку токенов + checkAdminTokens(); + } +}; + +// Watcher для отслеживания изменений в правах доступа +watch(canManageSettings, (newValue, oldValue) => { + console.log('[DleDeployFormView] canManageSettings changed:', { oldValue, newValue }); + // При изменении прав обновляем локальное состояние + adminTokenCheck.value.canManageSettings = newValue; +}, { immediate: true }); + // Основные настройки DLE const dleSettings = reactive({ // Юрисдикция @@ -1573,12 +1587,17 @@ const clearAllData = () => { // Очищаем также поиск адресов и флаги автовыбора postalCodeInput.value = ''; searchResults.value = []; + isSearchingAddress.value = false; autoSelectedOktmo.value = false; lastApiResult.value = null; // Сбрасываем выбранные уровни ОКВЭД selectedOkvedLevel1.value = ''; selectedOkvedLevel2.value = ''; + selectedOkvedLevel3.value = ''; + selectedOkvedLevel4.value = ''; + currentSelectedOkvedCode.value = ''; + currentSelectedOkvedText.value = ''; // Очищаем мульти-чейн состояние selectedNetworks.value = []; @@ -1591,10 +1610,79 @@ const clearAllData = () => { Object.keys(keyValidation).forEach(key => delete keyValidation[key]); showUnifiedKey.value = false; + // Очищаем настройки деплоя + etherscanApiKey.value = ''; + unifiedScanKeyVisible.value = false; + autoVerifyAfterDeploy.value = true; + showDeploymentWizard.value = false; + deployedDLEAddress.value = ''; + // Очищаем localStorage clearStoredData(); }; +// Сброс состояния UI компонентов +const resetUIState = () => { + // Сбрасываем состояние загрузки + isLoadingCountries.value = false; + isLoadingRussianClassifiers.value = false; + isLoadingNetworks.value = false; + isLoadingOkvedLevel1.value = false; + isLoadingOkvedLevel2.value = false; + isLoadingOkvedLevel3.value = false; + isLoadingOkvedLevel4.value = false; + isLoadingKppCodes.value = false; + + // Сбрасываем состояние админских токенов + adminTokenCheck.value = { + isLoading: false, + canManageSettings: false, + error: null + }; + + // Очищаем файл логотипа + logoFile.value = null; + logoPreviewUrl.value = ''; + ensDomain.value = ''; + ensResolvedUrl.value = ''; + + // Сбрасываем состояние видимости ключей + showPrivateKey.value = false; + showUnifiedKey.value = false; + + console.log('[DleDeployFormView] UI state reset completed'); +}; + +// Обработчики событий для очистки и обновления данных +const handleClearApplicationData = () => { + console.log('[DleDeployFormView] Clearing DLE deploy data'); + // Очищаем все данные формы при выходе из системы + clearAllData(); + // Сбрасываем состояние UI + resetUIState(); +}; + +// handleRefreshApplicationData будет определен после checkAdminTokens + +// Подписываемся на централизованные события очистки и обновления данных +onMounted(() => { + window.addEventListener('clear-application-data', handleClearApplicationData); + window.addEventListener('refresh-application-data', handleRefreshApplicationData); + // Подписка на события авторизации + unsubscribe = eventBus.on('auth-state-changed', handleAuthEvent); +}); + +onUnmounted(() => { + // Отписка от события при удалении компонента + if (unsubscribe) { + unsubscribe(); + } + + // Удаляем слушатели событий window + window.removeEventListener('clear-application-data', handleClearApplicationData); + window.removeEventListener('refresh-application-data', handleRefreshApplicationData); +}); + // (Старые функции ОКВЭД удалены - заменены каскадной системой) // Поиск по почтовому индексу (по кнопке) @@ -2382,7 +2470,12 @@ onUnmounted(() => { }); // Watcher для автоматического обновления адреса первого партнера при подключении кошелька -watch(address, (newAddress) => { +watch(address, (newAddress, oldAddress) => { + console.log('[DleDeployFormView] Address changed:', { oldAddress, newAddress }); + + // Обновляем состояние при изменении адреса (подключение/отключение кошелька) + checkAdminTokens(); + if (newAddress && dleSettings.partners[0]) { // Подставляем адрес, если поле пустое или пользователь только что подключил кошелек if (!dleSettings.partners[0].address) { @@ -2394,8 +2487,16 @@ watch(address, (newAddress) => { // Функция проверки админских токенов const checkAdminTokens = async () => { + console.log('[DleDeployFormView] checkAdminTokens called, address:', address.value); + + // Небольшая задержка чтобы дать время useAuth обновить состояние + await new Promise(resolve => setTimeout(resolve, 100)); + + // Обновляем локальное состояние на основе текущих прав из usePermissions + adminTokenCheck.value.canManageSettings = canManageSettings.value; + if (!address.value) { - adminTokenCheck.value = { isLoading: false, canManageSettings: false, error: 'Кошелек не подключен' }; + adminTokenCheck.value = { ...adminTokenCheck.value, isLoading: false, error: 'Кошелек не подключен' }; return; } @@ -2405,8 +2506,8 @@ const checkAdminTokens = async () => { const response = await api.get(`/dle-v2/check-admin-tokens?address=${address.value}`); if (response.data.success) { - adminTokenCheck.value = { ...adminTokenCheck.value, canManageSettings: response.data.data.userAccessLevel.hasAccess }; console.log('Проверка админских токенов:', response.data.data); + // Не перезаписываем canManageSettings, так как это управляется usePermissions } else { adminTokenCheck.value = { ...adminTokenCheck.value, error: response.data.message || 'Ошибка проверки токенов' }; } @@ -2418,6 +2519,12 @@ const checkAdminTokens = async () => { } }; +// Определяем handleRefreshApplicationData после checkAdminTokens +const handleRefreshApplicationData = () => { + console.log('[DleDeployFormView] Refreshing DLE deploy data'); + checkAdminTokens(); // Обновляем данные при входе в систему +}; + // Функции для работы с партнерами const addPartner = () => { dleSettings.partners.push({ address: '', amount: 1 });