feat: новая функция
This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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 {}; // Пустой объект - никаких хардкод цепочек
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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))",
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -565,7 +565,7 @@ class DLEV2Service {
|
||||
* @param {Array} allDles - Все DLE
|
||||
* @returns {Array} - Сгруппированные DLE
|
||||
*/
|
||||
groupMultichainDLEs(allDles) {
|
||||
async groupMultichainDLEs(allDles) {
|
||||
const groups = new Map();
|
||||
|
||||
for (const dle of allDles) {
|
||||
@@ -588,7 +588,7 @@ class DLEV2Service {
|
||||
groups.get(groupKey).networks.push({
|
||||
chainId: dle.chainId,
|
||||
address: dle.address,
|
||||
networkName: this.getRpcUrlForChain(dle.chainId)?.name || `Chain ${dle.chainId}`,
|
||||
networkName: (await this.getRpcUrlForChain(dle.chainId))?.name || `Chain ${dle.chainId}`,
|
||||
status: dle.status || 'active'
|
||||
});
|
||||
}
|
||||
@@ -610,16 +610,25 @@ class DLEV2Service {
|
||||
* @param {number} chainId - ID сети
|
||||
* @returns {Object|null} - Информация о RPC
|
||||
*/
|
||||
getRpcUrlForChain(chainId) {
|
||||
const rpcMappings = {
|
||||
1: { name: 'Ethereum Mainnet', url: 'https://mainnet.infura.io/v3/' },
|
||||
11155111: { name: 'Sepolia Testnet', url: 'https://sepolia.infura.io/v3/' },
|
||||
17000: { name: 'Holesky Testnet', url: 'https://holesky.infura.io/v3/' },
|
||||
421614: { name: 'Arbitrum Sepolia', url: 'https://sepolia-rollup.arbitrum.io/rpc' },
|
||||
84532: { name: 'Base Sepolia', url: 'https://sepolia.base.org' }
|
||||
};
|
||||
|
||||
return rpcMappings[chainId] || null;
|
||||
async getRpcUrlForChain(chainId) {
|
||||
try {
|
||||
// Получаем RPC URL из базы данных
|
||||
const rpcService = require('./rpcProviderService');
|
||||
const rpcUrl = await rpcService.getRpcUrlByChainId(chainId);
|
||||
|
||||
if (!rpcUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Возвращаем объект с RPC URL из базы данных
|
||||
return {
|
||||
name: `Chain ${chainId}`,
|
||||
url: rpcUrl
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`[DLE V2 Service] Ошибка получения RPC для chain_id ${chainId}:`, error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -639,7 +648,7 @@ class DLEV2Service {
|
||||
throw new Error(`RPC URL не найден для сети ${chainId}`);
|
||||
}
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
const provider = new ethers.JsonRpcProvider(await getRpcUrlByChainId(chainId));
|
||||
const balance = await provider.getBalance(wallet.address);
|
||||
|
||||
console.log(`💰 Баланс в сети ${chainId}: ${ethers.formatEther(balance)} ETH`);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 напрямую
|
||||
|
||||
87
backend/utils/loadRpcFromDatabase.js
Normal file
87
backend/utils/loadRpcFromDatabase.js
Normal 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 };
|
||||
@@ -23,10 +23,20 @@ class NonceManager {
|
||||
async getNonce(address, rpcUrl, chainId, options = {}) {
|
||||
const { timeout = 15000, maxRetries = 5 } = options; // Увеличиваем таймаут и попытки
|
||||
|
||||
// КРИТИЧЕСКАЯ ПРОВЕРКА: логируем входящие параметры
|
||||
console.log(`[NonceManager] getNonce вызван с параметрами: address=${address}, rpcUrl=${rpcUrl}, chainId=${chainId}`);
|
||||
|
||||
// КРИТИЧЕСКАЯ ПРОВЕРКА: если rpcUrl содержит 127.0.0.1:8545, это ошибка!
|
||||
if (rpcUrl && rpcUrl.includes('127.0.0.1:8545')) {
|
||||
console.error(`[NonceManager] ❌ КРИТИЧЕСКАЯ ОШИБКА: Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`);
|
||||
throw new Error(`Получен неправильный rpcUrl: ${rpcUrl} для chainId ${chainId}`);
|
||||
}
|
||||
|
||||
const cacheKey = `${address}-${chainId}`;
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
console.log(`[NonceManager] Попытка ${attempt}: создаем JsonRpcProvider с rpcUrl: ${rpcUrl}`);
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, {
|
||||
polling: false, // Отключаем polling для более быстрого получения nonce
|
||||
staticNetwork: true
|
||||
@@ -204,8 +214,8 @@ class NonceManager {
|
||||
console.warn(`[NonceManager] networkLoader недоступен для chainId ${chainId}, используем fallback: ${error.message}`);
|
||||
}
|
||||
|
||||
// Всегда добавляем fallback RPC для надежности
|
||||
const fallbackRPCs = this.getFallbackRPCs(chainId);
|
||||
// Всегда добавляем fallback RPC для надежности ИЗ БАЗЫ ДАННЫХ
|
||||
const fallbackRPCs = await this.getFallbackRPCs(chainId);
|
||||
for (const fallbackRpc of fallbackRPCs) {
|
||||
if (!rpcUrls.includes(fallbackRpc)) {
|
||||
rpcUrls.push(fallbackRpc);
|
||||
@@ -217,34 +227,31 @@ class NonceManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить список fallback RPC для сети
|
||||
* Получить список fallback RPC для сети ИЗ БАЗЫ ДАННЫХ
|
||||
* @param {number} chainId - ID сети
|
||||
* @returns {Array} - Массив RPC URL
|
||||
* @returns {Array} - Массив RPC URL из базы данных
|
||||
*/
|
||||
getFallbackRPCs(chainId) {
|
||||
const fallbackRPCs = {
|
||||
1: [ // Mainnet
|
||||
'https://eth.llamarpc.com',
|
||||
'https://rpc.ankr.com/eth',
|
||||
'https://ethereum.publicnode.com'
|
||||
],
|
||||
11155111: [ // Sepolia
|
||||
'https://rpc.sepolia.org',
|
||||
process.env.SEPOLIA_INFURA_URL || 'https://sepolia.infura.io/v3/YOUR_INFURA_KEY'
|
||||
],
|
||||
17000: [ // Holesky
|
||||
'https://ethereum-holesky.publicnode.com',
|
||||
process.env.HOLESKY_INFURA_URL || 'https://holesky.infura.io/v3/YOUR_INFURA_KEY'
|
||||
],
|
||||
421614: [ // Arbitrum Sepolia
|
||||
'https://sepolia-rollup.arbitrum.io/rpc'
|
||||
],
|
||||
84532: [ // Base Sepolia
|
||||
'https://sepolia.base.org'
|
||||
]
|
||||
};
|
||||
|
||||
return fallbackRPCs[chainId] || [];
|
||||
async getFallbackRPCs(chainId) {
|
||||
try {
|
||||
// Получаем ВСЕ RPC провайдеры для данной сети из базы данных
|
||||
const rpcService = require('../services/rpcProviderService');
|
||||
const providers = await rpcService.getAllRpcProviders();
|
||||
|
||||
// Фильтруем по chain_id
|
||||
const networkProviders = providers.filter(p => p.chain_id === chainId);
|
||||
|
||||
if (networkProviders.length === 0) {
|
||||
console.warn(`[NonceManager] Нет RPC провайдеров в базе данных для chain_id: ${chainId}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
// Возвращаем только RPC URL из базы данных
|
||||
return networkProviders.map(p => p.rpc_url).filter(url => url);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[NonceManager] Ошибка получения RPC из базы данных для chain_id ${chainId}:`, error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -276,7 +283,7 @@ class NonceManager {
|
||||
const cacheKey = `${address}-${chainId}`;
|
||||
|
||||
try {
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
const provider = new ethers.JsonRpcProvider(await rpcService.getRpcUrlByChainId(chainId));
|
||||
const networkNonce = await provider.getTransactionCount(address, 'pending');
|
||||
|
||||
// Принудительно обновляем кэш
|
||||
|
||||
105
backend/utils/proxyManager.js
Normal file
105
backend/utils/proxyManager.js
Normal 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()
|
||||
};
|
||||
@@ -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() вместо этого
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user