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') {
initializeDbSettingsService();
// Загружаем RPC URL из базы данных
const { loadRpcFromDatabase } = require('./utils/loadRpcFromDatabase');
loadRpcFromDatabase().catch(error => {
logger.error('[App] Ошибка загрузки RPC URL из базы данных:', error);
});
}
module.exports = { app, nonceStore };

View File

@@ -29,54 +29,8 @@ function getNetworks() {
// console.log удален - может мешать flatten
// Базовые сети
const baseNetworks = {
sepolia: {
url: process.env.SEPOLIA_RPC_URL || 'https://1rpc.io/sepolia',
chainId: 11155111,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
holesky: {
url: process.env.HOLESKY_RPC_URL || 'https://ethereum-holesky.publicnode.com',
chainId: 17000,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
mainnet: {
url: process.env.MAINNET_RPC_URL || 'https://eth-mainnet.nodereal.io/v1/YOUR_NODEREAL_KEY',
chainId: 1,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
arbitrumSepolia: {
url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc',
chainId: 421614,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
baseSepolia: {
url: process.env.BASE_SEPOLIA_RPC_URL || 'https://sepolia.base.org',
chainId: 84532,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
arbitrumOne: {
url: process.env.ARBITRUM_ONE_RPC_URL || 'https://arb1.arbitrum.io/rpc',
chainId: 42161,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
base: {
url: process.env.BASE_RPC_URL || 'https://mainnet.base.org',
chainId: 8453,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
polygon: {
url: process.env.POLYGON_RPC_URL || 'https://polygon-rpc.com',
chainId: 137,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
bsc: {
url: process.env.BSC_RPC_URL || 'https://bsc-dataseed.binance.org',
chainId: 56,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
};
// Базовые сети - УБРАНО, используем только базу данных
const baseNetworks = {}; // Пустой объект - никаких хардкод цепочек
// Если есть supported_chain_ids, фильтруем только нужные сети
if (supportedChainIds.length > 0) {
@@ -104,35 +58,9 @@ function getNetworks() {
}
}
// Функция для получения базовых сетей (fallback)
// Функция для получения базовых сетей (fallback) - УБРАНО, используем только базу данных
function getBaseNetworks() {
return {
sepolia: {
url: process.env.SEPOLIA_RPC_URL || 'https://1rpc.io/sepolia',
chainId: 11155111,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
holesky: {
url: process.env.HOLESKY_RPC_URL || 'https://ethereum-holesky.publicnode.com',
chainId: 17000,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
mainnet: {
url: process.env.MAINNET_RPC_URL || 'https://eth-mainnet.nodereal.io/v1/YOUR_NODEREAL_KEY',
chainId: 1,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
arbitrumSepolia: {
url: process.env.ARBITRUM_SEPOLIA_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc',
chainId: 421614,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
},
baseSepolia: {
url: process.env.BASE_SEPOLIA_RPC_URL || 'https://sepolia.base.org',
chainId: 84532,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : []
}
};
return {}; // Пустой объект - никаких хардкод цепочек
}

View File

@@ -54,7 +54,7 @@ router.post('/read-dle-info', async (req, res) => {
if (!rpcUrl) {
return res.status(500).json({ success: false, error: `RPC URL для сети ${targetChainId} не найден` });
}
provider = new ethers.JsonRpcProvider(rpcUrl);
provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const code = await provider.getCode(dleAddress);
if (!code || code === '0x') {
return res.status(400).json({ success: false, error: `По адресу ${dleAddress} нет контракта в сети ${targetChainId}` });
@@ -358,7 +358,7 @@ router.post('/get-proposals', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// ABI для чтения предложений (используем правильные функции из смарт-контракта)
const dleAbi = [
@@ -520,7 +520,7 @@ router.post('/get-proposal-info', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// ABI для чтения информации о предложении
const dleAbi = [
@@ -614,7 +614,7 @@ router.post('/deactivate-dle', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// ABI для проверки деактивации DLE
const dleAbi = [
@@ -707,7 +707,7 @@ router.post('/check-deactivation-proposal-result', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function checkDeactivationProposalResult(uint256 _proposalId) public view returns (bool passed, bool quorumReached)"
@@ -785,7 +785,7 @@ router.post('/load-deactivation-proposals', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function deactivationProposalCounter() external view returns (uint256)",
@@ -889,7 +889,7 @@ router.post('/execute-proposal', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [
@@ -965,7 +965,7 @@ router.post('/cancel-proposal', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function cancelProposal(uint256 _proposalId, string calldata reason) external"
@@ -1050,7 +1050,7 @@ router.post('/get-governance-params', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getGovernanceParams() external view returns (uint256 quorumPct, uint256 chainId, uint256 supportedCount)"
@@ -1126,7 +1126,7 @@ router.post('/get-proposal-state', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalState(uint256 _proposalId) public view returns (uint8 state)"
@@ -1201,7 +1201,7 @@ router.post('/get-proposal-votes', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalVotes(uint256 _proposalId) external view returns (uint256 forVotes, uint256 againstVotes, uint256 totalVotes, uint256 quorumRequired)"
@@ -1279,7 +1279,7 @@ router.post('/get-proposals-count', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalsCount() external view returns (uint256)"
@@ -1353,7 +1353,7 @@ router.post('/list-proposals', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)"
@@ -1429,7 +1429,7 @@ router.post('/get-voting-power-at', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256)"
@@ -1505,7 +1505,7 @@ router.post('/get-quorum-at', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getQuorumAt(uint256 timepoint) external view returns (uint256)"
@@ -1580,7 +1580,7 @@ router.post('/get-token-balance', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function balanceOf(address account) external view returns (uint256)"
@@ -1655,7 +1655,7 @@ router.post('/get-total-supply', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function totalSupply() external view returns (uint256)"
@@ -1729,7 +1729,7 @@ router.post('/is-active', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function isActive() external view returns (bool)"
@@ -1809,7 +1809,7 @@ router.post('/get-dle-analytics', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function totalSupply() external view returns (uint256)",
@@ -1956,7 +1956,7 @@ router.post('/get-dle-history', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalsCount() external view returns (uint256)",

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 = [
"function totalSupply() external view returns (uint256)",
@@ -242,7 +242,7 @@ router.post('/get-dle-history', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalsCount() external view returns (uint256)",

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
const dleAbi = [
@@ -254,7 +254,7 @@ router.post('/get-governance-params', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getGovernanceParams() external view returns (uint256 quorumPct, uint256 chainId, uint256 supportedCount)"
@@ -323,7 +323,7 @@ router.post('/is-active', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function isActive() external view returns (bool)"
@@ -408,7 +408,7 @@ router.post('/deactivate-dle', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// ABI для проверки деактивации DLE
const dleAbi = [

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

View File

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

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 = [
"function proposals(uint256) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, bytes memory operation, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targetChains)",
@@ -294,7 +294,7 @@ async function executeProposalInChain(dleAddress, proposalId, chainId, privateKe
throw new Error(`RPC URL для сети ${chainId} не найден`);
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [

View File

@@ -60,7 +60,7 @@ router.post('/get-proposals', async (req, res) => {
return;
}
if (rpcUrl) {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getSupportedChainCount() external view returns (uint256)",
"function getSupportedChainId(uint256 _index) external view returns (uint256)"
@@ -97,7 +97,7 @@ router.post('/get-proposals', async (req, res) => {
continue;
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// ABI для чтения предложений (используем getProposalSummary для мультиконтрактов)
const dleAbi = [
@@ -369,7 +369,7 @@ router.post('/get-proposal-info', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// ABI для чтения информации о предложении
const dleAbi = [
@@ -447,7 +447,7 @@ router.post('/get-proposal-state', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalState(uint256 _proposalId) public view returns (uint8 state)"
@@ -499,7 +499,7 @@ router.post('/get-proposal-votes', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
@@ -560,7 +560,7 @@ router.post('/get-proposals-count', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalsCount() external view returns (uint256)"
@@ -611,7 +611,7 @@ router.post('/list-proposals', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)"
@@ -664,7 +664,7 @@ router.post('/get-voting-power-at', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getVotingPowerAt(address voter, uint256 timepoint) external view returns (uint256)"
@@ -717,7 +717,7 @@ router.post('/get-quorum-at', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getQuorumAt(uint256 timepoint) external view returns (uint256)"
@@ -772,7 +772,7 @@ router.post('/execute-proposal', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function executeProposal(uint256 _proposalId) external"
@@ -827,7 +827,7 @@ router.post('/cancel-proposal', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function cancelProposal(uint256 _proposalId, string calldata reason) external"
@@ -879,7 +879,7 @@ router.post('/get-proposals-count', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function getProposalsCount() external view returns (uint256)"
@@ -929,7 +929,7 @@ router.post('/list-proposals', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function listProposals(uint256 offset, uint256 limit) external view returns (uint256[] memory)",
@@ -1030,7 +1030,7 @@ router.post('/vote-proposal', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function vote(uint256 _proposalId, bool _support) external"
@@ -1088,7 +1088,7 @@ router.post('/check-vote-status', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// Функция hasVoted не существует в контракте DLE
console.log(`[DLE Proposals] Функция hasVoted не поддерживается в контракте DLE`);
@@ -1135,7 +1135,7 @@ router.post('/track-vote-transaction', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// Ждем подтверждения транзакции
const receipt = await provider.waitForTransaction(txHash, 1, 60000); // 60 секунд таймаут
@@ -1193,7 +1193,7 @@ router.post('/track-execution-transaction', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// Ждем подтверждения транзакции
const receipt = await provider.waitForTransaction(txHash, 1, 60000); // 60 секунд таймаут
@@ -1252,7 +1252,7 @@ router.post('/decode-proposal-data', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
// Получаем данные транзакции
const tx = await provider.getTransaction(transactionHash);

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 = [
"function balanceOf(address account) external view returns (uint256)"
@@ -158,7 +158,7 @@ router.post('/get-total-supply', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function totalSupply() external view returns (uint256)"
@@ -243,7 +243,7 @@ router.post('/get-token-holders', async (req, res) => {
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcProviderService.getRpcUrlByChainId(chainId));
const dleAbi = [
"function totalSupply() external view returns (uint256)",

View File

@@ -17,9 +17,23 @@ const express = require('express');
const router = express.Router();
const { ethers } = require('ethers');
function getMainnetProvider() {
const url = process.env.MAINNET_RPC_URL || process.env.ETH_MAINNET_RPC || 'https://ethereum.publicnode.com';
return new ethers.JsonRpcProvider(url);
async function getMainnetProvider() {
try {
// Получаем RPC URL из базы данных для mainnet (chain_id = 1)
const rpcService = require('../services/rpcProviderService');
const rpcUrl = await rpcService.getRpcUrlByChainId(1);
if (!rpcUrl) {
throw new Error('RPC URL для mainnet не найден в базе данных');
}
console.log(`[ENS] Используем RPC из базы данных: ${rpcUrl}`);
return new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(1));
} catch (error) {
console.error(`[ENS] Ошибка получения RPC из базы данных:`, error);
throw new Error(`Не удалось получить RPC провайдер: ${error.message}`);
}
}
// GET /api/ens/avatar?name=vc-hb3-accelerator.eth
@@ -29,7 +43,7 @@ router.get('/avatar', async (req, res) => {
if (!name || !name.endsWith('.eth')) {
return res.status(400).json({ success: false, message: 'ENS name is required (e.g., example.eth)' });
}
const provider = getMainnetProvider();
const provider = await getMainnetProvider();
const url = await provider.getAvatar(name);
return res.json({ success: true, data: { url: url || null } });
} catch (e) {

View File

@@ -18,9 +18,11 @@ async function checkModules() {
const dleAddress = '0xCaa85e96a6929F0373442e31FD9888d985869EcE';
// RPC URL для Sepolia
const rpcUrl = process.env.SEPOLIA_RPC_URL || 'https://eth-sepolia.nodereal.io/v1/YOUR_NODEREAL_KEY';
// Получаем RPC URL из базы данных
const rpcService = require('../services/rpcProviderService');
const rpcUrl = await rpcService.getRpcUrlByChainId(11155111);
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(11155111));
// ABI для DLE контракта
const dleAbi = [

View File

@@ -179,27 +179,30 @@ async function verifyModuleAfterDeploy(chainId, contractAddress, moduleType, con
async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce, moduleInit, moduleType) {
const { ethers } = hre;
// Создаем временный провайдер для получения chainId
const tempProvider = new ethers.JsonRpcProvider(rpcUrl);
const network = await tempProvider.getNetwork();
const chainId = Number(network.chainId);
// Используем новый менеджер RPC с retry логикой
const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, {
const { provider, wallet, network: rpcNetwork } = await createRPCConnection(chainId, pk, {
maxRetries: 3,
timeout: 30000
});
const net = network;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploying ${moduleType}...`);
const net = rpcNetwork;
// 1) Используем NonceManager для правильного управления nonce
const chainId = Number(net.chainId);
logger.info(`[MODULES_DBG] chainId=${chainId} deploying ${moduleType}...`);
let current = await nonceManager.getNonce(wallet.address, rpcUrl, chainId);
logger.info(`[MODULES_DBG] chainId=${chainId} current nonce=${current} target=${targetNonce}`);
if (current > targetNonce) {
throw new Error(`Current nonce ${current} > targetNonce ${targetNonce} on chainId=${Number(net.chainId)}`);
throw new Error(`Current nonce ${current} > targetNonce ${targetNonce} on chainId=${chainId}`);
}
if (current < targetNonce) {
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} aligning nonce from ${current} to ${targetNonce} (${targetNonce - current} transactions needed)`);
logger.info(`[MODULES_DBG] chainId=${chainId} aligning nonce from ${current} to ${targetNonce} (${targetNonce - current} transactions needed)`);
// Используем burn address для более надежных транзакций
const burnAddress = "0x000000000000000000000000000000000000dEaD";
@@ -219,15 +222,15 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
gasLimit,
...overrides
};
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} sending filler tx nonce=${current} attempt=${attempt + 1}`);
logger.info(`[MODULES_DBG] chainId=${chainId} sending filler tx nonce=${current} attempt=${attempt + 1}`);
const rpcManager = new RPCConnectionManager();
const { tx: txFill, receipt } = await rpcManager.sendTransactionWithRetry(wallet, txReq, { maxRetries: 3 });
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx sent, hash=${txFill.hash}, waiting for confirmation...`);
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx nonce=${current} confirmed, hash=${txFill.hash}`);
logger.info(`[MODULES_DBG] chainId=${chainId} filler tx sent, hash=${txFill.hash}, waiting for confirmation...`);
logger.info(`[MODULES_DBG] chainId=${chainId} filler tx nonce=${current} confirmed, hash=${txFill.hash}`);
sent = true;
} catch (e) {
lastErr = e;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx nonce=${current} attempt=${attempt + 1} failed: ${e?.message || e}`);
logger.info(`[MODULES_DBG] chainId=${chainId} filler tx nonce=${current} attempt=${attempt + 1} failed: ${e?.message || e}`);
if (String(e?.message || '').toLowerCase().includes('intrinsic gas too low') && attempt < 2) {
gasLimit = 50000;
@@ -236,13 +239,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
if (String(e?.message || '').toLowerCase().includes('nonce too low') && attempt < 2) {
// Сбрасываем кэш и получаем актуальный nonce
nonceManager.resetNonce(wallet.address, Number(net.chainId));
nonceManager.resetNonce(wallet.address, chainId);
current = await provider.getTransactionCount(wallet.address, 'pending');
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} updated nonce to ${current}`);
logger.info(`[MODULES_DBG] chainId=${chainId} updated nonce to ${current}`);
// Если новый nonce больше целевого, это критическая ошибка
if (current > targetNonce) {
throw new Error(`Current nonce ${current} > target nonce ${targetNonce} on chainId=${Number(net.chainId)}. Cannot proceed with module deployment.`);
throw new Error(`Current nonce ${current} > target nonce ${targetNonce} on chainId=${chainId}. Cannot proceed with module deployment.`);
}
continue;
@@ -253,20 +256,20 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
}
if (!sent) {
logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} failed to send filler tx for nonce=${current}`);
logger.error(`[MODULES_DBG] chainId=${chainId} failed to send filler tx for nonce=${current}`);
throw lastErr || new Error('filler tx failed');
}
current++;
}
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} nonce alignment completed, current nonce=${current}`);
logger.info(`[MODULES_DBG] chainId=${chainId} nonce alignment completed, current nonce=${current}`);
} else {
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} nonce already aligned at ${current}`);
logger.info(`[MODULES_DBG] chainId=${chainId} nonce already aligned at ${current}`);
}
// 2) Деплой модуля напрямую на согласованном nonce
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploying ${moduleType} directly with nonce=${targetNonce}`);
logger.info(`[MODULES_DBG] chainId=${chainId} deploying ${moduleType} directly with nonce=${targetNonce}`);
const feeOverrides = await getFeeOverrides(provider);
let gasLimit;
@@ -283,7 +286,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
const fallbackGas = maxByBalance > 2_000_000n ? 2_000_000n : (maxByBalance < 500_000n ? 500_000n : maxByBalance);
gasLimit = est ? (est + est / 5n) : fallbackGas;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`);
logger.info(`[MODULES_DBG] chainId=${chainId} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`);
} catch (_) {
gasLimit = 1_000_000n;
}
@@ -293,13 +296,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
from: wallet.address,
nonce: targetNonce
});
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} predicted ${moduleType} address=${predictedAddress}`);
logger.info(`[MODULES_DBG] chainId=${chainId} predicted ${moduleType} address=${predictedAddress}`);
// Проверяем, не развернут ли уже контракт
const existingCode = await provider.getCode(predictedAddress);
if (existingCode && existingCode !== '0x') {
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} already exists at predictedAddress, skip deploy`);
return { address: predictedAddress, chainId: Number(net.chainId) };
logger.info(`[MODULES_DBG] chainId=${chainId} ${moduleType} already exists at predictedAddress, skip deploy`);
return { address: predictedAddress, chainId: chainId };
}
// Деплоим модуль с retry логикой для обработки race conditions
@@ -312,8 +315,8 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
deployAttempts++;
// Получаем актуальный nonce прямо перед отправкой транзакции
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, Number(net.chainId), { timeout: 15000, maxRetries: 5 });
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetNonce})`);
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 });
logger.info(`[MODULES_DBG] chainId=${chainId} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetNonce})`);
const txData = {
data: moduleInit,
@@ -326,26 +329,26 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
const result = await rpcManager.sendTransactionWithRetry(wallet, txData, { maxRetries: 3 });
tx = result.tx;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploy successful on attempt ${deployAttempts}`);
logger.info(`[MODULES_DBG] chainId=${chainId} deploy successful on attempt ${deployAttempts}`);
break; // Успешно отправили, выходим из цикла
} catch (e) {
const errorMsg = e?.message || e;
logger.warn(`[MODULES_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts} failed: ${errorMsg}`);
logger.warn(`[MODULES_DBG] chainId=${chainId} deploy attempt ${deployAttempts} failed: ${errorMsg}`);
// Проверяем, является ли это ошибкой nonce
if (String(errorMsg).toLowerCase().includes('nonce too low') && deployAttempts < maxDeployAttempts) {
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} nonce race condition detected, retrying...`);
logger.info(`[MODULES_DBG] chainId=${chainId} nonce race condition detected, retrying...`);
// Получаем актуальный nonce из сети
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, Number(net.chainId), { timeout: 15000, maxRetries: 5 });
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} current nonce: ${currentNonce}, target: ${targetNonce}`);
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 });
logger.info(`[MODULES_DBG] chainId=${chainId} current nonce: ${currentNonce}, target: ${targetNonce}`);
// Если текущий nonce больше целевого, обновляем targetNonce
if (currentNonce > targetNonce) {
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} current nonce ${currentNonce} > target nonce ${targetNonce}, updating target`);
logger.info(`[MODULES_DBG] chainId=${chainId} current nonce ${currentNonce} > target nonce ${targetNonce}, updating target`);
targetNonce = currentNonce;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} updated targetNonce to: ${targetNonce}`);
logger.info(`[MODULES_DBG] chainId=${chainId} updated targetNonce to: ${targetNonce}`);
// Короткая задержка перед следующей попыткой
await new Promise(resolve => setTimeout(resolve, 1000));
@@ -354,7 +357,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
// Если текущий nonce меньше целевого, выравниваем его
if (currentNonce < targetNonce) {
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} aligning nonce from ${currentNonce} to ${targetNonce}`);
logger.info(`[MODULES_DBG] chainId=${chainId} aligning nonce from ${currentNonce} to ${targetNonce}`);
// Выравниваем nonce нулевыми транзакциями
for (let i = currentNonce; i < targetNonce; i++) {
@@ -368,13 +371,13 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
});
await fillerTx.wait();
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx ${i} confirmed`);
logger.info(`[MODULES_DBG] chainId=${chainId} filler tx ${i} confirmed`);
// Обновляем nonce в кэше
nonceManager.reserveNonce(wallet.address, Number(net.chainId), i);
nonceManager.reserveNonce(wallet.address, chainId, i);
} catch (fillerError) {
logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} filler tx ${i} failed: ${fillerError.message}`);
logger.error(`[MODULES_DBG] chainId=${chainId} filler tx ${i} failed: ${fillerError.message}`);
throw fillerError;
}
}
@@ -382,7 +385,7 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
// ВАЖНО: Обновляем targetNonce на актуальный nonce для следующей попытки
targetNonce = currentNonce;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} updated targetNonce to: ${targetNonce}`);
logger.info(`[MODULES_DBG] chainId=${chainId} updated targetNonce to: ${targetNonce}`);
// Короткая задержка перед следующей попыткой
await new Promise(resolve => setTimeout(resolve, 1000));
@@ -402,24 +405,32 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
const rc = await tx.wait();
const deployedAddress = rc.contractAddress || predictedAddress;
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployed at=${deployedAddress}`);
return { address: deployedAddress, chainId: Number(net.chainId) };
logger.info(`[MODULES_DBG] chainId=${chainId} ${moduleType} deployed at=${deployedAddress}`);
return { address: deployedAddress, chainId: chainId };
}
// Деплой всех модулей в одной сети
async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params) {
async function deployAllModulesInNetwork(chainId, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params) {
const { ethers } = hre;
// Получаем RPC URL для данной сети
const rpcService = require('../../services/rpcProviderService');
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
if (!rpcUrl) {
throw new Error(`RPC URL не найден для chainId ${chainId}`);
}
// Используем новый менеджер RPC с retry логикой
const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, {
const { provider, wallet, network } = await createRPCConnection(chainId, pk, {
maxRetries: 3,
timeout: 30000
});
const net = network;
const chainId = Number(net.chainId);
logger.info(`[MODULES_DBG] chainId=${Number(net.chainId)} deploying modules: ${modulesToDeploy.join(', ')}`);
logger.info(`[MODULES_DBG] chainId=${chainId} deploying modules: ${modulesToDeploy.join(', ')}`);
const results = {};
@@ -432,14 +443,14 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
logger.info(`[MODULES_DBG] Деплой модуля ${moduleType} в сети ${net.name || net.chainId}`);
if (!MODULE_CONFIGS[moduleType]) {
logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type: ${moduleType}`);
logger.error(`[MODULES_DBG] chainId=${chainId} Unknown module type: ${moduleType}`);
results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` };
logger.error(`[MODULES_DBG] Неизвестный тип модуля: ${moduleType}`);
continue;
}
if (!moduleInit) {
logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} No init code for module: ${moduleType}`);
logger.error(`[MODULES_DBG] chainId=${chainId} No init code for module: ${moduleType}`);
results[moduleType] = { success: false, error: `No init code for module: ${moduleType}` };
logger.error(`[MODULES_DBG] Отсутствует код инициализации для модуля: ${moduleType}`);
continue;
@@ -458,7 +469,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
// Получаем аргументы конструктора для модуля
const moduleConfig = MODULE_CONFIGS[moduleType];
const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(net.chainId), wallet.address);
const constructorArgs = moduleConfig.constructorArgs(dleAddress, chainId, wallet.address);
// Ждем 30 секунд перед верификацией, чтобы транзакция получила подтверждения
logger.info(`[MODULES_DBG] Ждем 30 секунд перед верификацией модуля ${moduleType}...`);
@@ -478,7 +489,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
}
const verificationResult = await verifyModuleAfterDeploy(
Number(net.chainId),
chainId,
result.address,
moduleType,
constructorArgs,
@@ -510,9 +521,9 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
}
}
} catch (error) {
logger.error(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployment failed:`, error.message);
logger.error(`[MODULES_DBG] chainId=${chainId} ${moduleType} deployment failed:`, error.message);
results[moduleType] = {
chainId: Number(net.chainId),
chainId: chainId,
success: false,
error: error.message
};
@@ -521,7 +532,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
}
return {
chainId: Number(net.chainId),
chainId: chainId,
modules: results
};
}
@@ -544,6 +555,15 @@ async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, mod
}
async function main() {
// 🔧 BEST PRACTICE: Настраиваем NO_PROXY перед деплоем
try {
const proxyManager = require('../../utils/proxyManager');
await proxyManager.initialize();
console.log('[MODULES_DBG] ✅ ProxyManager инициализирован');
} catch (error) {
console.warn('[MODULES_DBG] ⚠️ Не удалось инициализировать ProxyManager:', error.message);
}
const { ethers } = hre;
// Обрабатываем аргументы командной строки и переменные окружения
@@ -609,6 +629,7 @@ async function main() {
const pk = params.privateKey || params.private_key || process.env.PRIVATE_KEY;
const networks = params.rpcUrls || params.rpc_urls || [];
const supportedChainIds = params.supportedChainIds || [];
const dleAddress = params.dleAddress;
const salt = params.CREATE2_SALT || params.create2_salt;
@@ -666,7 +687,7 @@ async function main() {
const ContractFactory = await hre.ethers.getContractFactory(moduleConfig.contractName);
// Получаем аргументы конструктора для первой сети (для расчета init кода)
const firstConnection = await createRPCConnection(networks[0], pk, {
const firstConnection = await createRPCConnection(supportedChainIds[0], pk, {
maxRetries: 3,
timeout: 30000
});
@@ -688,8 +709,8 @@ async function main() {
// Подготовим провайдеры и вычислим общие nonce для каждого модуля
// Создаем RPC соединения с retry логикой
logger.info(`[MODULES_DBG] Создаем RPC соединения для ${networks.length} сетей...`);
const connections = await createMultipleRPCConnections(networks, pk, {
logger.info(`[MODULES_DBG] Создаем RPC соединения для ${supportedChainIds.length} сетей...`);
const connections = await createMultipleRPCConnections(supportedChainIds, pk, {
maxRetries: 3,
timeout: 30000
});
@@ -698,7 +719,7 @@ async function main() {
throw new Error('Не удалось установить ни одного RPC соединения');
}
logger.info(`[MODULES_DBG] ✅ Успешно подключились к ${connections.length}/${networks.length} сетям`);
logger.info(`[MODULES_DBG] ✅ Успешно подключились к ${connections.length}/${supportedChainIds.length} сетям`);
const nonces = [];
for (const connection of connections) {
@@ -718,33 +739,28 @@ async function main() {
logger.info(`[MODULES_DBG] nonces=${JSON.stringify(nonces)} targetNonces=${JSON.stringify(targetNonces)}`);
// ПАРАЛЛЕЛЬНЫЙ деплой всех модулей во всех сетях одновременно
logger.info(`[MODULES_DBG] Starting PARALLEL deployment of all modules to ${networks.length} networks`);
logger.info(`[MODULES_DBG] Starting PARALLEL deployment of all modules to ${connections.length} networks`);
const deploymentPromises = networks.map(async (rpcUrl, networkIndex) => {
logger.info(`[MODULES_DBG] 🚀 Starting deployment to network ${networkIndex + 1}/${networks.length}: ${rpcUrl}`);
const deploymentPromises = connections.map(async (connection, networkIndex) => {
logger.info(`[MODULES_DBG] 🚀 Starting deployment to network ${networkIndex + 1}/${connections.length}: ${connection.rpcUrl}`);
try {
// Получаем chainId динамически из сети с retry логикой
const { provider, network } = await createRPCConnection(rpcUrl, pk, {
maxRetries: 3,
timeout: 30000
});
const chainId = Number(network.chainId);
const chainId = Number(connection.network.chainId);
logger.info(`[MODULES_DBG] 📡 Network ${networkIndex + 1} chainId: ${chainId}`);
const result = await deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params);
const result = await deployAllModulesInNetwork(chainId, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces, params);
logger.info(`[MODULES_DBG] ✅ Network ${networkIndex + 1} (chainId: ${chainId}) deployment SUCCESS`);
return { rpcUrl, chainId, ...result };
return { rpcUrl: connection.rpcUrl, chainId, ...result };
} catch (error) {
logger.error(`[MODULES_DBG] ❌ Network ${networkIndex + 1} deployment FAILED:`, error.message);
return { rpcUrl, error: error.message };
return { rpcUrl: connection.rpcUrl, error: error.message };
}
});
// Ждем завершения всех деплоев
const deployResults = await Promise.all(deploymentPromises);
logger.info(`[MODULES_DBG] All ${networks.length} deployments completed`);
logger.info(`[MODULES_DBG] All ${connections.length} deployments completed`);
// Логируем результаты деплоя для каждой сети
deployResults.forEach((result, index) => {

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 {
const { ethers } = hre;
// Получаем RPC URL для данной сети
const rpcService = require('../../services/rpcProviderService');
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
if (!rpcUrl) {
throw new Error(`RPC URL не найден для chainId ${chainId}`);
}
// Используем новый менеджер RPC с retry логикой
const { provider, wallet, network } = await createRPCConnection(rpcUrl, pk, {
const { provider, wallet, network } = await createRPCConnection(chainId, pk, {
maxRetries: 3,
timeout: 30000
});
@@ -245,27 +252,26 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
}
// 1) Используем NonceManager для получения актуального nonce
const chainId = Number(net.chainId);
let current = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 });
logger.info(`[MULTI_DBG] chainId=${chainId} current nonce=${current} (target was ${targetDLENonce})`);
// Если текущий nonce больше целевого, обновляем targetDLENonce
if (current > targetDLENonce) {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current nonce ${current} > targetDLENonce ${targetDLENonce}, updating target`);
logger.info(`[MULTI_DBG] chainId=${chainId} current nonce ${current} > targetDLENonce ${targetDLENonce}, updating target`);
targetDLENonce = current;
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} updated targetDLENonce to: ${targetDLENonce}`);
logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`);
}
// Если текущий nonce меньше целевого, выравниваем его
if (current < targetDLENonce) {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} starting nonce alignment: ${current} -> ${targetDLENonce} (${targetDLENonce - current} transactions needed)`);
logger.info(`[MULTI_DBG] chainId=${chainId} starting nonce alignment: ${current} -> ${targetDLENonce} (${targetDLENonce - current} transactions needed)`);
} else {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce already aligned: ${current} = ${targetDLENonce}`);
logger.info(`[MULTI_DBG] chainId=${chainId} nonce already aligned: ${current} = ${targetDLENonce}`);
}
// 2) Выравниваем nonce если нужно (используем NonceManager)
if (current < targetDLENonce) {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} aligning nonce from ${current} to ${targetDLENonce}`);
logger.info(`[MULTI_DBG] chainId=${chainId} aligning nonce from ${current} to ${targetDLENonce}`);
try {
current = await nonceManager.alignNonceToTarget(
@@ -277,31 +283,31 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
{ gasLimit: 21000, maxRetries: 5 }
);
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce alignment completed, current nonce=${current}`);
logger.info(`[MULTI_DBG] chainId=${chainId} nonce alignment completed, current nonce=${current}`);
// Зарезервируем nonce в NonceManager
nonceManager.reserveNonce(wallet.address, chainId, targetDLENonce);
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} ready for DLE deployment with nonce=${current}`);
logger.info(`[MULTI_DBG] chainId=${chainId} ready for DLE deployment with nonce=${current}`);
} catch (error) {
logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce alignment failed: ${error.message}`);
logger.error(`[MULTI_DBG] chainId=${chainId} nonce alignment failed: ${error.message}`);
throw error;
}
} else {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce already aligned at ${current}`);
logger.info(`[MULTI_DBG] chainId=${chainId} nonce already aligned at ${current}`);
}
// 2) Проверяем баланс перед деплоем
const balance = await provider.getBalance(wallet.address, 'latest');
const balanceEth = ethers.formatEther(balance);
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} wallet balance: ${balanceEth} ETH`);
logger.info(`[MULTI_DBG] chainId=${chainId} wallet balance: ${balanceEth} ETH`);
if (balance < ethers.parseEther('0.01')) {
throw new Error(`Insufficient balance for deployment on chainId=${Number(net.chainId)}. Current: ${balanceEth} ETH, required: 0.01 ETH minimum`);
throw new Error(`Insufficient balance for deployment on chainId=${chainId}. Current: ${balanceEth} ETH, required: 0.01 ETH minimum`);
}
// 3) Деплой DLE с актуальным nonce
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} deploying DLE with current nonce`);
logger.info(`[MULTI_DBG] chainId=${chainId} deploying DLE with current nonce`);
const feeOverrides = await getFeeOverrides(provider);
let gasLimit;
@@ -318,7 +324,7 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
const fallbackGas = maxByBalance > 5_000_000n ? 5_000_000n : (maxByBalance < 2_500_000n ? 2_500_000n : maxByBalance);
gasLimit = est ? (est + est / 5n) : fallbackGas;
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`);
logger.info(`[MULTI_DBG] chainId=${chainId} estGas=${est?.toString?.()||'null'} effGasPrice=${effPrice?.toString?.()||'0'} maxByBalance=${maxByBalance.toString()} chosenGasLimit=${gasLimit.toString()}`);
} catch (_) {
gasLimit = 3_000_000n;
}
@@ -328,17 +334,17 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
from: wallet.address,
nonce: targetDLENonce
});
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} predicted DLE address=${predictedAddress} (nonce=${targetDLENonce})`);
logger.info(`[MULTI_DBG] chainId=${chainId} predicted DLE address=${predictedAddress} (nonce=${targetDLENonce})`);
// Проверяем, не развернут ли уже контракт
const existingCode = await provider.getCode(predictedAddress);
if (existingCode && existingCode !== '0x') {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE already exists at predictedAddress, skip deploy`);
logger.info(`[MULTI_DBG] chainId=${chainId} DLE already exists at predictedAddress, skip deploy`);
// Проверяем и инициализируем логотип для существующего контракта
if (params.logoURI && params.logoURI !== '') {
try {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} checking logoURI for existing contract`);
logger.info(`[MULTI_DBG] chainId=${chainId} checking logoURI for existing contract`);
// Ждем 2 секунды для стабильности соединения
await new Promise(resolve => setTimeout(resolve, 2000));
@@ -348,19 +354,19 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
const currentLogo = await dleContract.logoURI();
if (currentLogo === '' || currentLogo === '0x') {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing logoURI for existing contract: ${params.logoURI}`);
logger.info(`[MULTI_DBG] chainId=${chainId} initializing logoURI for existing contract: ${params.logoURI}`);
const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides);
await logoTx.wait();
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialized for existing contract`);
logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialized for existing contract`);
} else {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI already set: ${currentLogo}`);
logger.info(`[MULTI_DBG] chainId=${chainId} logoURI already set: ${currentLogo}`);
}
} catch (error) {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed for existing contract: ${error.message}`);
logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialization failed for existing contract: ${error.message}`);
}
}
return { address: predictedAddress, chainId: Number(net.chainId) };
return { address: predictedAddress, chainId: chainId };
}
// Деплоим DLE с retry логикой для обработки race conditions
@@ -374,13 +380,13 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
// Получаем актуальный nonce прямо перед отправкой транзакции
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 });
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetDLENonce})`);
logger.info(`[MULTI_DBG] chainId=${chainId} deploy attempt ${deployAttempts}/${maxDeployAttempts} with current nonce=${currentNonce} (target was ${targetDLENonce})`);
// Если текущий nonce больше целевого, обновляем targetDLENonce
if (currentNonce > targetDLENonce) {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current nonce ${currentNonce} > target nonce ${targetDLENonce}, updating target`);
logger.info(`[MULTI_DBG] chainId=${chainId} current nonce ${currentNonce} > target nonce ${targetDLENonce}, updating target`);
targetDLENonce = currentNonce;
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} updated targetDLENonce to: ${targetDLENonce}`);
logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`);
}
const txData = {
@@ -397,25 +403,25 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
// Отмечаем транзакцию как pending в NonceManager
nonceManager.markTransactionPending(wallet.address, chainId, currentNonce, tx.hash);
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} deploy successful on attempt ${deployAttempts}`);
logger.info(`[MULTI_DBG] chainId=${chainId} deploy successful on attempt ${deployAttempts}`);
break; // Успешно отправили, выходим из цикла
} catch (e) {
const errorMsg = e?.message || e;
logger.warn(`[MULTI_DBG] chainId=${Number(net.chainId)} deploy attempt ${deployAttempts} failed: ${errorMsg}`);
logger.warn(`[MULTI_DBG] chainId=${chainId} deploy attempt ${deployAttempts} failed: ${errorMsg}`);
// Проверяем, является ли это ошибкой nonce
if (String(errorMsg).toLowerCase().includes('nonce too low') && deployAttempts < maxDeployAttempts) {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} nonce race condition detected, retrying...`);
logger.info(`[MULTI_DBG] chainId=${chainId} nonce race condition detected, retrying...`);
// Используем NonceManager для обновления nonce
nonceManager.resetNonce(wallet.address, chainId);
const currentNonce = await nonceManager.getNonce(wallet.address, rpcUrl, chainId, { timeout: 15000, maxRetries: 5 });
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current nonce: ${currentNonce}, target was: ${targetDLENonce}`);
logger.info(`[MULTI_DBG] chainId=${chainId} current nonce: ${currentNonce}, target was: ${targetDLENonce}`);
// Обновляем targetDLENonce на актуальный nonce
targetDLENonce = currentNonce;
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} updated targetDLENonce to: ${targetDLENonce}`);
logger.info(`[MULTI_DBG] chainId=${chainId} updated targetDLENonce to: ${targetDLENonce}`);
// Короткая задержка перед следующей попыткой
await new Promise(resolve => setTimeout(resolve, 1000));
@@ -440,19 +446,19 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
// Проверяем, что адрес соответствует предсказанному
if (deployedAddress !== predictedAddress) {
logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} ADDRESS MISMATCH! predicted=${predictedAddress} actual=${deployedAddress}`);
logger.error(`[MULTI_DBG] chainId=${chainId} ADDRESS MISMATCH! predicted=${predictedAddress} actual=${deployedAddress}`);
throw new Error(`Address mismatch: predicted ${predictedAddress} != actual ${deployedAddress}`);
}
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} DLE deployed at=${deployedAddress}`);
logger.info(`[MULTI_DBG] chainId=${chainId} DLE deployed at=${deployedAddress}`);
// Инициализация логотипа если он указан
if (params.logoURI && params.logoURI !== '') {
try {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} initializing logoURI: ${params.logoURI}`);
logger.info(`[MULTI_DBG] chainId=${chainId} initializing logoURI: ${params.logoURI}`);
// Ждем 5 секунд, чтобы контракт получил подтверждения
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} waiting 5 seconds for contract confirmations...`);
logger.info(`[MULTI_DBG] chainId=${chainId} waiting 5 seconds for contract confirmations...`);
await new Promise(resolve => setTimeout(resolve, 5000));
const DLE = await hre.ethers.getContractFactory('contracts/DLE.sol:DLE');
@@ -460,24 +466,24 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
// Проверяем текущий логотип перед инициализацией
const currentLogo = await dleContract.logoURI();
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} current logoURI: ${currentLogo}`);
logger.info(`[MULTI_DBG] chainId=${chainId} current logoURI: ${currentLogo}`);
if (currentLogo === '' || currentLogo === '0x') {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI is empty, initializing...`);
logger.info(`[MULTI_DBG] chainId=${chainId} logoURI is empty, initializing...`);
const logoTx = await dleContract.connect(wallet).initializeLogoURI(params.logoURI, feeOverrides);
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI transaction sent: ${logoTx.hash}`);
logger.info(`[MULTI_DBG] chainId=${chainId} logoURI transaction sent: ${logoTx.hash}`);
await logoTx.wait(2); // Ждем 2 подтверждения с таймаутом
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialized successfully`);
logger.info(`[MULTI_DBG] chainId=${chainId} logoURI initialized successfully`);
} else {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI already set: ${currentLogo}, skipping initialization`);
logger.info(`[MULTI_DBG] chainId=${chainId} logoURI already set: ${currentLogo}, skipping initialization`);
}
} catch (error) {
logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} logoURI initialization failed: ${error.message}`);
logger.error(`[MULTI_DBG] chainId=${Number(net.chainId)} error stack: ${error.stack}`);
logger.error(`[MULTI_DBG] chainId=${chainId} logoURI initialization failed: ${error.message}`);
logger.error(`[MULTI_DBG] chainId=${chainId} error stack: ${error.stack}`);
// Не прерываем деплой из-за ошибки логотипа
}
} else {
logger.info(`[MULTI_DBG] chainId=${Number(net.chainId)} no logoURI specified, skipping initialization`);
logger.info(`[MULTI_DBG] chainId=${chainId} no logoURI specified, skipping initialization`);
}
// Автоматическая верификация DLE контракта после успешного деплоя
@@ -573,6 +579,15 @@ async function deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, dleInit
async function main() {
console.log('[MULTI_DBG] 🚀 ВХОДИМ В ФУНКЦИЮ MAIN!');
// 🔧 BEST PRACTICE: Настраиваем NO_PROXY перед деплоем
try {
const proxyManager = require('../../utils/proxyManager');
await proxyManager.initialize();
console.log('[MULTI_DBG] ✅ ProxyManager инициализирован');
} catch (error) {
console.warn('[MULTI_DBG] ⚠️ Не удалось инициализировать ProxyManager:', error.message);
}
const { ethers } = hre;
console.log('[MULTI_DBG] ✅ ethers получен');
@@ -699,8 +714,8 @@ async function main() {
}
// Подготовим провайдеры и вычислим общий nonce для DLE с retry логикой
logger.info(`[MULTI_DBG] Создаем RPC соединения для ${networks.length} сетей...`);
const connections = await createMultipleRPCConnections(networks, pk, {
logger.info(`[MULTI_DBG] Создаем RPC соединения для ${supportedChainIds.length} сетей...`);
const connections = await createMultipleRPCConnections(supportedChainIds, pk, {
maxRetries: 3,
timeout: 30000
});
@@ -709,7 +724,7 @@ async function main() {
throw new Error('Не удалось установить ни одного RPC соединения');
}
logger.info(`[MULTI_DBG] ✅ Успешно подключились к ${connections.length}/${networks.length} сетям`);
logger.info(`[MULTI_DBG] ✅ Успешно подключились к ${connections.length}/${supportedChainIds.length} сетям`);
// Очищаем старые pending транзакции для всех сетей
for (const connection of connections) {
@@ -719,12 +734,13 @@ async function main() {
const nonces = [];
for (const connection of connections) {
logger.info(`[MULTI_DBG] Получаем nonce для connection: address=${connection.wallet.address}, rpcUrl=${connection.rpcUrl}, chainId=${Number(connection.network.chainId)}`);
const n = await nonceManager.getNonce(connection.wallet.address, connection.rpcUrl, Number(connection.network.chainId));
nonces.push(n);
}
const targetDLENonce = Math.max(...nonces);
logger.info(`[MULTI_DBG] nonces=${JSON.stringify(nonces)} targetDLENonce=${targetDLENonce}`);
logger.info(`[MULTI_DBG] Starting deployment to ${networks.length} networks:`, networks);
logger.info(`[MULTI_DBG] Starting deployment to ${connections.length} networks`);
// ПАРАЛЛЕЛЬНЫЙ деплой во всех успешных сетях одновременно
console.log(`[MULTI_DBG] 🚀 ДОШЛИ ДО ПАРАЛЛЕЛЬНОГО ДЕПЛОЯ!`);
@@ -744,7 +760,7 @@ async function main() {
throw new Error(`InitCode не найден для chainId: ${chainId}`);
}
const r = await deployInNetwork(rpcUrl, pk, initCodeHash, targetDLENonce, networkInitCode, params, dleConfig, initializer, etherscanKey);
const r = await deployInNetwork(chainId, pk, initCodeHash, targetDLENonce, networkInitCode, params, dleConfig, initializer, etherscanKey);
logger.info(`[MULTI_DBG] ✅ Network ${i + 1} (chainId: ${chainId}) deployment SUCCESS: ${r.address}`);
return {
rpcUrl,

View File

@@ -20,10 +20,12 @@ async function main() {
console.log(`Читаем данные DLE из блокчейна по адресу: ${dleAddress}`);
// Получаем RPC URL из переменных окружения или используем дефолтный для Sepolia
const rpcUrl = process.env.RPC_URL || 'https://eth-sepolia.nodereal.io/v1/YOUR_NODEREAL_KEY';
// Получаем RPC URL из базы данных
const rpcService = require('../../services/rpcProviderService');
const rpcUrl = await rpcService.getRpcUrlByChainId(11155111);
// Создаем провайдер
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(11155111));
try {
// Получаем ABI контракта DLE

View File

@@ -11,6 +11,51 @@
*/
require('dotenv').config();
// Функция для настройки NO_PROXY на основе RPC провайдеров из базы данных
async function configureNoProxyFromRpcProviders() {
try {
const rpcService = require('./services/rpcProviderService');
const providers = await rpcService.getAllRpcProviders();
const rpcDomains = providers
.map(provider => provider.rpc_url)
.filter(url => url && url.startsWith('http'))
.map(url => {
try {
const urlObj = new URL(url);
return urlObj.hostname;
} catch (e) {
return null;
}
})
.filter(hostname => hostname)
.filter((hostname, index, array) => array.indexOf(hostname) === index); // убираем дубликаты
if (rpcDomains.length > 0) {
const existingNoProxy = process.env.NO_PROXY || '';
// Добавляем RPC домены к существующему NO_PROXY
const newDomains = rpcDomains.filter(domain => !existingNoProxy.includes(domain));
if (newDomains.length > 0) {
process.env.NO_PROXY = existingNoProxy ? `${existingNoProxy},${newDomains.join(',')}` : newDomains.join(',');
console.log('[Server] ✅ Добавлены RPC домены в NO_PROXY:', newDomains.join(', '));
console.log('[Server] 📋 Обновленный NO_PROXY:', process.env.NO_PROXY);
} else {
console.log('[Server] Все RPC домены уже в NO_PROXY');
}
} else {
console.warn('[Server] ⚠️ Не найдено RPC провайдеров для настройки NO_PROXY');
}
} catch (error) {
console.warn('[Server] ❌ Не удалось загрузить RPC провайдеры для NO_PROXY:', error.message);
}
}
// Экспортируем функцию для использования в других модулях
module.exports.configureNoProxyFromRpcProviders = configureNoProxyFromRpcProviders;
const { app, nonceStore } = require('./app');
const http = require('http');
const { initWSS } = require('./wsHub');
@@ -32,6 +77,9 @@ initWSS(server);
async function startServer() {
await initDbPool();
// Настройка NO_PROXY для RPC провайдеров из базы данных
await configureNoProxyFromRpcProviders();
// Инициализация AI ассистента В ФОНЕ (неблокирующая)
seedAIAssistantSettings().catch(error => {
console.warn('[Server] Ollama недоступен, AI ассистент будет инициализирован позже:', error.message);

View File

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

View File

@@ -455,6 +455,7 @@ class AuthService {
}
}
/**
* Определяет уровень доступа пользователя на основе количества токенов
* @param {string} address - Адрес кошелька
@@ -490,23 +491,35 @@ class AuthService {
const tokens = tokensResult.rows;
// Получаем RPC провайдеры
const rpcProvidersResult = await db.getQuery()(
'SELECT id, chain_id, created_at, updated_at, decrypt_text(network_id_encrypted, $1) as network_id, decrypt_text(rpc_url_encrypted, $1) as rpc_url FROM rpc_providers',
[encryptionKey]
);
const rpcProviders = rpcProvidersResult.rows;
// Убрано - используем rpcService вместо прямого запроса к БД
const rpcMap = {};
for (const rpc of rpcProviders) {
rpcMap[rpc.network_id] = rpc.rpc_url;
}
// Используем правильный RPC URL из базы данных
const rpcService = require('./rpcProviderService');
// Получаем балансы токенов из блокчейна
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
const tokenBalances = [];
// Получаем все RPC провайдеры из базы данных для маппинга
const allRpcProviders = await rpcService.getAllRpcProviders();
const networkToChainId = {};
// Создаем маппинг из базы данных
for (const provider of allRpcProviders) {
if (provider.network_id) {
networkToChainId[provider.network_id] = provider.chain_id;
}
}
for (const token of tokens) {
const rpcUrl = rpcMap[token.network];
// Получаем chain_id из названия сети из базы данных
const chainId = networkToChainId[token.network];
if (!chainId) {
logger.warn(`[getUserAccessLevel] Неизвестная сеть: ${token.network}`);
continue;
}
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
if (!rpcUrl) continue;
try {

View File

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

View File

@@ -32,26 +32,52 @@ async function getUserTokenBalances(address) {
);
const tokens = tokensResult.rows;
const rpcProvidersResult = await db.getQuery()(
'SELECT id, chain_id, created_at, updated_at, decrypt_text(network_id_encrypted, $1) as network_id, decrypt_text(rpc_url_encrypted, $1) as rpc_url FROM rpc_providers',
[encryptionKey]
);
const rpcProviders = rpcProvidersResult.rows;
const rpcMap = {};
for (const rpc of rpcProviders) {
rpcMap[rpc.network_id] = rpc.rpc_url;
}
// Убрано - используем rpcService вместо прямого запроса к БД
// Используем правильный RPC URL из базы данных
const rpcService = require('./rpcProviderService');
const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)'];
const results = [];
// Получаем все RPC провайдеры из базы данных для маппинга
const allRpcProviders = await rpcService.getAllRpcProviders();
const networkToChainId = {};
// Создаем маппинг из базы данных
for (const provider of allRpcProviders) {
if (provider.network_id) {
networkToChainId[provider.network_id] = provider.chain_id;
}
}
for (const token of tokens) {
const rpcUrl = rpcMap[token.network];
if (!rpcUrl) {
logger.warn(`[tokenBalanceService] RPC URL не найден для сети ${token.network}`);
// Получаем chain_id из названия сети из базы данных
const chainId = networkToChainId[token.network];
if (!chainId) {
logger.warn(`[tokenBalanceService] Неизвестная сеть: ${token.network}`);
continue;
}
logger.info(`[tokenBalanceService] Ищем RPC для token.network: ${token.network} (chainId: ${chainId})`);
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
if (!rpcUrl) {
logger.warn(`[tokenBalanceService] RPC URL не найден для сети ${token.network} (chainId: ${chainId})`);
// Пропускаем токен, если нет RPC URL
results.push({
network: token.network,
tokenAddress: token.address,
tokenName: token.name,
symbol: token.symbol || '',
balance: '0',
minBalance: token.min_balance,
readonlyThreshold: token.readonly_threshold || 1,
editorThreshold: token.editor_threshold || 2,
error: 'RPC URL не настроен'
});
continue;
}
logger.info(`[tokenBalanceService] Найден RPC URL: ${rpcUrl}`);
// Создаем провайдер с таймаутом
const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, {
polling: false,
@@ -84,8 +110,38 @@ async function getUserTokenBalances(address) {
`[tokenBalanceService] Ошибка получения баланса для ${token.name} (${token.address}) в сети ${token.network}:`,
e.message || e
);
// Проверяем тип ошибки для лучшей диагностики
const errorMessage = e.message || e.toString();
let errorType = 'Неизвестная ошибка';
if (errorMessage.includes('timeout') || errorMessage.includes('TIMEOUT')) {
errorType = 'Таймаут соединения - возможно, нужен VPN';
} else if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND')) {
errorType = 'Не удается подключиться к RPC провайдеру';
} else if (errorMessage.includes('NETWORK_ERROR')) {
errorType = 'Ошибка сети - проверьте интернет-соединение';
}
balance = '0';
// Добавляем информацию об ошибке в результат
results.push({
network: token.network,
tokenAddress: token.address,
tokenName: token.name,
symbol: token.symbol || '',
balance,
minBalance: token.min_balance,
readonlyThreshold: token.readonly_threshold || 1,
editorThreshold: token.editor_threshold || 2,
error: errorType,
errorDetails: errorMessage
});
continue;
}
// Добавляем успешный результат
results.push({
network: token.network,
tokenAddress: token.address,
@@ -98,7 +154,8 @@ async function getUserTokenBalances(address) {
});
}
return results;
// Преобразуем в обычный массив для корректной сериализации
return JSON.parse(JSON.stringify(results));
}
module.exports = { getUserTokenBalances };

View File

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

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 = {}) {
const { timeout = 15000, maxRetries = 5 } = options; // Увеличиваем таймаут и попытки
// КРИТИЧЕСКАЯ ПРОВЕРКА: логируем входящие параметры
console.log(`[NonceManager] getNonce вызван с параметрами: address=${address}, rpcUrl=${rpcUrl}, chainId=${chainId}`);
// КРИТИЧЕСКАЯ ПРОВЕРКА: если rpcUrl содержит 127.0.0.1:8545, это ошибка!
if (rpcUrl && rpcUrl.includes('127.0.0.1:8545')) {
console.error(`[NonceManager] ❌ КРИТИЧЕСКАЯ ОШИБКА: Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`);
throw new Error(`Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`);
}
const cacheKey = `${address}-${chainId}`;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
console.log(`[NonceManager] Попытка ${attempt}: создаем JsonRpcProvider с rpcUrl: ${rpcUrl}`);
const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, {
polling: false, // Отключаем polling для более быстрого получения nonce
staticNetwork: true
@@ -204,8 +214,8 @@ class NonceManager {
console.warn(`[NonceManager] networkLoader недоступен для chainId ${chainId}, используем fallback: ${error.message}`);
}
// Всегда добавляем fallback RPC для надежности
const fallbackRPCs = this.getFallbackRPCs(chainId);
// Всегда добавляем fallback RPC для надежности ИЗ БАЗЫ ДАННЫХ
const fallbackRPCs = await this.getFallbackRPCs(chainId);
for (const fallbackRpc of fallbackRPCs) {
if (!rpcUrls.includes(fallbackRpc)) {
rpcUrls.push(fallbackRpc);
@@ -217,34 +227,31 @@ class NonceManager {
}
/**
* Получить список fallback RPC для сети
* Получить список fallback RPC для сети ИЗ БАЗЫ ДАННЫХ
* @param {number} chainId - ID сети
* @returns {Array} - Массив RPC URL
* @returns {Array} - Массив RPC URL из базы данных
*/
getFallbackRPCs(chainId) {
const fallbackRPCs = {
1: [ // Mainnet
'https://eth.llamarpc.com',
'https://rpc.ankr.com/eth',
'https://ethereum.publicnode.com'
],
11155111: [ // Sepolia
'https://rpc.sepolia.org',
process.env.SEPOLIA_INFURA_URL || 'https://sepolia.infura.io/v3/YOUR_INFURA_KEY'
],
17000: [ // Holesky
'https://ethereum-holesky.publicnode.com',
process.env.HOLESKY_INFURA_URL || 'https://holesky.infura.io/v3/YOUR_INFURA_KEY'
],
421614: [ // Arbitrum Sepolia
'https://sepolia-rollup.arbitrum.io/rpc'
],
84532: [ // Base Sepolia
'https://sepolia.base.org'
]
};
async getFallbackRPCs(chainId) {
try {
// Получаем ВСЕ RPC провайдеры для данной сети из базы данных
const rpcService = require('../services/rpcProviderService');
const providers = await rpcService.getAllRpcProviders();
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}`;
try {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(chainId));
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 logger = require('./logger');
const rpcService = require('../services/rpcProviderService');
class RPCConnectionManager {
constructor() {
@@ -19,13 +20,26 @@ class RPCConnectionManager {
/**
* Создает RPC соединение с retry логикой
* @param {string} rpcUrl - URL RPC
* @param {number} chainId - ID цепочки
* @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения
* @returns {Promise<Object>} - {provider, wallet, network}
*/
async createConnection(rpcUrl, privateKey, options = {}) {
async createConnection(chainId, privateKey, options = {}) {
const config = { ...this.retryConfig, ...options };
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
logger.info(`[RPC_MANAGER] Получен RPC URL для chainId ${chainId}: ${rpcUrl}`);
// КРИТИЧЕСКАЯ ПРОВЕРКА: если rpcUrl содержит 127.0.0.1:8545, это ошибка!
if (rpcUrl && rpcUrl.includes('127.0.0.1:8545')) {
logger.error(`[RPC_MANAGER] ❌ КРИТИЧЕСКАЯ ОШИБКА: Получен неправильный RPC URL: ${rpcUrl} для chainId ${chainId}`);
throw new Error(`Получен неправильный RPC URL: ${rpcUrl} для chainId ${chainId}`);
}
if (!rpcUrl) {
throw new Error(`RPC URL не найден для chainId ${chainId}`);
}
const connectionKey = `${rpcUrl}_${privateKey}`;
// Проверяем кэш
@@ -33,7 +47,8 @@ class RPCConnectionManager {
const cached = this.connections.get(connectionKey);
if (Date.now() - cached.timestamp < 60000) { // 1 минута кэш
logger.info(`[RPC_MANAGER] Используем кэшированное соединение: ${rpcUrl}`);
return cached.connection;
// Убеждаемся, что кэшированное соединение содержит rpcUrl
return { ...cached.connection, rpcUrl };
}
}
@@ -56,7 +71,7 @@ class RPCConnectionManager {
const wallet = new ethers.Wallet(privateKey, provider);
const connection = { provider, wallet, network };
const connection = { provider, wallet, network, rpcUrl };
// Кэшируем соединение
this.connections.set(connectionKey, {
@@ -84,21 +99,21 @@ class RPCConnectionManager {
/**
* Создает множественные RPC соединения с обработкой ошибок
* @param {Array} rpcUrls - Массив RPC URL
* @param {Array} chainIds - Массив chain ID
* @param {string} privateKey - Приватный ключ
* @param {Object} options - Опции соединения
* @returns {Promise<Array>} - Массив успешных соединений
*/
async createMultipleConnections(rpcUrls, privateKey, options = {}) {
logger.info(`[RPC_MANAGER] Создаем ${rpcUrls.length} RPC соединений...`);
async createMultipleConnections(chainIds, privateKey, options = {}) {
logger.info(`[RPC_MANAGER] Создаем ${chainIds.length} RPC соединений...`);
const connectionPromises = rpcUrls.map(async (rpcUrl, index) => {
const connectionPromises = chainIds.map(async (chainId, index) => {
try {
const connection = await this.createConnection(rpcUrl, privateKey, options);
return { index, rpcUrl, ...connection, success: true };
const connection = await this.createConnection(chainId, privateKey, options);
return { index, chainId, ...connection, success: true };
} catch (error) {
logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: ${rpcUrl} - ${error.message}`);
return { index, rpcUrl, error: error.message, success: false };
logger.error(`[RPC_MANAGER] ❌ Соединение ${index + 1} failed: chainId ${chainId} - ${error.message}`);
return { index, chainId, error: error.message, success: false };
}
});
@@ -106,10 +121,10 @@ class RPCConnectionManager {
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
logger.info(`[RPC_MANAGER] ✅ Успешных соединений: ${successful.length}/${rpcUrls.length}`);
logger.info(`[RPC_MANAGER] ✅ Успешных соединений: ${successful.length}/${chainIds.length}`);
if (failed.length > 0) {
logger.warn(`[RPC_MANAGER] ⚠️ Неудачных соединений: ${failed.length}`);
failed.forEach(f => logger.warn(`[RPC_MANAGER] - ${f.rpcUrl}: ${f.error}`));
failed.forEach(f => logger.warn(`[RPC_MANAGER] - ChainId ${f.chainId}: ${f.error}`));
}
if (successful.length === 0) {
@@ -183,13 +198,25 @@ class RPCConnectionManager {
'ENOTFOUND',
'ETIMEDOUT',
'RPC timeout',
'Transaction timeout'
'Transaction timeout',
'ECONNREFUSED',
'ENETUNREACH',
'EHOSTUNREACH'
];
const errorMessage = error.message.toLowerCase();
return retryableErrors.some(retryableError =>
const isRetryable = retryableErrors.some(retryableError =>
errorMessage.includes(retryableError.toLowerCase())
);
// Логируем информацию об ошибке для диагностики
if (isRetryable) {
logger.warn(`[RPC_MANAGER] Повторяемая ошибка: ${error.message}`);
} else {
logger.error(`[RPC_MANAGER] Неповторяемая ошибка: ${error.message}`);
}
return isRetryable;
}
// getNonceWithRetry функция удалена - используем nonceManager.getNonceWithRetry() вместо этого

View File

@@ -554,34 +554,56 @@ module.exports = {
// Обработчик запроса балансов токенов
async function handleTokenBalancesRequest(ws, address, userId) {
try {
// console.log(`[WebSocket] Запрос балансов для адреса: ${address}`); // Убрано избыточное логирование
console.log(`[WebSocket] Запрос балансов для адреса: ${address}`);
// Получаем балансы через отдельный сервис без зависимостей от wsHub
const balances = await tokenBalanceService.getUserTokenBalances(address);
console.log(`[WebSocket] Получены балансы для ${address}:`, balances);
console.log(`[WebSocket] Количество токенов:`, balances?.length || 0);
// Отправляем ответ клиенту
ws.send(JSON.stringify({
const response = {
type: 'token_balances_response',
data: {
address: address,
balances: balances,
timestamp: Date.now()
}
}));
};
// console.log(`[WebSocket] Отправлены балансы для ${address}:`, balances.length, 'токенов'); // Убрано избыточное логирование
console.log(`[WebSocket] Отправляем ответ:`, JSON.stringify(response, null, 2));
ws.send(JSON.stringify(response));
} catch (error) {
console.error('[WebSocket] Ошибка при получении балансов:', error);
// Определяем тип ошибки для лучшей диагностики
let errorType = 'Неизвестная ошибка';
const errorMessage = error.message || error.toString();
if (errorMessage.includes('timeout') || errorMessage.includes('TIMEOUT')) {
errorType = 'Таймаут соединения - возможно, нужен VPN';
} else if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND')) {
errorType = 'Не удается подключиться к RPC провайдеру';
} else if (errorMessage.includes('TLS') || errorMessage.includes('socket disconnected')) {
errorType = 'Проблема с TLS соединением - проверьте VPN';
} else if (errorMessage.includes('NETWORK_ERROR')) {
errorType = 'Ошибка сети - проверьте интернет-соединение';
}
// Отправляем ошибку клиенту
ws.send(JSON.stringify({
const errorResponse = {
type: 'token_balances_error',
data: {
address: address,
error: error.message,
error: errorType,
errorDetails: errorMessage,
timestamp: Date.now()
}
}));
};
console.log('[WebSocket] Отправляем ошибку клиенту:', JSON.stringify(errorResponse, null, 2));
ws.send(JSON.stringify(errorResponse));
}
}

View File

@@ -121,10 +121,13 @@
</div>
<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-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>
@@ -456,6 +459,22 @@ h3 {
font-size: var(--font-size-sm);
}
/* Стили для ошибок токенов */
.token-balance-row.token-error {
background-color: rgba(255, 0, 0, 0.1);
border: 1px solid rgba(255, 0, 0, 0.3);
border-radius: var(--radius-sm);
padding: var(--spacing-xs);
}
.token-error-message {
color: var(--color-danger);
font-size: var(--font-size-xs);
font-weight: bold;
flex: 1;
cursor: help;
}
/* Медиа-запросы для адаптивности */
@media screen and (min-width: 1200px) {
.wallet-sidebar {

View File

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

View File

@@ -51,9 +51,24 @@ export function useTokenBalancesWebSocket() {
// Обработчик ошибки
const handleTokenBalancesError = (data) => {
console.error('[useTokenBalancesWebSocket] Ошибка получения балансов:', data.error);
console.error('[useTokenBalancesWebSocket] Ошибка получения балансов:', data);
isLoadingTokens.value = false;
tokenBalances.value = [];
// Создаем объект с информацией об ошибке для отображения пользователю
const errorInfo = {
network: 'unknown',
tokenAddress: 'error',
tokenName: 'Ошибка получения балансов',
symbol: 'ERROR',
balance: '0',
minBalance: '0',
readonlyThreshold: 1,
editorThreshold: 1,
error: data.error || 'Неизвестная ошибка',
errorDetails: data.errorDetails || data.error
};
tokenBalances.value = [errorInfo];
};
// Обработчик обновления балансов

View File

@@ -906,6 +906,7 @@ import { useAuthContext } from '@/composables/useAuth';
import { usePermissions } from '@/composables/usePermissions';
import api from '@/api/axios';
import DeploymentWizard from '@/components/deployment/DeploymentWizard.vue';
import eventBus from '@/utils/eventBus';
const router = useRouter();
// Нормализация приватного ключа: убираем пробелы/"0x", посторонние символы,
@@ -924,19 +925,10 @@ function normalizePrivateKey(raw) {
const { address } = useAuthContext();
const { canManageSettings } = usePermissions();
// Подписываемся на централизованные события очистки и обновления данных
onMounted(() => {
window.addEventListener('clear-application-data', () => {
console.log('[DleDeployFormView] Clearing DLE deploy data');
// Очищаем данные при выходе из системы
// DleDeployFormView не нуждается в очистке данных
});
// Обработчики событий будут определены после функций clearAllData и resetUIState
window.addEventListener('refresh-application-data', () => {
console.log('[DleDeployFormView] Refreshing DLE deploy data');
checkAdminTokens(); // Обновляем данные при входе в систему
});
});
// Подписка на события авторизации (как в других файлах проекта)
let unsubscribe = null;
// Состояние для проверки админских токенов
const adminTokenCheck = ref({
@@ -945,6 +937,28 @@ const adminTokenCheck = ref({
error: null
});
// Обработка события изменения авторизации
const handleAuthEvent = (eventData) => {
console.log('[DleDeployFormView] Получено событие изменения авторизации:', eventData);
// Если пользователь отключился, сбрасываем все данные формы
if (!eventData.authenticated) {
console.log('[DleDeployFormView] User disconnected, clearing form data');
clearAllData();
resetUIState();
} else {
// При подключении обновляем проверку токенов
checkAdminTokens();
}
};
// Watcher для отслеживания изменений в правах доступа
watch(canManageSettings, (newValue, oldValue) => {
console.log('[DleDeployFormView] canManageSettings changed:', { oldValue, newValue });
// При изменении прав обновляем локальное состояние
adminTokenCheck.value.canManageSettings = newValue;
}, { immediate: true });
// Основные настройки DLE
const dleSettings = reactive({
// Юрисдикция
@@ -1573,12 +1587,17 @@ const clearAllData = () => {
// Очищаем также поиск адресов и флаги автовыбора
postalCodeInput.value = '';
searchResults.value = [];
isSearchingAddress.value = false;
autoSelectedOktmo.value = false;
lastApiResult.value = null;
// Сбрасываем выбранные уровни ОКВЭД
selectedOkvedLevel1.value = '';
selectedOkvedLevel2.value = '';
selectedOkvedLevel3.value = '';
selectedOkvedLevel4.value = '';
currentSelectedOkvedCode.value = '';
currentSelectedOkvedText.value = '';
// Очищаем мульти-чейн состояние
selectedNetworks.value = [];
@@ -1591,10 +1610,79 @@ const clearAllData = () => {
Object.keys(keyValidation).forEach(key => delete keyValidation[key]);
showUnifiedKey.value = false;
// Очищаем настройки деплоя
etherscanApiKey.value = '';
unifiedScanKeyVisible.value = false;
autoVerifyAfterDeploy.value = true;
showDeploymentWizard.value = false;
deployedDLEAddress.value = '';
// Очищаем localStorage
clearStoredData();
};
// Сброс состояния UI компонентов
const resetUIState = () => {
// Сбрасываем состояние загрузки
isLoadingCountries.value = false;
isLoadingRussianClassifiers.value = false;
isLoadingNetworks.value = false;
isLoadingOkvedLevel1.value = false;
isLoadingOkvedLevel2.value = false;
isLoadingOkvedLevel3.value = false;
isLoadingOkvedLevel4.value = false;
isLoadingKppCodes.value = false;
// Сбрасываем состояние админских токенов
adminTokenCheck.value = {
isLoading: false,
canManageSettings: false,
error: null
};
// Очищаем файл логотипа
logoFile.value = null;
logoPreviewUrl.value = '';
ensDomain.value = '';
ensResolvedUrl.value = '';
// Сбрасываем состояние видимости ключей
showPrivateKey.value = false;
showUnifiedKey.value = false;
console.log('[DleDeployFormView] UI state reset completed');
};
// Обработчики событий для очистки и обновления данных
const handleClearApplicationData = () => {
console.log('[DleDeployFormView] Clearing DLE deploy data');
// Очищаем все данные формы при выходе из системы
clearAllData();
// Сбрасываем состояние UI
resetUIState();
};
// handleRefreshApplicationData будет определен после checkAdminTokens
// Подписываемся на централизованные события очистки и обновления данных
onMounted(() => {
window.addEventListener('clear-application-data', handleClearApplicationData);
window.addEventListener('refresh-application-data', handleRefreshApplicationData);
// Подписка на события авторизации
unsubscribe = eventBus.on('auth-state-changed', handleAuthEvent);
});
onUnmounted(() => {
// Отписка от события при удалении компонента
if (unsubscribe) {
unsubscribe();
}
// Удаляем слушатели событий window
window.removeEventListener('clear-application-data', handleClearApplicationData);
window.removeEventListener('refresh-application-data', handleRefreshApplicationData);
});
// (Старые функции ОКВЭД удалены - заменены каскадной системой)
// Поиск по почтовому индексу (по кнопке)
@@ -2382,7 +2470,12 @@ onUnmounted(() => {
});
// Watcher для автоматического обновления адреса первого партнера при подключении кошелька
watch(address, (newAddress) => {
watch(address, (newAddress, oldAddress) => {
console.log('[DleDeployFormView] Address changed:', { oldAddress, newAddress });
// Обновляем состояние при изменении адреса (подключение/отключение кошелька)
checkAdminTokens();
if (newAddress && dleSettings.partners[0]) {
// Подставляем адрес, если поле пустое или пользователь только что подключил кошелек
if (!dleSettings.partners[0].address) {
@@ -2394,8 +2487,16 @@ watch(address, (newAddress) => {
// Функция проверки админских токенов
const checkAdminTokens = async () => {
console.log('[DleDeployFormView] checkAdminTokens called, address:', address.value);
// Небольшая задержка чтобы дать время useAuth обновить состояние
await new Promise(resolve => setTimeout(resolve, 100));
// Обновляем локальное состояние на основе текущих прав из usePermissions
adminTokenCheck.value.canManageSettings = canManageSettings.value;
if (!address.value) {
adminTokenCheck.value = { isLoading: false, canManageSettings: false, error: 'Кошелек не подключен' };
adminTokenCheck.value = { ...adminTokenCheck.value, isLoading: false, error: 'Кошелек не подключен' };
return;
}
@@ -2405,8 +2506,8 @@ const checkAdminTokens = async () => {
const response = await api.get(`/dle-v2/check-admin-tokens?address=${address.value}`);
if (response.data.success) {
adminTokenCheck.value = { ...adminTokenCheck.value, canManageSettings: response.data.data.userAccessLevel.hasAccess };
console.log('Проверка админских токенов:', response.data.data);
// Не перезаписываем canManageSettings, так как это управляется usePermissions
} else {
adminTokenCheck.value = { ...adminTokenCheck.value, error: response.data.message || 'Ошибка проверки токенов' };
}
@@ -2418,6 +2519,12 @@ const checkAdminTokens = async () => {
}
};
// Определяем handleRefreshApplicationData после checkAdminTokens
const handleRefreshApplicationData = () => {
console.log('[DleDeployFormView] Refreshing DLE deploy data');
checkAdminTokens(); // Обновляем данные при входе в систему
};
// Функции для работы с партнерами
const addPartner = () => {
dleSettings.partners.push({ address: '', amount: 1 });