feat: новая функция

This commit is contained in:
2025-10-15 21:43:18 +03:00
parent 0e028bc722
commit e0300480e1
32 changed files with 972 additions and 439 deletions

40
DEPLOYMENT_FIXES.md Normal file
View File

@@ -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`

View File

@@ -330,6 +330,12 @@ const initializeDbSettingsService = async () => {
// Инициализируем сервис настроек БД при запуске // Инициализируем сервис настроек БД при запуске
if (process.env.NODE_ENV !== 'migration') { if (process.env.NODE_ENV !== 'migration') {
initializeDbSettingsService(); initializeDbSettingsService();
// Загружаем RPC URL из базы данных
const { loadRpcFromDatabase } = require('./utils/loadRpcFromDatabase');
loadRpcFromDatabase().catch(error => {
logger.error('[App] Ошибка загрузки RPC URL из базы данных:', error);
});
} }
module.exports = { app, nonceStore }; module.exports = { app, nonceStore };

View File

@@ -29,54 +29,8 @@ function getNetworks() {
// console.log удален - может мешать flatten // console.log удален - может мешать flatten
// Базовые сети // Базовые сети - УБРАНО, используем только базу данных
const baseNetworks = { 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] : []
}
};
// Если есть supported_chain_ids, фильтруем только нужные сети // Если есть supported_chain_ids, фильтруем только нужные сети
if (supportedChainIds.length > 0) { if (supportedChainIds.length > 0) {
@@ -104,35 +58,9 @@ function getNetworks() {
} }
} }
// Функция для получения базовых сетей (fallback) // Функция для получения базовых сетей (fallback) - УБРАНО, используем только базу данных
function getBaseNetworks() { function getBaseNetworks() {
return { 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] : []
}
};
} }

View File

@@ -54,7 +54,7 @@ router.post('/read-dle-info', async (req, res) => {
if (!rpcUrl) { if (!rpcUrl) {
return res.status(500).json({ success: false, error: `RPC URL для сети ${targetChainId} не найден` }); 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); const code = await provider.getCode(dleAddress);
if (!code || code === '0x') { if (!code || code === '0x') {
return res.status(400).json({ success: false, error: `По адресу ${dleAddress} нет контракта в сети ${targetChainId}` }); 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 для чтения предложений (используем правильные функции из смарт-контракта) // ABI для чтения предложений (используем правильные функции из смарт-контракта)
const dleAbi = [ 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 для чтения информации о предложении // ABI для чтения информации о предложении
const dleAbi = [ 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 // ABI для проверки деактивации DLE
const dleAbi = [ 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 = [ const dleAbi = [
"function checkDeactivationProposalResult(uint256 _proposalId) public view returns (bool passed, bool quorumReached)" "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 = [ const dleAbi = [
"function deactivationProposalCounter() external view returns (uint256)", "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 wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [ 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 = [ const dleAbi = [
"function cancelProposal(uint256 _proposalId, string calldata reason) external" "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 = [ const dleAbi = [
"function getGovernanceParams() external view returns (uint256 quorumPct, uint256 chainId, uint256 supportedCount)" "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 = [ const dleAbi = [
"function getProposalState(uint256 _proposalId) public view returns (uint8 state)" "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 = [ const dleAbi = [
"function getProposalVotes(uint256 _proposalId) external view returns (uint256 forVotes, uint256 againstVotes, uint256 totalVotes, uint256 quorumRequired)" "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 = [ const dleAbi = [
"function getProposalsCount() external view returns (uint256)" "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 = [ const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)" "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 = [ const dleAbi = [
"function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256)" "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 = [ const dleAbi = [
"function getQuorumAt(uint256 timepoint) external view returns (uint256)" "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 = [ const dleAbi = [
"function balanceOf(address account) external view returns (uint256)" "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 = [ const dleAbi = [
"function totalSupply() external view returns (uint256)" "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 = [ const dleAbi = [
"function isActive() external view returns (bool)" "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 = [ const dleAbi = [
"function totalSupply() external view returns (uint256)", "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 = [ const dleAbi = [
"function getProposalsCount() external view returns (uint256)", "function getProposalsCount() external view returns (uint256)",

View File

@@ -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 = [ const dleAbi = [
"function totalSupply() external view returns (uint256)", "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 = [ const dleAbi = [
"function getProposalsCount() external view returns (uint256)", "function getProposalsCount() external view returns (uint256)",

View File

@@ -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 // ABI для чтения данных DLE
const dleAbi = [ 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 = [ const dleAbi = [
"function getGovernanceParams() external view returns (uint256 quorumPct, uint256 chainId, uint256 supportedCount)" "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 = [ const dleAbi = [
"function isActive() external view returns (bool)" "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 // ABI для проверки деактивации DLE
const dleAbi = [ const dleAbi = [

View File

@@ -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 = [ const dleAbi = [
"function getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))", "function 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))",

View File

@@ -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 = [ const dleAbi = [
"function isModuleActive(bytes32 _moduleId) external view returns (bool)" "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 = [ const dleAbi = [
"function getModuleAddress(bytes32 _moduleId) external view returns (address)" "function getModuleAddress(bytes32 _moduleId) external view returns (address)"
@@ -348,7 +348,13 @@ router.post('/prepare-initialize-modules-all-networks', async (req, res) => {
const results = []; const results = [];
for (const network of supportedNetworks) { for (const network of supportedNetworks) {
try { 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( const dle = new ethers.Contract(
dleAddress, dleAddress,
[ [
@@ -576,14 +582,16 @@ router.post('/get-all-modules', async (req, res) => {
return networks[chainId] || `Chain ${chainId}`; return networks[chainId] || `Chain ${chainId}`;
} }
function getFallbackRpcUrl(chainId) { async function getFallbackRpcUrl(chainId) {
const fallbackUrls = { try {
11155111: process.env.SEPOLIA_RPC_URL || 'https://eth-sepolia.nodereal.io/v1/YOUR_NODEREAL_KEY', // Получаем RPC URL из базы данных
17000: 'https://ethereum-holesky.publicnode.com', const rpcService = require('../services/rpcProviderService');
421614: 'https://sepolia-rollup.arbitrum.io/rpc', const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
84532: 'https://sepolia.base.org' return rpcUrl;
}; } catch (error) {
return fallbackUrls[chainId] || null; console.error(`[DLE Modules] Ошибка получения RPC из базы данных для chain_id ${chainId}:`, error);
return null;
}
} }
function getEtherscanUrl(chainId) { function getEtherscanUrl(chainId) {
@@ -621,48 +629,19 @@ router.post('/get-all-modules', async (req, res) => {
const supportedChainIds = params.supportedChainIds || []; const supportedChainIds = params.supportedChainIds || [];
const rpcUrls = params.rpcUrls || params.rpc_urls || {}; const rpcUrls = params.rpcUrls || params.rpc_urls || {};
supportedNetworks = supportedChainIds.map((chainId, index) => ({ supportedNetworks = await Promise.all(supportedChainIds.map(async (chainId, index) => ({
chainId: Number(chainId), chainId: Number(chainId),
networkName: getNetworkName(Number(chainId)), networkName: getNetworkName(Number(chainId)),
rpcUrl: rpcUrls[chainId] || getFallbackRpcUrl(chainId), rpcUrl: rpcUrls[chainId] || await getFallbackRpcUrl(chainId),
etherscanUrl: getEtherscanUrl(chainId), etherscanUrl: getEtherscanUrl(chainId),
networkIndex: index networkIndex: index
})); })));
} }
await deployParamsService.close(); await deployParamsService.close();
} catch (error) { } catch (error) {
console.error('❌ Ошибка получения параметров деплоя:', error); console.error('❌ Ошибка получения параметров деплоя:', error);
// Fallback для совместимости // НЕ показываем fallback цепочки - только те, что выбрал пользователь
supportedNetworks = [ 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
}
];
} }
res.json({ 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; 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 = [ const dleAbi = [
"function createRemoveModuleProposal(string memory _description, uint256 _duration, bytes32 _moduleId, uint256 _chainId) external returns (uint256)" "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 для модуля // Получаем ABI и bytecode для модуля
const { ethers } = require('ethers'); 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); const code = await provider.getCode(moduleAddress);
@@ -1389,7 +1368,11 @@ router.post('/check-modules-status', async (req, res) => {
// Проверяем первую доступную сеть // Проверяем первую доступную сеть
const network = supportedNetworks[0]; 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 = [ const dleAbi = [
"function initializer() external view returns (address)", "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})`); console.log(`[DLE Modules] Инициализация модулей в сети: ${network.networkName} (${network.chainId})`);
try { 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 wallet = new ethers.Wallet(privateKey, provider);
const dle = new ethers.Contract(dleAddress, dleAbi, wallet); const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
@@ -1723,7 +1711,12 @@ router.post('/verify-modules-all-networks', async (req, res) => {
}; };
try { 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); const dle = new ethers.Contract(dleAddress, dleAbi, provider);
for (const [moduleKey, moduleId] of Object.entries(moduleIds)) { 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}`); 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); 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}`); throw new Error(`RPC URL не найден для сети ${chainId}`);
} }
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [ const dleAbi = [
"function getModuleAddress(bytes32 _moduleId) external view returns (address)", "function getModuleAddress(bytes32 _moduleId) external view returns (address)",
@@ -2179,7 +2172,7 @@ router.post('/deploy-module-all-networks', async (req, res) => {
try { try {
const result = await executeWithRetries( const result = await executeWithRetries(
async () => { 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 wallet = new ethers.Wallet(privateKey, provider);
// Используем NonceManager для правильного управления nonce // Используем NonceManager для правильного управления nonce
@@ -2558,7 +2551,7 @@ router.post('/verify-module-all-networks', async (req, res) => {
try { try {
const result = await executeWithRetries( const result = await executeWithRetries(
async () => { async () => {
const provider = new ethers.JsonRpcProvider(network.rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(network.chainId));
const dle = new ethers.Contract(dleAddress, [ const dle = new ethers.Contract(dleAddress, [
"function getModuleAddress(bytes32 _moduleId) external view returns (address)" "function getModuleAddress(bytes32 _moduleId) external view returns (address)"
], provider); ], provider);
@@ -2698,7 +2691,7 @@ router.post('/initialize-module-all-networks', async (req, res) => {
try { try {
const result = await executeWithRetries( const result = await executeWithRetries(
async () => { 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 wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [ const dleAbi = [
@@ -2826,7 +2819,7 @@ router.post('/final-deployment-check', async (req, res) => {
throw new Error(`RPC URL не найден для сети ${chainId}`); throw new Error(`RPC URL не найден для сети ${chainId}`);
} }
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [ const dleAbi = [
"function name() external view returns (string)", "function name() external view returns (string)",
@@ -3025,7 +3018,7 @@ router.post('/get-deployment-status', async (req, res) => {
const result = await executeWithRetries( const result = await executeWithRetries(
async () => { async () => {
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); 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); const dleCode = await provider.getCode(dleAddress);
return dleCode !== '0x'; return dleCode !== '0x';
}, },
@@ -3045,7 +3038,7 @@ router.post('/get-deployment-status', async (req, res) => {
const verificationResult = await executeWithRetries( const verificationResult = await executeWithRetries(
async () => { async () => {
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); 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); const code = await provider.getCode(dleAddress);
return code !== '0x' && code.length > 2; return code !== '0x' && code.length > 2;
@@ -3102,7 +3095,7 @@ router.post('/get-deployment-status', async (req, res) => {
const result = await executeWithRetries( const result = await executeWithRetries(
async () => { async () => {
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); 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, [ const dle = new ethers.Contract(dleAddress, [
"function getModuleAddress(bytes32 _moduleId) external view returns (address)", "function getModuleAddress(bytes32 _moduleId) external view returns (address)",
"function isModuleActive(bytes32 _moduleId) external view returns (bool)" "function isModuleActive(bytes32 _moduleId) external view returns (bool)"
@@ -3129,7 +3122,7 @@ router.post('/get-deployment-status', async (req, res) => {
const verificationResult = await executeWithRetries( const verificationResult = await executeWithRetries(
async () => { async () => {
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); 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); const moduleCode = await provider.getCode(moduleAddress);
return moduleCode !== '0x'; return moduleCode !== '0x';
}, },
@@ -3168,7 +3161,7 @@ router.post('/get-deployment-status', async (req, res) => {
const result = await executeWithRetries( const result = await executeWithRetries(
async () => { async () => {
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(supportedNetworks[0].chainId); 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, [ const dle = new ethers.Contract(dleAddress, [
], provider); ], provider);

View File

@@ -63,10 +63,10 @@ router.post('/get-multichain-contracts', async (req, res) => {
// Если не найден в параметрах, используем fallback // Если не найден в параметрах, используем fallback
if (!rpcUrl) { if (!rpcUrl) {
const fallbackConfigs = { const fallbackConfigs = {
'11155111': 'https://1rpc.io/sepolia', '11155111': null,
'17000': 'https://ethereum-holesky.publicnode.com', '17000': null,
'421614': 'https://sepolia-rollup.arbitrum.io/rpc', '421614': null,
'84532': 'https://sepolia.base.org' '84532': null
}; };
rpcUrl = fallbackConfigs[targetChainId]; rpcUrl = fallbackConfigs[targetChainId];
} }
@@ -86,7 +86,7 @@ router.post('/get-multichain-contracts', async (req, res) => {
} }
try { try {
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const contractCode = await provider.getCode(originalContract); const contractCode = await provider.getCode(originalContract);
if (contractCode && contractCode !== '0x') { if (contractCode && contractCode !== '0x') {

View File

@@ -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 = [ 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)", "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} не найден`); 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 wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [ const dleAbi = [

View File

@@ -60,7 +60,7 @@ router.post('/get-proposals', async (req, res) => {
return; return;
} }
if (rpcUrl) { if (rpcUrl) {
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [ const dleAbi = [
"function getSupportedChainCount() external view returns (uint256)", "function getSupportedChainCount() external view returns (uint256)",
"function getSupportedChainId(uint256 _index) external view returns (uint256)" "function getSupportedChainId(uint256 _index) external view returns (uint256)"
@@ -97,7 +97,7 @@ router.post('/get-proposals', async (req, res) => {
continue; continue;
} }
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// ABI для чтения предложений (используем getProposalSummary для мультиконтрактов) // ABI для чтения предложений (используем getProposalSummary для мультиконтрактов)
const dleAbi = [ 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 для чтения информации о предложении // ABI для чтения информации о предложении
const dleAbi = [ 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 = [ const dleAbi = [
"function getProposalState(uint256 _proposalId) public view returns (uint8 state)" "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 = [ const dleAbi = [
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)", "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 = [ const dleAbi = [
"function getProposalsCount() external view returns (uint256)" "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 = [ const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)" "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 = [ const dleAbi = [
"function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256)" "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 = [ const dleAbi = [
"function getQuorumAt(uint256 timepoint) external view returns (uint256)" "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 = [ const dleAbi = [
"function executeProposal(uint256 _proposalId) external" "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 = [ const dleAbi = [
"function cancelProposal(uint256 _proposalId, string calldata reason) external" "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 = [ const dleAbi = [
"function getProposalsCount() external view returns (uint256)" "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 = [ const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)", "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 = [ const dleAbi = [
"function vote(uint256 _proposalId, bool _support) external" "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 // Функция hasVoted не существует в контракте DLE
console.log(`[DLE Proposals] Функция 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 секунд таймаут 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 секунд таймаут 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); const tx = await provider.getTransaction(transactionHash);

View File

@@ -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 = [ const dleAbi = [
"function balanceOf(address account) external view returns (uint256)" "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 = [ const dleAbi = [
"function totalSupply() external view returns (uint256)" "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 = [ const dleAbi = [
"function totalSupply() external view returns (uint256)", "function totalSupply() external view returns (uint256)",

View File

@@ -17,9 +17,23 @@ const express = require('express');
const router = express.Router(); const router = express.Router();
const { ethers } = require('ethers'); const { ethers } = require('ethers');
function getMainnetProvider() { async function getMainnetProvider() {
const url = process.env.MAINNET_RPC_URL || process.env.ETH_MAINNET_RPC || 'https://ethereum.publicnode.com'; try {
return new ethers.JsonRpcProvider(url); // Получаем 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 // GET /api/ens/avatar?name=vc-hb3-accelerator.eth
@@ -29,7 +43,7 @@ router.get('/avatar', async (req, res) => {
if (!name || !name.endsWith('.eth')) { if (!name || !name.endsWith('.eth')) {
return res.status(400).json({ success: false, message: 'ENS name is required (e.g., example.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); const url = await provider.getAvatar(name);
return res.json({ success: true, data: { url: url || null } }); return res.json({ success: true, data: { url: url || null } });
} catch (e) { } catch (e) {

View File

@@ -18,9 +18,11 @@ async function checkModules() {
const dleAddress = '0xCaa85e96a6929F0373442e31FD9888d985869EcE'; const dleAddress = '0xCaa85e96a6929F0373442e31FD9888d985869EcE';
// RPC URL для Sepolia // 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 контракта // ABI для DLE контракта
const dleAbi = [ const dleAbi = [

View File

@@ -179,27 +179,30 @@ async function verifyModuleAfterDeploy(chainId, contractAddress, moduleType, con
async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce, moduleInit, moduleType) { async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce, moduleInit, moduleType) {
const { ethers } = hre; const { ethers } = hre;
// Создаем временный провайдер для получения chainId
const tempProvider = new ethers.JsonRpcProvider(rpcUrl);
const network = await tempProvider.getNetwork();
const chainId = Number(network.chainId);
// Используем новый менеджер RPC с retry логикой // Используем новый менеджер RPC с retry логикой
const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, { const { provider, wallet, network: rpcNetwork } = await createRPCConnection(chainId, pk, {
maxRetries: 3, maxRetries: 3,
timeout: 30000 timeout: 30000
}); });
const net = network; const net = rpcNetwork;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploying ${moduleType}...`);
// 1) Используем NonceManager для правильного управления nonce // 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); let current = await nonceManager.getNonce(wallet.address, rpcUrl, chainId);
logger.info(`[MODULES_DBG] chainId=${chainId} current nonce=${current} target=${targetNonce}`); logger.info(`[MODULES_DBG] chainId=${chainId} current nonce=${current} target=${targetNonce}`);
if (current > 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) { 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 для более надежных транзакций // Используем burn address для более надежных транзакций
const burnAddress = "0x000000000000000000000000000000000000dEaD"; const burnAddress = "0x000000000000000000000000000000000000dEaD";
@@ -219,15 +222,15 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
gasLimit, gasLimit,
...overrides ...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 rpcManager = new RPCConnectionManager();
const { tx: txFill, receipt } = await rpcManager.sendTransactionWithRetry(wallet, txReq, { maxRetries: 3 }); 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=${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 nonce=${current} confirmed, hash=${txFill.hash}`);
sent = true; sent = true;
} catch (e) { } catch (e) {
lastErr = 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) { if (String(e?.message || '').toLowerCase().includes('intrinsic gas too low') && attempt < 2) {
gasLimit = 50000; 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) { if (String(e?.message || '').toLowerCase().includes('nonce too low') && attempt < 2) {
// Сбрасываем кэш и получаем актуальный nonce // Сбрасываем кэш и получаем актуальный nonce
nonceManager.resetNonce(wallet.address, Number(net.chainId)); nonceManager.resetNonce(wallet.address, chainId);
current = await provider.getTransactionCount(wallet.address, 'pending'); 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 больше целевого, это критическая ошибка // Если новый nonce больше целевого, это критическая ошибка
if (current > targetNonce) { 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; continue;
@@ -253,20 +256,20 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
} }
if (!sent) { 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'); throw lastErr || new Error('filler tx failed');
} }
current++; 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 { } 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 // 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); const feeOverrides = await getFeeOverrides(provider);
let gasLimit; 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); const fallbackGas = maxByBalance > 2_000_000n ? 2_000_000n : (maxByBalance < 500_000n ? 500_000n : maxByBalance);
gasLimit = est ? (est + est / 5n) : fallbackGas; 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 (_) { } catch (_) {
gasLimit = 1_000_000n; gasLimit = 1_000_000n;
} }
@@ -293,13 +296,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
from: wallet.address, from: wallet.address,
nonce: targetNonce 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); const existingCode = await provider.getCode(predictedAddress);
if (existingCode && existingCode !== '0x') { if (existingCode && existingCode !== '0x') {
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} already exists at predictedAddress, skip deploy`); logger.info(`[MODULES_DBG] chainId=${chainId} ${moduleType} already exists at predictedAddress, skip deploy`);
return { address: predictedAddress, chainId: Number(net.chainId) }; return { address: predictedAddress, chainId: chainId };
} }
// Деплоим модуль с retry логикой для обработки race conditions // Деплоим модуль с retry логикой для обработки race conditions
@@ -312,8 +315,8 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
deployAttempts++; deployAttempts++;
// Получаем актуальный nonce прямо перед отправкой транзакции // Получаем актуальный nonce прямо перед отправкой транзакции
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, Number(net.chainId), { timeout: 15000, maxRetries: 5 }); const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 });
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetNonce})`); logger.info(`[MODULES_DBG] chainId=${chainId} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetNonce})`);
const txData = { const txData = {
data: moduleInit, data: moduleInit,
@@ -326,26 +329,26 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
const result = await rpcManager.sendTransactionWithRetry(wallet, txData, { maxRetries: 3 }); const result = await rpcManager.sendTransactionWithRetry(wallet, txData, { maxRetries: 3 });
tx = result.tx; 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; // Успешно отправили, выходим из цикла break; // Успешно отправили, выходим из цикла
} catch (e) { } catch (e) {
const errorMsg = e?.message || 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 // Проверяем, является ли это ошибкой nonce
if (String(errorMsg).toLowerCase().includes('nonce too low') && deployAttempts < maxDeployAttempts) { 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 из сети // Получаем актуальный nonce из сети
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, Number(net.chainId), { timeout: 15000, maxRetries: 5 }); const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 });
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} current nonce: ${currentNonce}, target: ${targetNonce}`); logger.info(`[MODULES_DBG] chainId=${chainId} current nonce: ${currentNonce}, target: ${targetNonce}`);
// Если текущий nonce больше целевого, обновляем targetNonce // Если текущий nonce больше целевого, обновляем targetNonce
if (currentNonce > 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; 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)); await new Promise(resolve => setTimeout(resolve, 1000));
@@ -354,7 +357,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
// Если текущий nonce меньше целевого, выравниваем его // Если текущий nonce меньше целевого, выравниваем его
if (currentNonce < targetNonce) { 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 нулевыми транзакциями // Выравниваем nonce нулевыми транзакциями
for (let i = currentNonce; i < targetNonce; i++) { for (let i = currentNonce; i < targetNonce; i++) {
@@ -368,13 +371,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
}); });
await fillerTx.wait(); 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 в кэше // Обновляем nonce в кэше
nonceManager.reserveNonce(wallet.address, Number(net.chainId), i); nonceManager.reserveNonce(wallet.address, chainId, i);
} catch (fillerError) { } 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; throw fillerError;
} }
} }
@@ -382,7 +385,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
// ВАЖНО: Обновляем targetNonce на актуальный nonce для следующей попытки // ВАЖНО: Обновляем targetNonce на актуальный nonce для следующей попытки
targetNonce = currentNonce; 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)); 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 rc = await tx.wait();
const deployedAddress = rc.contractAddress || predictedAddress; const deployedAddress = rc.contractAddress || predictedAddress;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployed at=${deployedAddress}`); logger.info(`[MODULES_DBG] chainId=${chainId} ${moduleType} deployed at=${deployedAddress}`);
return { address: deployedAddress, chainId: Number(net.chainId) }; 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; 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 логикой // Используем новый менеджер RPC с retry логикой
const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, { const { provider, wallet, network } = await createRPCConnection(chainId, pk, {
maxRetries: 3, maxRetries: 3,
timeout: 30000 timeout: 30000
}); });
const net = network; 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 = {}; const results = {};
@@ -432,14 +443,14 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
logger.info(`[MODULES_DBG] Деплой модуля ${moduleType} в сети ${net.name || net.chainId}`); logger.info(`[MODULES_DBG] Деплой модуля ${moduleType} в сети ${net.name || net.chainId}`);
if (!MODULE_CONFIGS[moduleType]) { 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}` }; results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` };
logger.error(`[MODULES_DBG] Неизвестный тип модуля: ${moduleType}`); logger.error(`[MODULES_DBG] Неизвестный тип модуля: ${moduleType}`);
continue; continue;
} }
if (!moduleInit) { 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}` }; results[moduleType] = { success: false, error: `No init code for module: ${moduleType}` };
logger.error(`[MODULES_DBG] Отсутствует код инициализации для модуля: ${moduleType}`); logger.error(`[MODULES_DBG] Отсутствует код инициализации для модуля: ${moduleType}`);
continue; continue;
@@ -458,7 +469,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
// Получаем аргументы конструктора для модуля // Получаем аргументы конструктора для модуля
const moduleConfig = MODULE_CONFIGS[moduleType]; const moduleConfig = MODULE_CONFIGS[moduleType];
const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(net.chainId), wallet.address); const constructorArgs = moduleConfig.constructorArgs(dleAddress, chainId, wallet.address);
// Ждем 30 секунд перед верификацией, чтобы транзакция получила подтверждения // Ждем 30 секунд перед верификацией, чтобы транзакция получила подтверждения
logger.info(`[MODULES_DBG] Ждем 30 секунд перед верификацией модуля ${moduleType}...`); logger.info(`[MODULES_DBG] Ждем 30 секунд перед верификацией модуля ${moduleType}...`);
@@ -478,7 +489,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
} }
const verificationResult = await verifyModuleAfterDeploy( const verificationResult = await verifyModuleAfterDeploy(
Number(net.chainId), chainId,
result.address, result.address,
moduleType, moduleType,
constructorArgs, constructorArgs,
@@ -510,9 +521,9 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
} }
} }
} catch (error) { } 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] = { results[moduleType] = {
chainId: Number(net.chainId), chainId: chainId,
success: false, success: false,
error: error.message error: error.message
}; };
@@ -521,7 +532,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
} }
return { return {
chainId: Number(net.chainId), chainId: chainId,
modules: results modules: results
}; };
} }
@@ -544,6 +555,15 @@ async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, mod
} }
async function main() { 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; const { ethers } = hre;
// Обрабатываем аргументы командной строки и переменные окружения // Обрабатываем аргументы командной строки и переменные окружения
@@ -609,6 +629,7 @@ async function main() {
const pk = params.privateKey || params.private_key || process.env.PRIVATE_KEY; const pk = params.privateKey || params.private_key || process.env.PRIVATE_KEY;
const networks = params.rpcUrls || params.rpc_urls || []; const networks = params.rpcUrls || params.rpc_urls || [];
const supportedChainIds = params.supportedChainIds || [];
const dleAddress = params.dleAddress; const dleAddress = params.dleAddress;
const salt = params.CREATE2_SALT || params.create2_salt; const salt = params.CREATE2_SALT || params.create2_salt;
@@ -666,7 +687,7 @@ async function main() {
const ContractFactory = await hre.ethers.getContractFactory(moduleConfig.contractName); const ContractFactory = await hre.ethers.getContractFactory(moduleConfig.contractName);
// Получаем аргументы конструктора для первой сети (для расчета init кода) // Получаем аргументы конструктора для первой сети (для расчета init кода)
const firstConnection = await createRPCConnection(networks[0], pk, { const firstConnection = await createRPCConnection(supportedChainIds[0], pk, {
maxRetries: 3, maxRetries: 3,
timeout: 30000 timeout: 30000
}); });
@@ -688,8 +709,8 @@ async function main() {
// Подготовим провайдеры и вычислим общие nonce для каждого модуля // Подготовим провайдеры и вычислим общие nonce для каждого модуля
// Создаем RPC соединения с retry логикой // Создаем RPC соединения с retry логикой
logger.info(`[MODULES_DBG] Создаем RPC соединения для ${networks.length} сетей...`); logger.info(`[MODULES_DBG] Создаем RPC соединения для ${supportedChainIds.length} сетей...`);
const connections = await createMultipleRPCConnections(networks, pk, { const connections = await createMultipleRPCConnections(supportedChainIds, pk, {
maxRetries: 3, maxRetries: 3,
timeout: 30000 timeout: 30000
}); });
@@ -698,7 +719,7 @@ async function main() {
throw new Error('Не удалось установить ни одного RPC соединения'); throw new Error('Не удалось установить ни одного RPC соединения');
} }
logger.info(`[MODULES_DBG] ✅ Успешно подключились к ${connections.length}/${networks.length} сетям`); logger.info(`[MODULES_DBG] ✅ Успешно подключились к ${connections.length}/${supportedChainIds.length} сетям`);
const nonces = []; const nonces = [];
for (const connection of connections) { 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] 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) => { const deploymentPromises = connections.map(async (connection, networkIndex) => {
logger.info(`[MODULES_DBG] 🚀 Starting deployment to network ${networkIndex + 1}/${networks.length}: ${rpcUrl}`); logger.info(`[MODULES_DBG] 🚀 Starting deployment to network ${networkIndex + 1}/${connections.length}: ${connection.rpcUrl}`);
try { try {
// Получаем chainId динамически из сети с retry логикой const chainId = Number(connection.network.chainId);
const { provider, network } = await createRPCConnection(rpcUrl, pk, {
maxRetries: 3,
timeout: 30000
});
const chainId = Number(network.chainId);
logger.info(`[MODULES_DBG] 📡 Network ${networkIndex + 1} chainId: ${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`); logger.info(`[MODULES_DBG] ✅ Network ${networkIndex + 1} (chainId: ${chainId}) deployment SUCCESS`);
return { rpcUrl, chainId, ...result }; return { rpcUrl: connection.rpcUrl, chainId, ...result };
} catch (error) { } catch (error) {
logger.error(`[MODULES_DBG] ❌ Network ${networkIndex + 1} deployment FAILED:`, error.message); 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); 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) => { deployResults.forEach((result, index) => {

View File

@@ -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 { try {
const { ethers } = hre; 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 логикой // Используем новый менеджер RPC с retry логикой
const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, { const { provider, wallet, network } = await createRPCConnection(chainId, pk, {
maxRetries: 3, maxRetries: 3,
timeout: 30000 timeout: 30000
}); });
@@ -245,27 +252,26 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
} }
// 1) Используем NonceManager для получения актуального nonce // 1) Используем NonceManager для получения актуального nonce
const chainId = Number(net.chainId);
let current = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); 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})`); logger.info(`[MULTI_DBG] chainId=${chainId} current nonce=${current} (target was ${targetDLENonce})`);
// Если текущий nonce больше целевого, обновляем targetDLENonce // Если текущий nonce больше целевого, обновляем targetDLENonce
if (current > 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; 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 меньше целевого, выравниваем его // Если текущий nonce меньше целевого, выравниваем его
if (current < targetDLENonce) { 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 { } 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) // 2) Выравниваем nonce если нужно (используем NonceManager)
if (current < targetDLENonce) { 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 { try {
current = await nonceManager.alignNonceToTarget( current = await nonceManager.alignNonceToTarget(
@@ -277,31 +283,31 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
{ gasLimit: 21000, maxRetries: 5 } { 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 // Зарезервируем nonce в NonceManager
nonceManager.reserveNonce(wallet.address, chainId, targetDLENonce); 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) { } 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; throw error;
} }
} else { } 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) Проверяем баланс перед деплоем // 2) Проверяем баланс перед деплоем
const balance = await provider.getBalance(wallet.address, 'latest'); const balance = await provider.getBalance(wallet.address, 'latest');
const balanceEth = ethers.formatEther(balance); 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')) { 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 // 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); const feeOverrides = await getFeeOverrides(provider);
let gasLimit; 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); const fallbackGas = maxByBalance > 5_000_000n ? 5_000_000n : (maxByBalance < 2_500_000n ? 2_500_000n : maxByBalance);
gasLimit = est ? (est + est / 5n) : fallbackGas; 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 (_) { } catch (_) {
gasLimit = 3_000_000n; gasLimit = 3_000_000n;
} }
@@ -328,17 +334,17 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
from: wallet.address, from: wallet.address,
nonce: targetDLENonce 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); const existingCode = await provider.getCode(predictedAddress);
if (existingCode && existingCode !== '0x') { 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 !== '') { if (params.logoURI && params.logoURI !== '') {
try { 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 секунды для стабильности соединения // Ждем 2 секунды для стабильности соединения
await new Promise(resolve => setTimeout(resolve, 2000)); await new Promise(resolve => setTimeout(resolve, 2000));
@@ -348,19 +354,19 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
const currentLogo = await dleContract.logoURI(); const currentLogo = await dleContract.logoURI();
if (currentLogo === '' || currentLogo === '0x') { 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); const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides);
await logoTx.wait(); 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 { } 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) { } 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 // Деплоим DLE с retry логикой для обработки race conditions
@@ -374,13 +380,13 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
// Получаем актуальный nonce прямо перед отправкой транзакции // Получаем актуальный nonce прямо перед отправкой транзакции
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); 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 // Если текущий nonce больше целевого, обновляем targetDLENonce
if (currentNonce > 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; 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 = { const txData = {
@@ -397,25 +403,25 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
// Отмечаем транзакцию как pending в NonceManager // Отмечаем транзакцию как pending в NonceManager
nonceManager.markTransactionPending(wallet.address, chainId, currentNonce, tx.hash); 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; // Успешно отправили, выходим из цикла break; // Успешно отправили, выходим из цикла
} catch (e) { } catch (e) {
const errorMsg = e?.message || 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 // Проверяем, является ли это ошибкой nonce
if (String(errorMsg).toLowerCase().includes('nonce too low') && deployAttempts < maxDeployAttempts) { 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 для обновления nonce
nonceManager.resetNonce(wallet.address, chainId); nonceManager.resetNonce(wallet.address, chainId);
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 }); 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 на актуальный nonce
targetDLENonce = currentNonce; 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)); await new Promise(resolve => setTimeout(resolve, 1000));
@@ -440,19 +446,19 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
// Проверяем, что адрес соответствует предсказанному // Проверяем, что адрес соответствует предсказанному
if (deployedAddress !== predictedAddress) { 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}`); 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 !== '') { if (params.logoURI && params.logoURI !== '') {
try { try {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing logoURI: ${params.logoURI}`); logger.info(`[MULTI_DBG] chainId=${chainId} initializing logoURI: ${params.logoURI}`);
// Ждем 5 секунд, чтобы контракт получил подтверждения // Ждем 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)); await new Promise(resolve => setTimeout(resolve, 5000));
const DLE = await hre.ethers.getContractFactory('contracts/DLE.sol:DLE'); 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(); 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') { 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); 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 подтверждения с таймаутом 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 { } 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) { } catch (error) {
logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed: ${error.message}`); logger.error(`[MULTI_DBG] chainId=${chainId} logoURI initialization failed: ${error.message}`);
logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} error stack: ${error.stack}`); logger.error(`[MULTI_DBG] chainId=${chainId} error stack: ${error.stack}`);
// Не прерываем деплой из-за ошибки логотипа // Не прерываем деплой из-за ошибки логотипа
} }
} else { } 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 контракта после успешного деплоя // Автоматическая верификация DLE контракта после успешного деплоя
@@ -573,6 +579,15 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
async function main() { async function main() {
console.log('[MULTI_DBG] 🚀 ВХОДИМ В ФУНКЦИЮ 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; const { ethers } = hre;
console.log('[MULTI_DBG] ✅ ethers получен'); console.log('[MULTI_DBG] ✅ ethers получен');
@@ -699,8 +714,8 @@ async function main() {
} }
// Подготовим провайдеры и вычислим общий nonce для DLE с retry логикой // Подготовим провайдеры и вычислим общий nonce для DLE с retry логикой
logger.info(`[MULTI_DBG] Создаем RPC соединения для ${networks.length} сетей...`); logger.info(`[MULTI_DBG] Создаем RPC соединения для ${supportedChainIds.length} сетей...`);
const connections = await createMultipleRPCConnections(networks, pk, { const connections = await createMultipleRPCConnections(supportedChainIds, pk, {
maxRetries: 3, maxRetries: 3,
timeout: 30000 timeout: 30000
}); });
@@ -709,7 +724,7 @@ async function main() {
throw new Error('Не удалось установить ни одного RPC соединения'); throw new Error('Не удалось установить ни одного RPC соединения');
} }
logger.info(`[MULTI_DBG] ✅ Успешно подключились к ${connections.length}/${networks.length} сетям`); logger.info(`[MULTI_DBG] ✅ Успешно подключились к ${connections.length}/${supportedChainIds.length} сетям`);
// Очищаем старые pending транзакции для всех сетей // Очищаем старые pending транзакции для всех сетей
for (const connection of connections) { for (const connection of connections) {
@@ -719,12 +734,13 @@ async function main() {
const nonces = []; const nonces = [];
for (const connection of connections) { 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)); const n = await nonceManager.getNonce(connection.wallet.address, connection.rpcUrl, Number(connection.network.chainId));
nonces.push(n); nonces.push(n);
} }
const targetDLENonce = Math.max(...nonces); const targetDLENonce = Math.max(...nonces);
logger.info(`[MULTI_DBG] nonces=${JSON.stringify(nonces)} targetDLENonce=${targetDLENonce}`); 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] 🚀 ДОШЛИ ДО ПАРАЛЛЕЛЬНОГО ДЕПЛОЯ!`); console.log(`[MULTI_DBG] 🚀 ДОШЛИ ДО ПАРАЛЛЕЛЬНОГО ДЕПЛОЯ!`);
@@ -744,7 +760,7 @@ async function main() {
throw new Error(`InitCode не найден для chainId: ${chainId}`); 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}`); logger.info(`[MULTI_DBG] ✅ Network ${i + 1} (chainId: ${chainId}) deployment SUCCESS: ${r.address}`);
return { return {
rpcUrl, rpcUrl,

View File

@@ -20,10 +20,12 @@ async function main() {
console.log(`Читаем данные DLE из блокчейна по адресу: ${dleAddress}`); console.log(`Читаем данные DLE из блокчейна по адресу: ${dleAddress}`);
// Получаем RPC URL из переменных окружения или используем дефолтный для Sepolia // Получаем 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 { try {
// Получаем ABI контракта DLE // Получаем ABI контракта DLE

View File

@@ -11,6 +11,51 @@
*/ */
require('dotenv').config(); 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 { app, nonceStore } = require('./app');
const http = require('http'); const http = require('http');
const { initWSS } = require('./wsHub'); const { initWSS } = require('./wsHub');
@@ -32,6 +77,9 @@ initWSS(server);
async function startServer() { async function startServer() {
await initDbPool(); await initDbPool();
// Настройка NO_PROXY для RPC провайдеров из базы данных
await configureNoProxyFromRpcProviders();
// Инициализация AI ассистента В ФОНЕ (неблокирующая) // Инициализация AI ассистента В ФОНЕ (неблокирующая)
seedAIAssistantSettings().catch(error => { seedAIAssistantSettings().catch(error => {
console.warn('[Server] Ollama недоступен, AI ассистент будет инициализирован позже:', error.message); console.warn('[Server] Ollama недоступен, AI ассистент будет инициализирован позже:', error.message);

View File

@@ -67,7 +67,7 @@ async function checkAdminRole(address) {
errorCount++; errorCount++;
return null; return null;
} }
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(chainId));
// Проверяем доступность сети с таймаутом // Проверяем доступность сети с таймаутом
try { try {
const networkCheckPromise = provider.getNetwork(); const networkCheckPromise = provider.getNetwork();

View File

@@ -455,6 +455,7 @@ class AuthService {
} }
} }
/** /**
* Определяет уровень доступа пользователя на основе количества токенов * Определяет уровень доступа пользователя на основе количества токенов
* @param {string} address - Адрес кошелька * @param {string} address - Адрес кошелька
@@ -490,23 +491,35 @@ class AuthService {
const tokens = tokensResult.rows; const tokens = tokensResult.rows;
// Получаем RPC провайдеры // Получаем RPC провайдеры
const rpcProvidersResult = await db.getQuery()( // Убрано - используем rpcService вместо прямого запроса к БД
'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 = {}; // Используем правильный RPC URL из базы данных
for (const rpc of rpcProviders) { const rpcService = require('./rpcProviderService');
rpcMap[rpc.network_id] = rpc.rpc_url;
}
// Получаем балансы токенов из блокчейна // Получаем балансы токенов из блокчейна
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
const tokenBalances = []; 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) { 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; if (!rpcUrl) continue;
try { try {

View File

@@ -565,7 +565,7 @@ class DLEV2Service {
* @param {Array} allDles - Все DLE * @param {Array} allDles - Все DLE
* @returns {Array} - Сгруппированные DLE * @returns {Array} - Сгруппированные DLE
*/ */
groupMultichainDLEs(allDles) { async groupMultichainDLEs(allDles) {
const groups = new Map(); const groups = new Map();
for (const dle of allDles) { for (const dle of allDles) {
@@ -588,7 +588,7 @@ class DLEV2Service {
groups.get(groupKey).networks.push({ groups.get(groupKey).networks.push({
chainId: dle.chainId, chainId: dle.chainId,
address: dle.address, 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' status: dle.status || 'active'
}); });
} }
@@ -610,16 +610,25 @@ class DLEV2Service {
* @param {number} chainId - ID сети * @param {number} chainId - ID сети
* @returns {Object|null} - Информация о RPC * @returns {Object|null} - Информация о RPC
*/ */
getRpcUrlForChain(chainId) { async getRpcUrlForChain(chainId) {
const rpcMappings = { try {
1: { name: 'Ethereum Mainnet', url: 'https://mainnet.infura.io/v3/' }, // Получаем RPC URL из базы данных
11155111: { name: 'Sepolia Testnet', url: 'https://sepolia.infura.io/v3/' }, const rpcService = require('./rpcProviderService');
17000: { name: 'Holesky Testnet', url: 'https://holesky.infura.io/v3/' }, const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
421614: { name: 'Arbitrum Sepolia', url: 'https://sepolia-rollup.arbitrum.io/rpc' },
84532: { name: 'Base Sepolia', url: 'https://sepolia.base.org' }
};
return rpcMappings[chainId] || null; 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}`); 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); const balance = await provider.getBalance(wallet.address);
console.log(`💰 Баланс в сети ${chainId}: ${ethers.formatEther(balance)} ETH`); console.log(`💰 Баланс в сети ${chainId}: ${ethers.formatEther(balance)} ETH`);

View File

@@ -32,26 +32,52 @@ async function getUserTokenBalances(address) {
); );
const tokens = tokensResult.rows; const tokens = tokensResult.rows;
const rpcProvidersResult = await db.getQuery()( // Убрано - используем rpcService вместо прямого запроса к БД
'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', // Используем правильный RPC URL из базы данных
[encryptionKey] const rpcService = require('./rpcProviderService');
);
const rpcProviders = rpcProvidersResult.rows;
const rpcMap = {};
for (const rpc of rpcProviders) {
rpcMap[rpc.network_id] = rpc.rpc_url;
}
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
const results = []; 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) { for (const token of tokens) {
const rpcUrl = rpcMap[token.network]; // Получаем chain_id из названия сети из базы данных
if (!rpcUrl) { const chainId = networkToChainId[token.network];
logger.warn(`[tokenBalanceService] RPC URL не найден для сети ${token.network}`); if (!chainId) {
logger.warn(`[tokenBalanceService] Неизвестная сеть: ${token.network}`);
continue; 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, { const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, {
polling: false, polling: false,
@@ -84,8 +110,38 @@ async function getUserTokenBalances(address) {
`[tokenBalanceService] Ошибка получения баланса для ${token.name} (${token.address}) в сети ${token.network}:`, `[tokenBalanceService] Ошибка получения баланса для ${token.name} (${token.address}) в сети ${token.network}:`,
e.message || e 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'; 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({ results.push({
network: token.network, network: token.network,
tokenAddress: token.address, tokenAddress: token.address,
@@ -98,7 +154,8 @@ async function getUserTokenBalances(address) {
}); });
} }
return results; // Преобразуем в обычный массив для корректной сериализации
return JSON.parse(JSON.stringify(results));
} }
module.exports = { getUserTokenBalances }; module.exports = { getUserTokenBalances };

View File

@@ -93,26 +93,26 @@ async function getBalance(provider, address) {
/** /**
* Создает RPC соединение с retry логикой * Создает RPC соединение с retry логикой
* @param {string} rpcUrl - URL RPC * @param {number} chainId - ID цепочки
* @param {string} privateKey - Приватный ключ * @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения * @param {Object} options - Опции соединения
* @returns {Promise<Object>} - {provider, wallet, network} * @returns {Promise<Object>} - {provider, wallet, network}
*/ */
async function createRPCConnection(rpcUrl, privateKey, options = {}) { async function createRPCConnection(chainId, privateKey, options = {}) {
const rpcManager = new RPCConnectionManager(); const rpcManager = new RPCConnectionManager();
return await rpcManager.createConnection(rpcUrl, privateKey, options); return await rpcManager.createConnection(chainId, privateKey, options);
} }
/** /**
* Создает множественные RPC соединения с обработкой ошибок * Создает множественные RPC соединения с обработкой ошибок
* @param {Array} rpcUrls - Массив RPC URL * @param {Array} chainIds - Массив chain ID
* @param {string} privateKey - Приватный ключ * @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения * @param {Object} options - Опции соединения
* @returns {Promise<Array>} - Массив успешных соединений * @returns {Promise<Array>} - Массив успешных соединений
*/ */
async function createMultipleRPCConnections(rpcUrls, privateKey, options = {}) { async function createMultipleRPCConnections(chainIds, privateKey, options = {}) {
const rpcManager = new RPCConnectionManager(); const rpcManager = new RPCConnectionManager();
return await rpcManager.createMultipleConnections(rpcUrls, privateKey, options); return await rpcManager.createMultipleConnections(chainIds, privateKey, options);
} }
// sendTransactionWithRetry функция удалена - используем RPCConnectionManager напрямую // sendTransactionWithRetry функция удалена - используем RPCConnectionManager напрямую

View File

@@ -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 };

View File

@@ -23,10 +23,20 @@ class NonceManager {
async getNonce(address, rpcUrl, chainId, options = {}) { async getNonce(address, rpcUrl, chainId, options = {}) {
const { timeout = 15000, maxRetries = 5 } = 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}`; const cacheKey = `${address}-${chainId}`;
for (let attempt = 1; attempt <= maxRetries; attempt++) { for (let attempt = 1; attempt <= maxRetries; attempt++) {
try { try {
console.log(`[NonceManager] Попытка ${attempt}: создаем JsonRpcProvider с rpcUrl: ${rpcUrl}`);
const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, { const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, {
polling: false, // Отключаем polling для более быстрого получения nonce polling: false, // Отключаем polling для более быстрого получения nonce
staticNetwork: true staticNetwork: true
@@ -204,8 +214,8 @@ class NonceManager {
console.warn(`[NonceManager] networkLoader недоступен для chainId ${chainId}, используем fallback: ${error.message}`); console.warn(`[NonceManager] networkLoader недоступен для chainId ${chainId}, используем fallback: ${error.message}`);
} }
// Всегда добавляем fallback RPC для надежности // Всегда добавляем fallback RPC для надежности ИЗ БАЗЫ ДАННЫХ
const fallbackRPCs = this.getFallbackRPCs(chainId); const fallbackRPCs = await this.getFallbackRPCs(chainId);
for (const fallbackRpc of fallbackRPCs) { for (const fallbackRpc of fallbackRPCs) {
if (!rpcUrls.includes(fallbackRpc)) { if (!rpcUrls.includes(fallbackRpc)) {
rpcUrls.push(fallbackRpc); rpcUrls.push(fallbackRpc);
@@ -217,34 +227,31 @@ class NonceManager {
} }
/** /**
* Получить список fallback RPC для сети * Получить список fallback RPC для сети ИЗ БАЗЫ ДАННЫХ
* @param {number} chainId - ID сети * @param {number} chainId - ID сети
* @returns {Array} - Массив RPC URL * @returns {Array} - Массив RPC URL из базы данных
*/ */
getFallbackRPCs(chainId) { async getFallbackRPCs(chainId) {
const fallbackRPCs = { try {
1: [ // Mainnet // Получаем ВСЕ RPC провайдеры для данной сети из базы данных
'https://eth.llamarpc.com', const rpcService = require('../services/rpcProviderService');
'https://rpc.ankr.com/eth', const providers = await rpcService.getAllRpcProviders();
'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] || []; // Фильтруем по 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}`; const cacheKey = `${address}-${chainId}`;
try { try {
const provider = new ethers.JsonRpcProvider(rpcUrl); const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(chainId));
const networkNonce = await provider.getTransactionCount(address, 'pending'); const networkNonce = await provider.getTransactionCount(address, 'pending');
// Принудительно обновляем кэш // Принудительно обновляем кэш

View File

@@ -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()
};

View File

@@ -5,6 +5,7 @@
const { ethers } = require('ethers'); const { ethers } = require('ethers');
const logger = require('./logger'); const logger = require('./logger');
const rpcService = require('../services/rpcProviderService');
class RPCConnectionManager { class RPCConnectionManager {
constructor() { constructor() {
@@ -19,13 +20,26 @@ class RPCConnectionManager {
/** /**
* Создает RPC соединение с retry логикой * Создает RPC соединение с retry логикой
* @param {string} rpcUrl - URL RPC * @param {number} chainId - ID цепочки
* @param {string} privateKey - Приватный ключ * @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения * @param {Object} options - Опции соединения
* @returns {Promise<Object>} - {provider, wallet, network} * @returns {Promise<Object>} - {provider, wallet, network}
*/ */
async createConnection(rpcUrl, privateKey, options = {}) { async createConnection(chainId, privateKey, options = {}) {
const config = { ...this.retryConfig, ...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}`; const connectionKey = `${rpcUrl}_${privateKey}`;
// Проверяем кэш // Проверяем кэш
@@ -33,7 +47,8 @@ class RPCConnectionManager {
const cached = this.connections.get(connectionKey); const cached = this.connections.get(connectionKey);
if (Date.now() - cached.timestamp < 60000) { // 1 минута кэш if (Date.now() - cached.timestamp < 60000) { // 1 минута кэш
logger.info(`[RPC_MANAGER] Используем кэшированное соединение: ${rpcUrl}`); 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 wallet = new ethers.Wallet(privateKey, provider);
const connection = { provider, wallet, network }; const connection = { provider, wallet, network, rpcUrl };
// Кэшируем соединение // Кэшируем соединение
this.connections.set(connectionKey, { this.connections.set(connectionKey, {
@@ -84,21 +99,21 @@ class RPCConnectionManager {
/** /**
* Создает множественные RPC соединения с обработкой ошибок * Создает множественные RPC соединения с обработкой ошибок
* @param {Array} rpcUrls - Массив RPC URL * @param {Array} chainIds - Массив chain ID
* @param {string} privateKey - Приватный ключ * @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения * @param {Object} options - Опции соединения
* @returns {Promise<Array>} - Массив успешных соединений * @returns {Promise<Array>} - Массив успешных соединений
*/ */
async createMultipleConnections(rpcUrls, privateKey, options = {}) { async createMultipleConnections(chainIds, privateKey, options = {}) {
logger.info(`[RPC_MANAGER] Создаем ${rpcUrls.length} RPC соединений...`); logger.info(`[RPC_MANAGER] Создаем ${chainIds.length} RPC соединений...`);
const connectionPromises = rpcUrls.map(async (rpcUrl, index) => { const connectionPromises = chainIds.map(async (chainId, index) => {
try { try {
const connection = await this.createConnection(rpcUrl, privateKey, options); const connection = await this.createConnection(chainId, privateKey, options);
return { index, rpcUrl, ...connection, success: true }; return { index, chainId, ...connection, success: true };
} catch (error) { } catch (error) {
logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: ${rpcUrl} - ${error.message}`); logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: chainId ${chainId} - ${error.message}`);
return { index, rpcUrl, error: error.message, success: false }; return { index, chainId, error: error.message, success: false };
} }
}); });
@@ -106,10 +121,10 @@ class RPCConnectionManager {
const successful = results.filter(r => r.success); const successful = results.filter(r => r.success);
const failed = 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) { if (failed.length > 0) {
logger.warn(`[RPC_MANAGER] ⚠️ Неудачных соединений: ${failed.length}`); 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) { if (successful.length === 0) {
@@ -183,13 +198,25 @@ class RPCConnectionManager {
'ENOTFOUND', 'ENOTFOUND',
'ETIMEDOUT', 'ETIMEDOUT',
'RPC timeout', 'RPC timeout',
'Transaction timeout' 'Transaction timeout',
'ECONNREFUSED',
'ENETUNREACH',
'EHOSTUNREACH'
]; ];
const errorMessage = error.message.toLowerCase(); const errorMessage = error.message.toLowerCase();
return retryableErrors.some(retryableError => const isRetryable = retryableErrors.some(retryableError =>
errorMessage.includes(retryableError.toLowerCase()) errorMessage.includes(retryableError.toLowerCase())
); );
// Логируем информацию об ошибке для диагностики
if (isRetryable) {
logger.warn(`[RPC_MANAGER] Повторяемая ошибка: ${error.message}`);
} else {
logger.error(`[RPC_MANAGER] Неповторяемая ошибка: ${error.message}`);
}
return isRetryable;
} }
// getNonceWithRetry функция удалена - используем nonceManager.getNonceWithRetry() вместо этого // getNonceWithRetry функция удалена - используем nonceManager.getNonceWithRetry() вместо этого

View File

@@ -554,34 +554,56 @@ module.exports = {
// Обработчик запроса балансов токенов // Обработчик запроса балансов токенов
async function handleTokenBalancesRequest(ws, address, userId) { async function handleTokenBalancesRequest(ws, address, userId) {
try { try {
// console.log(`[WebSocket] Запрос балансов для адреса: ${address}`); // Убрано избыточное логирование console.log(`[WebSocket] Запрос балансов для адреса: ${address}`);
// Получаем балансы через отдельный сервис без зависимостей от wsHub // Получаем балансы через отдельный сервис без зависимостей от wsHub
const balances = await tokenBalanceService.getUserTokenBalances(address); 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', type: 'token_balances_response',
data: { data: {
address: address, address: address,
balances: balances, balances: balances,
timestamp: Date.now() timestamp: Date.now()
} }
})); };
// console.log(`[WebSocket] Отправлены балансы для ${address}:`, balances.length, 'токенов'); // Убрано избыточное логирование console.log(`[WebSocket] Отправляем ответ:`, JSON.stringify(response, null, 2));
ws.send(JSON.stringify(response));
} catch (error) { } catch (error) {
console.error('[WebSocket] Ошибка при получении балансов:', 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', type: 'token_balances_error',
data: { data: {
address: address, address: address,
error: error.message, error: errorType,
errorDetails: errorMessage,
timestamp: Date.now() timestamp: Date.now()
} }
})); };
console.log('[WebSocket] Отправляем ошибку клиенту:', JSON.stringify(errorResponse, null, 2));
ws.send(JSON.stringify(errorResponse));
} }
} }

View File

@@ -121,10 +121,13 @@
</div> </div>
<div v-else> <div v-else>
<div v-for="(token, index) in tokenBalances" :key="token.tokenAddress ? token.tokenAddress : 'token-' + index" class="token-balance-row"> <div v-for="(token, index) in tokenBalances" :key="token.tokenAddress ? token.tokenAddress : 'token-' + index" class="token-balance-row" :class="{ 'token-error': token.error }">
<span class="token-name">{{ token.tokenName }}</span> <span class="token-name">{{ token.tokenName }}</span>
<span class="token-network">{{ token.network }}</span> <span class="token-network">{{ token.network }}</span>
<span class="token-amount">{{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}</span> <span v-if="token.error" class="token-error-message" :title="token.errorDetails">
❌ {{ token.error }}
</span>
<span v-else class="token-amount">{{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}</span>
</div> </div>
</div> </div>
</div> </div>
@@ -456,6 +459,22 @@ h3 {
font-size: var(--font-size-sm); 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) { @media screen and (min-width: 1200px) {
.wallet-sidebar { .wallet-sidebar {

View File

@@ -12,6 +12,7 @@
import { ref, onMounted, onUnmounted, provide, inject } from 'vue'; import { ref, onMounted, onUnmounted, provide, inject } from 'vue';
import axios from '../api/axios'; import axios from '../api/axios';
import eventBus from '../utils/eventBus';
// === SINGLETON STATE === // === SINGLETON STATE ===
const isAuthenticated = ref(false); const isAuthenticated = ref(false);
@@ -243,15 +244,14 @@ const updateAuth = async ({
window.dispatchEvent(new CustomEvent('refresh-application-data')); window.dispatchEvent(new CustomEvent('refresh-application-data'));
} }
window.dispatchEvent(new CustomEvent('auth-state-changed', { // Отправляем событие через eventBus (централизованный подход)
detail: { eventBus.emit('auth-state-changed', {
authenticated: isAuthenticated.value, authenticated: isAuthenticated.value,
authType: authType.value, authType: authType.value,
userId: userId.value, userId: userId.value,
address: address.value, address: address.value,
userAccessLevel: userAccessLevel.value userAccessLevel: userAccessLevel.value
} });
}));
} }
// Если пользователь только что аутентифицировался или сменил аккаунт, // Если пользователь только что аутентифицировался или сменил аккаунт,

View File

@@ -51,9 +51,24 @@ export function useTokenBalancesWebSocket() {
// Обработчик ошибки // Обработчик ошибки
const handleTokenBalancesError = (data) => { const handleTokenBalancesError = (data) => {
console.error('[useTokenBalancesWebSocket] Ошибка получения балансов:', data.error); console.error('[useTokenBalancesWebSocket] Ошибка получения балансов:', data);
isLoadingTokens.value = false; 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];
}; };
// Обработчик обновления балансов // Обработчик обновления балансов

View File

@@ -906,6 +906,7 @@ import { useAuthContext } from '@/composables/useAuth';
import { usePermissions } from '@/composables/usePermissions'; import { usePermissions } from '@/composables/usePermissions';
import api from '@/api/axios'; import api from '@/api/axios';
import DeploymentWizard from '@/components/deployment/DeploymentWizard.vue'; import DeploymentWizard from '@/components/deployment/DeploymentWizard.vue';
import eventBus from '@/utils/eventBus';
const router = useRouter(); const router = useRouter();
// Нормализация приватного ключа: убираем пробелы/"0x", посторонние символы, // Нормализация приватного ключа: убираем пробелы/"0x", посторонние символы,
@@ -924,19 +925,10 @@ function normalizePrivateKey(raw) {
const { address } = useAuthContext(); const { address } = useAuthContext();
const { canManageSettings } = usePermissions(); const { canManageSettings } = usePermissions();
// Подписываемся на централизованные события очистки и обновления данных // Обработчики событий будут определены после функций clearAllData и resetUIState
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'); let unsubscribe = null;
checkAdminTokens(); // Обновляем данные при входе в систему
});
});
// Состояние для проверки админских токенов // Состояние для проверки админских токенов
const adminTokenCheck = ref({ const adminTokenCheck = ref({
@@ -945,6 +937,28 @@ const adminTokenCheck = ref({
error: null 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 // Основные настройки DLE
const dleSettings = reactive({ const dleSettings = reactive({
// Юрисдикция // Юрисдикция
@@ -1573,12 +1587,17 @@ const clearAllData = () => {
// Очищаем также поиск адресов и флаги автовыбора // Очищаем также поиск адресов и флаги автовыбора
postalCodeInput.value = ''; postalCodeInput.value = '';
searchResults.value = []; searchResults.value = [];
isSearchingAddress.value = false;
autoSelectedOktmo.value = false; autoSelectedOktmo.value = false;
lastApiResult.value = null; lastApiResult.value = null;
// Сбрасываем выбранные уровни ОКВЭД // Сбрасываем выбранные уровни ОКВЭД
selectedOkvedLevel1.value = ''; selectedOkvedLevel1.value = '';
selectedOkvedLevel2.value = ''; selectedOkvedLevel2.value = '';
selectedOkvedLevel3.value = '';
selectedOkvedLevel4.value = '';
currentSelectedOkvedCode.value = '';
currentSelectedOkvedText.value = '';
// Очищаем мульти-чейн состояние // Очищаем мульти-чейн состояние
selectedNetworks.value = []; selectedNetworks.value = [];
@@ -1591,10 +1610,79 @@ const clearAllData = () => {
Object.keys(keyValidation).forEach(key => delete keyValidation[key]); Object.keys(keyValidation).forEach(key => delete keyValidation[key]);
showUnifiedKey.value = false; showUnifiedKey.value = false;
// Очищаем настройки деплоя
etherscanApiKey.value = '';
unifiedScanKeyVisible.value = false;
autoVerifyAfterDeploy.value = true;
showDeploymentWizard.value = false;
deployedDLEAddress.value = '';
// Очищаем localStorage // Очищаем localStorage
clearStoredData(); 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 для автоматического обновления адреса первого партнера при подключении кошелька // Watcher для автоматического обновления адреса первого партнера при подключении кошелька
watch(address, (newAddress) => { watch(address, (newAddress, oldAddress) => {
console.log('[DleDeployFormView] Address changed:', { oldAddress, newAddress });
// Обновляем состояние при изменении адреса (подключение/отключение кошелька)
checkAdminTokens();
if (newAddress && dleSettings.partners[0]) { if (newAddress && dleSettings.partners[0]) {
// Подставляем адрес, если поле пустое или пользователь только что подключил кошелек // Подставляем адрес, если поле пустое или пользователь только что подключил кошелек
if (!dleSettings.partners[0].address) { if (!dleSettings.partners[0].address) {
@@ -2394,8 +2487,16 @@ watch(address, (newAddress) => {
// Функция проверки админских токенов // Функция проверки админских токенов
const checkAdminTokens = async () => { 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) { if (!address.value) {
adminTokenCheck.value = { isLoading: false, canManageSettings: false, error: 'Кошелек не подключен' }; adminTokenCheck.value = { ...adminTokenCheck.value, isLoading: false, error: 'Кошелек не подключен' };
return; return;
} }
@@ -2405,8 +2506,8 @@ const checkAdminTokens = async () => {
const response = await api.get(`/dle-v2/check-admin-tokens?address=${address.value}`); const response = await api.get(`/dle-v2/check-admin-tokens?address=${address.value}`);
if (response.data.success) { if (response.data.success) {
adminTokenCheck.value = { ...adminTokenCheck.value, canManageSettings: response.data.data.userAccessLevel.hasAccess };
console.log('Проверка админских токенов:', response.data.data); console.log('Проверка админских токенов:', response.data.data);
// Не перезаписываем canManageSettings, так как это управляется usePermissions
} else { } else {
adminTokenCheck.value = { ...adminTokenCheck.value, error: response.data.message || 'Ошибка проверки токенов' }; 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 = () => { const addPartner = () => {
dleSettings.partners.push({ address: '', amount: 1 }); dleSettings.partners.push({ address: '', amount: 1 });