ваше сообщение коммита

This commit is contained in:
2025-09-30 00:23:37 +03:00
parent ca718e3178
commit 4b03951b31
77 changed files with 17161 additions and 7255 deletions

View File

@@ -43,6 +43,16 @@ router.get('/nonce', async (req, res) => {
return res.status(400).json({ error: 'Address is required' });
}
// Очищаем истекшие nonce перед генерацией нового
try {
await db.getQuery()(
'DELETE FROM nonces WHERE expires_at < NOW()'
);
logger.info(`[nonce] Cleaned up expired nonces`);
} catch (cleanupError) {
logger.warn(`[nonce] Error cleaning up expired nonces: ${cleanupError.message}`);
}
// Генерируем случайный nonce
const nonce = crypto.randomBytes(16).toString('hex');
logger.info(`[nonce] Generated nonce: ${nonce}`);
@@ -136,9 +146,9 @@ router.post('/verify', async (req, res) => {
console.error('Error reading encryption key:', keyError);
}
// Проверяем nonce в базе данных
// Проверяем nonce в базе данных с проверкой времени истечения
const nonceResult = await db.getQuery()(
'SELECT nonce_encrypted FROM nonces WHERE identity_value_encrypted = encrypt_text($1, $2)',
'SELECT nonce_encrypted, expires_at FROM nonces WHERE identity_value_encrypted = encrypt_text($1, $2)',
[normalizedAddressLower, encryptionKey]
);
@@ -147,6 +157,14 @@ router.post('/verify', async (req, res) => {
return res.status(401).json({ success: false, error: 'Nonce not found' });
}
// Проверяем, не истек ли срок действия nonce
const expiresAt = new Date(nonceResult.rows[0].expires_at);
const now = new Date();
if (now > expiresAt) {
logger.error(`[verify] Nonce expired for address: ${normalizedAddressLower}. Expired at: ${expiresAt}, Now: ${now}`);
return res.status(401).json({ success: false, error: 'Nonce expired' });
}
// Расшифровываем nonce из базы данных
const storedNonce = await db.getQuery()(
'SELECT decrypt_text(nonce_encrypted, $1) as nonce FROM nonces WHERE identity_value_encrypted = encrypt_text($2, $1)',
@@ -156,9 +174,12 @@ router.post('/verify', async (req, res) => {
logger.info(`[verify] Stored nonce from DB: ${storedNonce.rows[0]?.nonce}`);
logger.info(`[verify] Nonce from request: ${nonce}`);
logger.info(`[verify] Nonce match: ${storedNonce.rows[0]?.nonce === nonce}`);
logger.info(`[verify] Stored nonce length: ${storedNonce.rows[0]?.nonce?.length}`);
logger.info(`[verify] Request nonce length: ${nonce?.length}`);
if (storedNonce.rows.length === 0 || storedNonce.rows[0].nonce !== nonce) {
logger.error(`[verify] Invalid nonce for address: ${normalizedAddressLower}. Expected: ${storedNonce.rows[0]?.nonce}, Got: ${nonce}`);
logger.error(`[verify] Stored nonce type: ${typeof storedNonce.rows[0]?.nonce}, Request nonce type: ${typeof nonce}`);
return res.status(401).json({ success: false, error: 'Invalid nonce' });
}

View File

@@ -15,6 +15,29 @@ const router = express.Router();
const { ethers } = require('ethers');
const rpcProviderService = require('../services/rpcProviderService');
// Функция для получения списка сетей из БД для данного DLE
async function getSupportedChainIds(dleAddress) {
try {
const DeployParamsService = require('../services/deployParamsService');
const deployParamsService = new DeployParamsService();
const deployments = await deployParamsService.getAllDeployments();
// Находим деплой с данным адресом
for (const deployment of deployments) {
if (deployment.dleAddress === dleAddress && deployment.supportedChainIds) {
console.log(`[Blockchain] Найдены сети для DLE ${dleAddress}:`, deployment.supportedChainIds);
return deployment.supportedChainIds;
}
}
// Fallback к стандартным сетям
return [17000, 11155111, 421614, 84532];
} catch (error) {
console.error(`[Blockchain] Ошибка получения сетей для DLE ${dleAddress}:`, error);
return [17000, 11155111, 421614, 84532];
}
}
// Чтение данных DLE из блокчейна
router.post('/read-dle-info', async (req, res) => {
try {
@@ -31,7 +54,9 @@ router.post('/read-dle-info', async (req, res) => {
// Определяем корректную сеть для данного адреса (или используем chainId из запроса)
let provider, rpcUrl, targetChainId = req.body.chainId;
const candidateChainIds = [11155111, 17000, 421614, 84532];
// Получаем список сетей из базы данных для данного DLE
const candidateChainIds = await getSupportedChainIds(dleAddress);
if (targetChainId) {
rpcUrl = await rpcProviderService.getRpcUrlByChainId(Number(targetChainId));
if (!rpcUrl) {
@@ -43,18 +68,46 @@ router.post('/read-dle-info', async (req, res) => {
return res.status(400).json({ success: false, error: `По адресу ${dleAddress} нет контракта в сети ${targetChainId}` });
}
} else {
// Ищем контракт во всех сетях
let foundContracts = [];
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') { provider = prov; rpcUrl = url; targetChainId = cid; break; }
if (code && code !== '0x') {
// Контракт найден, currentChainId теперь равен block.chainid
foundContracts.push({
chainId: cid,
currentChainId: cid, // currentChainId = block.chainid = cid
provider: prov,
rpcUrl: url
});
}
} catch (_) {}
}
if (!provider) {
if (foundContracts.length === 0) {
return res.status(400).json({ success: false, error: 'Не удалось найти сеть, где по адресу есть контракт' });
}
// Выбираем первую доступную сеть (currentChainId - это governance chain, не primary)
const primaryContract = foundContracts[0];
if (primaryContract) {
// Используем основную сеть для чтения данных
provider = primaryContract.provider;
rpcUrl = primaryContract.rpcUrl;
targetChainId = primaryContract.chainId;
} else {
// Fallback: берем первый найденный контракт
const firstContract = foundContracts[0];
provider = firstContract.provider;
rpcUrl = firstContract.rpcUrl;
targetChainId = firstContract.chainId;
}
}
// ABI для чтения данных DLE
@@ -75,7 +128,7 @@ router.post('/read-dle-info', async (req, res) => {
const dleInfo = await dle.getDLEInfo();
const totalSupply = await dle.totalSupply();
const quorumPercentage = await dle.quorumPercentage();
const currentChainId = await dle.getCurrentChainId();
const currentChainId = targetChainId; // currentChainId = block.chainid = targetChainId
// Читаем логотип
let logoURI = '';
@@ -205,6 +258,27 @@ router.post('/read-dle-info', async (req, res) => {
console.log(`[Blockchain] Ошибка при чтении модулей:`, modulesError.message);
}
// Собираем информацию о всех развернутых сетях
const deployedNetworks = [];
if (typeof foundContracts !== 'undefined') {
for (const contract of foundContracts) {
deployedNetworks.push({
chainId: contract.chainId,
address: dleAddress,
currentChainId: contract.currentChainId,
isPrimary: false // currentChainId - это governance chain, не primary
});
}
} else {
// Если chainId был указан в запросе, добавляем только эту сеть
deployedNetworks.push({
chainId: targetChainId,
address: dleAddress,
currentChainId: Number(currentChainId),
isPrimary: Number(currentChainId) === targetChainId
});
}
const blockchainData = {
name: dleInfo.name,
symbol: dleInfo.symbol,
@@ -225,7 +299,8 @@ router.post('/read-dle-info', async (req, res) => {
currentChainId: Number(currentChainId),
rpcUsed: rpcUrl,
participantCount: participantCount,
modules: modules // Информация о модулях
modules: modules, // Информация о модулях
deployedNetworks: deployedNetworks // Информация о всех развернутых сетях
};
console.log(`[Blockchain] Данные DLE прочитаны из блокчейна:`, blockchainData);
@@ -260,8 +335,30 @@ router.post('/get-proposals', async (req, res) => {
console.log(`[Blockchain] Получение списка предложений для DLE: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -345,7 +442,7 @@ router.post('/get-proposals', async (req, res) => {
initiator: proposal.initiator,
governanceChainId: Number(proposal.governanceChainId),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
targetChains: proposal.targets.map(chainId => Number(chainId)),
targetChains: proposal.targets.map(targetChainId => Number(targetChainId)),
isPassed: isPassed,
blockNumber: events[i].blockNumber
};
@@ -400,8 +497,30 @@ router.post('/get-proposal-info', async (req, res) => {
console.log(`[Blockchain] Получение информации о предложении ${proposalId} в DLE: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -424,7 +543,7 @@ router.post('/get-proposal-info', async (req, res) => {
const isPassed = await dle.checkProposalResult(proposalId);
// governanceChainId не сохраняется в предложении, используем текущую цепочку
const governanceChainId = 11155111; // Sepolia chain ID
const governanceChainId = targetChainId || 11155111; // Используем найденную сеть или Sepolia по умолчанию
const proposalInfo = {
description: proposal.description,
@@ -472,8 +591,30 @@ router.post('/deactivate-dle', async (req, res) => {
console.log(`[Blockchain] Проверка возможности деактивации DLE: ${dleAddress} пользователем: ${userAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -543,7 +684,30 @@ router.post('/check-deactivation-proposal-result', async (req, res) => {
console.log(`[Blockchain] Проверка результата предложения деактивации: ${proposalId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -598,7 +762,30 @@ router.post('/load-deactivation-proposals', async (req, res) => {
console.log(`[Blockchain] Загрузка предложений деактивации для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -679,7 +866,30 @@ router.post('/execute-proposal', async (req, res) => {
console.log(`[Blockchain] Исполнение предложения ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -732,7 +942,30 @@ router.post('/cancel-proposal', async (req, res) => {
console.log(`[Blockchain] Отмена предложения ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -794,7 +1027,30 @@ router.post('/get-governance-params', async (req, res) => {
console.log(`[Blockchain] Получение параметров управления для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -847,7 +1103,30 @@ router.post('/get-proposal-state', async (req, res) => {
console.log(`[Blockchain] Получение состояния предложения ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -899,7 +1178,30 @@ router.post('/get-proposal-votes', async (req, res) => {
console.log(`[Blockchain] Получение голосов по предложению ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -954,7 +1256,30 @@ router.post('/get-proposals-count', async (req, res) => {
console.log(`[Blockchain] Получение количества предложений для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1005,7 +1330,30 @@ router.post('/list-proposals', async (req, res) => {
console.log(`[Blockchain] Получение списка предложений для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1058,7 +1406,30 @@ router.post('/get-voting-power-at', async (req, res) => {
console.log(`[Blockchain] Получение голосующей силы для ${voter} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1111,7 +1482,30 @@ router.post('/get-quorum-at', async (req, res) => {
console.log(`[Blockchain] Получение требуемого кворума для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1163,7 +1557,30 @@ router.post('/get-token-balance', async (req, res) => {
console.log(`[Blockchain] Получение баланса токенов для ${account} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1215,7 +1632,30 @@ router.post('/get-total-supply', async (req, res) => {
console.log(`[Blockchain] Получение общего предложения токенов для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1266,7 +1706,30 @@ router.post('/is-active', async (req, res) => {
console.log(`[Blockchain] Проверка активности DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1323,7 +1786,30 @@ router.post('/get-dle-analytics', async (req, res) => {
console.log(`[Blockchain] Получение аналитики DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1447,7 +1933,30 @@ router.post('/get-dle-history', async (req, res) => {
console.log(`[Blockchain] Получение истории DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
const candidateChainIds = await getSupportedChainIds(dleAddress);
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,

View File

@@ -47,6 +47,28 @@ router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res) => {
hardhatProcess.on('close', (code) => {
if (code === 0) {
console.log('✅ Компиляция завершена успешно');
// Автоматически генерируем ABI для фронтенда
try {
const { generateABIFile } = require('../scripts/generate-abi');
generateABIFile();
console.log('✅ ABI файл автоматически обновлен');
} catch (abiError) {
console.warn('⚠️ Ошибка генерации ABI:', abiError.message);
}
// Автоматически генерируем flattened контракт для верификации
try {
const { generateFlattened } = require('../scripts/generate-flattened');
generateFlattened().then(() => {
console.log('✅ Flattened контракт автоматически обновлен');
}).catch((flattenError) => {
console.warn('⚠️ Ошибка генерации flattened контракта:', flattenError.message);
});
} catch (flattenError) {
console.warn('⚠️ Ошибка генерации flattened контракта:', flattenError.message);
}
res.json({
success: true,
message: 'Смарт-контракты скомпилированы успешно',

View File

@@ -29,7 +29,41 @@ router.post('/get-dle-analytics', async (req, res) => {
console.log(`[DLE Analytics] Получение аналитики для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -165,7 +199,41 @@ router.post('/get-dle-history', async (req, res) => {
console.log(`[DLE Analytics] Получение истории для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,

View File

@@ -29,8 +29,41 @@ router.post('/read-dle-info', async (req, res) => {
console.log(`[DLE Core] Чтение данных DLE из блокчейна: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [11155111, 421614, 84532, 17000]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -205,11 +238,27 @@ router.post('/get-governance-params', async (req, res) => {
console.log(`[DLE Core] Получение параметров управления для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Получаем RPC URL из параметров деплоя или используем Sepolia как fallback
let rpcUrl;
try {
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
const supportedChainIds = params.supportedChainIds || [];
const chainId = supportedChainIds.length > 0 ? supportedChainIds[0] : 11155111;
rpcUrl = await rpcProviderService.getRpcUrlByChainId(chainId);
} else {
rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем Sepolia:', error);
rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
error: 'RPC URL не найден'
});
}
@@ -258,11 +307,27 @@ router.post('/is-active', async (req, res) => {
console.log(`[DLE Core] Проверка активности DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Получаем RPC URL из параметров деплоя или используем Sepolia как fallback
let rpcUrl;
try {
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
const supportedChainIds = params.supportedChainIds || [];
const chainId = supportedChainIds.length > 0 ? supportedChainIds[0] : 11155111;
rpcUrl = await rpcProviderService.getRpcUrlByChainId(chainId);
} else {
rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем Sepolia:', error);
rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
error: 'RPC URL не найден'
});
}
@@ -309,8 +374,41 @@ router.post('/deactivate-dle', async (req, res) => {
console.log(`[DLE Core] Проверка возможности деактивации DLE: ${dleAddress} пользователем: ${userAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [11155111, 421614, 84532, 17000]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,

View File

@@ -30,7 +30,41 @@ router.post('/get-extended-history', async (req, res) => {
console.log(`[DLE History] Получение расширенной истории для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,

View File

@@ -575,6 +575,26 @@ router.post('/get-all-modules', async (req, res) => {
return networks[chainId] || `Chain ${chainId}`;
}
function getFallbackRpcUrl(chainId) {
const fallbackUrls = {
11155111: 'https://eth-sepolia.nodereal.io/v1/56dec8028bae4f26b76099a42dae2b52',
17000: 'https://ethereum-holesky.publicnode.com',
421614: 'https://sepolia-rollup.arbitrum.io/rpc',
84532: 'https://sepolia.base.org'
};
return fallbackUrls[chainId] || null;
}
function getEtherscanUrl(chainId) {
const etherscanUrls = {
11155111: 'https://sepolia.etherscan.io',
17000: 'https://holesky.etherscan.io',
421614: 'https://sepolia.arbiscan.io',
84532: 'https://sepolia.basescan.org'
};
return etherscanUrls[chainId] || null;
}
function getModuleDescription(moduleType) {
const descriptions = {
treasury: 'Казначейство DLE - управление финансами, депозиты, выводы, дивиденды',
@@ -590,37 +610,57 @@ router.post('/get-all-modules', async (req, res) => {
console.log(`[DLE Modules] Найдено типов модулей: ${formattedModules.length}`);
// Получаем поддерживаемые сети из модулей
const supportedNetworks = [
{
chainId: 11155111,
networkName: 'Sepolia',
rpcUrl: 'https://eth-sepolia.nodereal.io/v1/56dec8028bae4f26b76099a42dae2b52',
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
// Получаем поддерживаемые сети из параметров деплоя
let supportedNetworks = [];
try {
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
const supportedChainIds = params.supportedChainIds || [];
const rpcUrls = params.rpcUrls || params.rpc_urls || {};
supportedNetworks = supportedChainIds.map((chainId, index) => ({
chainId: Number(chainId),
networkName: getNetworkName(Number(chainId)),
rpcUrl: rpcUrls[chainId] || getFallbackRpcUrl(chainId),
etherscanUrl: getEtherscanUrl(chainId),
networkIndex: index
}));
}
];
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя:', error);
// Fallback для совместимости
supportedNetworks = [
{
chainId: 11155111,
networkName: 'Sepolia',
rpcUrl: 'https://eth-sepolia.nodereal.io/v1/56dec8028bae4f26b76099a42dae2b52',
etherscanUrl: 'https://sepolia.etherscan.io',
networkIndex: 0
},
{
chainId: 17000,
networkName: 'Holesky',
rpcUrl: 'https://ethereum-holesky.publicnode.com',
etherscanUrl: 'https://holesky.etherscan.io',
networkIndex: 1
},
{
chainId: 421614,
networkName: 'Arbitrum Sepolia',
rpcUrl: 'https://sepolia-rollup.arbitrum.io/rpc',
etherscanUrl: 'https://sepolia.arbiscan.io',
networkIndex: 2
},
{
chainId: 84532,
networkName: 'Base Sepolia',
rpcUrl: 'https://sepolia.base.org',
etherscanUrl: 'https://sepolia.basescan.org',
networkIndex: 3
}
];
}
res.json({
success: true,
@@ -642,10 +682,57 @@ router.post('/get-all-modules', async (req, res) => {
}
});
// Создать предложение о добавлении модуля
// Получить deploymentId по адресу DLE
router.post('/get-deployment-id', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Modules] Поиск deploymentId для DLE: ${dleAddress}`);
const DeployParamsService = require('../services/deployParamsService');
const deployParamsService = new DeployParamsService();
// Ищем параметры деплоя по адресу DLE
const result = await deployParamsService.getDeployParamsByDleAddress(dleAddress);
if (!result) {
return res.status(404).json({
success: false,
error: 'DeploymentId не найден для данного адреса DLE'
});
}
await deployParamsService.close();
res.json({
success: true,
data: {
deploymentId: result.deployment_id,
dleAddress: result.dle_address,
deploymentStatus: result.deployment_status
}
});
} catch (error) {
console.error('[DLE Modules] Ошибка при получении deploymentId:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении deploymentId: ' + error.message
});
}
});
// Создать предложение о добавлении модуля (с автоматической оплатой газа)
router.post('/create-add-module-proposal', async (req, res) => {
try {
const { dleAddress, description, duration, moduleId, moduleAddress, chainId } = req.body;
const { dleAddress, description, duration, moduleId, moduleAddress, chainId, deploymentId } = req.body;
if (!dleAddress || !description || !duration || !moduleId || !moduleAddress || !chainId) {
return res.status(400).json({
@@ -666,14 +753,54 @@ router.post('/create-add-module-proposal', async (req, res) => {
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Получаем приватный ключ из параметров деплоя
let privateKey;
if (deploymentId) {
const DeployParamsService = require('../services/deployParamsService');
const deployParamsService = new DeployParamsService();
const params = await deployParamsService.getDeployParams(deploymentId);
if (!params || !params.privateKey) {
return res.status(400).json({
success: false,
error: 'Приватный ключ не найден в параметрах деплоя'
});
}
privateKey = params.privateKey;
await deployParamsService.close();
} else {
// Fallback к переменной окружения
privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
return res.status(400).json({
success: false,
error: 'Приватный ключ не найден. Укажите deploymentId или установите PRIVATE_KEY'
});
}
}
// Создаем кошелек
const wallet = new ethers.Wallet(privateKey, provider);
console.log(`[DLE Modules] Используем кошелек: ${wallet.address}`);
const dleAbi = [
"function createAddModuleProposal(string memory _description, uint256 _duration, bytes32 _moduleId, address _moduleAddress, uint256 _chainId) external returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
// Подготавливаем данные для транзакции (не отправляем)
const txData = await dle.createAddModuleProposal.populateTransaction(
// Отправляем транзакцию автоматически
console.log(`[DLE Modules] Отправляем транзакцию создания предложения...`);
console.log(`[DLE Modules] Параметры:`, {
description,
duration,
moduleId,
moduleAddress,
chainId
});
const tx = await dle.createAddModuleProposal(
description,
duration,
moduleId,
@@ -681,16 +808,130 @@ router.post('/create-add-module-proposal', async (req, res) => {
chainId
);
console.log(`[DLE Modules] Данные транзакции подготовлены:`, txData);
console.log(`[DLE Modules] Транзакция отправлена: ${tx.hash}`);
console.log(`[DLE Modules] Ожидаем подтверждения...`);
// Ждем подтверждения
const receipt = await tx.wait();
// Пробуем получить proposalId из возвращаемого значения транзакции
let proposalIdFromReturn = null;
try {
// Если функция возвращает значение, оно должно быть в receipt
if (receipt.logs && receipt.logs.length > 0) {
console.log(`[DLE Modules] Ищем ProposalCreated в ${receipt.logs.length} логах транзакции...`);
// Ищем событие с возвращаемым значением
for (let i = 0; i < receipt.logs.length; i++) {
const log = receipt.logs[i];
console.log(`[DLE Modules] Лог ${i}:`, {
address: log.address,
topics: log.topics,
data: log.data
});
try {
const parsedLog = dle.interface.parseLog(log);
console.log(`[DLE Modules] Парсинг лога ${i}:`, parsedLog);
if (parsedLog && parsedLog.name === 'ProposalCreated') {
proposalIdFromReturn = parsedLog.args.proposalId.toString();
console.log(`[DLE Modules] ✅ Получен proposalId из события: ${proposalIdFromReturn}`);
break;
}
} catch (e) {
console.log(`[DLE Modules] Ошибка парсинга лога ${i}:`, e.message);
// Пробуем альтернативный способ - ищем по топикам
if (log.topics && log.topics.length > 0) {
// ProposalCreated имеет сигнатуру: ProposalCreated(uint256,address,string)
// Первый топик - это хеш сигнатуры события
const proposalCreatedTopic = ethers.id("ProposalCreated(uint256,address,string)");
if (log.topics[0] === proposalCreatedTopic) {
console.log(`[DLE Modules] Найден топик ProposalCreated, извлекаем proposalId из данных...`);
// proposalId находится в indexed параметрах (топиках)
if (log.topics.length > 1) {
proposalIdFromReturn = BigInt(log.topics[1]).toString();
console.log(`[DLE Modules] ✅ Извлечен proposalId из топика: ${proposalIdFromReturn}`);
break;
}
}
}
}
}
}
} catch (e) {
console.log(`[DLE Modules] Ошибка при получении proposalId из возвращаемого значения:`, e.message);
}
console.log(`[DLE Modules] Транзакция подтверждена:`, {
hash: receipt.hash,
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed.toString(),
logsCount: receipt.logs.length,
status: receipt.status
});
// Используем proposalId из события, если он найден
let proposalId = proposalIdFromReturn;
// Если не найден в событии, пробуем другие способы
if (!proposalId) {
console.log(`[DLE Modules] Анализируем ${receipt.logs.length} логов для поиска ProposalCreated...`);
if (receipt.logs && receipt.logs.length > 0) {
// Ищем событие ProposalCreated
for (let i = 0; i < receipt.logs.length; i++) {
const log = receipt.logs[i];
console.log(`[DLE Modules] Лог ${i}:`, {
address: log.address,
topics: log.topics,
data: log.data
});
try {
const parsedLog = dle.interface.parseLog(log);
console.log(`[DLE Modules] Парсинг лога ${i}:`, parsedLog);
if (parsedLog && parsedLog.name === 'ProposalCreated') {
proposalId = parsedLog.args.proposalId.toString();
console.log(`[DLE Modules] ✅ Найден ProposalCreated с ID: ${proposalId}`);
break;
}
} catch (e) {
console.log(`[DLE Modules] Ошибка парсинга лога ${i}:`, e.message);
// Пробуем альтернативный способ - ищем по топикам
if (log.topics && log.topics.length > 0) {
// ProposalCreated имеет сигнатуру: ProposalCreated(uint256,address,string)
// Первый топик - это хеш сигнатуры события
const proposalCreatedTopic = ethers.id("ProposalCreated(uint256,address,string)");
if (log.topics[0] === proposalCreatedTopic) {
console.log(`[DLE Modules] Найден топик ProposalCreated, извлекаем proposalId из данных...`);
// proposalId находится в indexed параметрах (топиках)
if (log.topics.length > 1) {
proposalId = BigInt(log.topics[1]).toString();
console.log(`[DLE Modules] ✅ Извлечен proposalId из топика: ${proposalId}`);
break;
}
}
}
}
}
}
}
if (!proposalId) {
console.warn(`[DLE Modules] ⚠️ Не удалось извлечь proposalId из логов транзакции`);
console.warn(`[DLE Modules] ⚠️ Это критическая проблема - без proposalId нельзя исполнить предложение!`);
} else {
console.log(`[DLE Modules] ✅ Успешно получен proposalId: ${proposalId}`);
}
res.json({
success: true,
data: {
to: dleAddress,
data: txData.data,
value: "0x0",
gasLimit: "0x1e8480", // 2,000,000 gas
message: "Подготовлены данные для создания предложения о добавлении модуля. Отправьте транзакцию через MetaMask."
transactionHash: receipt.hash,
proposalId: proposalId,
gasUsed: receipt.gasUsed.toString(),
message: `Предложение о добавлении модуля успешно создано! ID: ${proposalId || 'неизвестно'}`
}
});
@@ -717,7 +958,41 @@ router.post('/create-remove-module-proposal', async (req, res) => {
console.log(`[DLE Modules] Создание предложения об удалении модуля: ${moduleId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -1255,8 +1530,9 @@ async function createStandardJsonInput(contractName, moduleAddress, dleAddress,
settings: {
optimizer: {
enabled: true,
runs: 200
runs: 0
},
viaIR: true,
evmVersion: "paris",
outputSelection: {
"*": {
@@ -1265,7 +1541,7 @@ async function createStandardJsonInput(contractName, moduleAddress, dleAddress,
},
libraries: {},
remappings: [
"@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/"
"@openzeppelin/contracts/=@openzeppelin/contracts/"
]
}
};
@@ -1904,8 +2180,9 @@ router.post('/deploy-module-all-networks', async (req, res) => {
const provider = new ethers.JsonRpcProvider(network.rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
// Получаем текущий nonce
const currentNonce = await wallet.getNonce();
// Используем NonceManager для правильного управления nonce
const { nonceManager } = require('../utils/nonceManager');
const currentNonce = await nonceManager.getNonce(wallet.address, network.rpcUrl, network.chainId);
console.log(`[DLE Modules] Текущий nonce для сети ${network.chainId}: ${currentNonce}`);
// Получаем фабрику контракта
@@ -2057,7 +2334,7 @@ router.post('/verify-dle-all-networks', async (req, res) => {
const supportedChainIds = Array.isArray(saved?.networks)
? saved.networks.map(n => Number(n.chainId)).filter(v => !isNaN(v))
: (saved?.governanceSettings?.supportedChainIds || []);
const currentChainId = Number(saved?.governanceSettings?.currentChainId || network.chainId);
const currentChainId = Number(saved?.governanceSettings?.currentChainId || 1); // governance chain, не network.chainId
// Создаем стандартный JSON input для верификации
const standardJsonInput = {
@@ -2070,7 +2347,7 @@ router.post('/verify-dle-all-networks', async (req, res) => {
settings: {
optimizer: {
enabled: true,
runs: 1
runs: 0
},
viaIR: true,
outputSelection: {
@@ -2123,7 +2400,7 @@ router.post('/verify-dle-all-networks', async (req, res) => {
const initPartners = Array.isArray(found?.initialPartners) ? found.initialPartners : [];
const initAmounts = Array.isArray(found?.initialAmounts) ? found.initialAmounts : [];
const scIds = Array.isArray(found?.networks) ? found.networks.map(n => Number(n.chainId)).filter(v => !isNaN(v)) : supportedChainIds;
const currentCid = Number(found?.governanceSettings?.currentChainId || found?.networks?.[0]?.chainId || network.chainId);
const currentCid = Number(found?.governanceSettings?.currentChainId || 1); // governance chain, не network.chainId
const encoded = ethers.AbiCoder.defaultAbiCoder().encode(
['tuple(string,string,string,string,uint256,string,uint256,uint256,address[],uint256[],uint256[])', 'uint256', 'address'],
[[name, symbol, location, coordinates, jurisdiction, oktmo, kpp, quorumPercentage, initPartners, initAmounts.map(a => BigInt(a)), scIds], BigInt(currentCid), initializer]

View File

@@ -1,442 +1,124 @@
/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
const express = require('express');
const router = express.Router();
const { ethers } = require('ethers');
const rpcProviderService = require('../services/rpcProviderService');
const deployParamsService = require('../services/deployParamsService');
// Получить поддерживаемые сети
router.post('/get-supported-chains', async (req, res) => {
/**
* Получить адрес контракта в указанной сети для мультичейн голосования
* POST /api/dle-core/get-multichain-contracts
*/
router.post('/get-multichain-contracts', async (req, res) => {
try {
const { dleAddress } = req.body;
const { originalContract, targetChainId } = req.body;
if (!dleAddress) {
console.log('🔍 [MULTICHAIN] Поиск контракта для мультичейн голосования:', {
originalContract,
targetChainId
});
if (!originalContract || !targetChainId) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
error: 'Не указан originalContract или targetChainId'
});
}
console.log(`[DLE Multichain] Получение поддерживаемых сетей для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
// Ищем контракт в указанной сети
// Для мультичейн контрактов с одинаковым адресом (детерминированный деплой)
// или контракты в разных сетях с разными адресами
// Сначала проверяем, есть ли контракт с таким же адресом в целевой сети
const contractsInTargetNetwork = await deployParamsService.getContractsByChainId(targetChainId);
console.log('📊 [MULTICHAIN] Контракты в целевой сети:', contractsInTargetNetwork);
// Ищем контракт в целевой сети (все контракты в targetChainId уже отфильтрованы)
const targetContract = contractsInTargetNetwork[0]; // Берем первый контракт в целевой сети
if (targetContract) {
console.log('✅ [MULTICHAIN] Найден контракт в целевой сети:', targetContract.dleAddress);
return res.json({
success: true,
contractAddress: targetContract.dleAddress,
chainId: targetChainId,
source: 'database'
});
}
// Если не найден контракт в целевой сети, проверяем мультичейн развертывание
// с одинаковым адресом (CREATE2)
const { ethers } = require('ethers');
// Получаем RPC URL из параметров деплоя
let rpcUrl;
try {
// Получаем последние параметры деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
const rpcUrls = params.rpcUrls || params.rpc_urls || {};
rpcUrl = rpcUrls[targetChainId];
}
// Если не найден в параметрах, используем 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'
};
rpcUrl = fallbackConfigs[targetChainId];
}
if (!rpcUrl) {
return res.status(400).json({
success: false,
error: `Неподдерживаемая сеть: ${targetChainId}`
});
}
} catch (error) {
console.error('❌ Ошибка получения RPC URL:', error);
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
error: 'Ошибка получения конфигурации сети'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getSupportedChainCount() external view returns (uint256)",
"function getSupportedChainId(uint256 _index) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем количество поддерживаемых сетей
const chainCount = await dle.getSupportedChainCount();
// Получаем ID каждой сети
const supportedChains = [];
for (let i = 0; i < Number(chainCount); i++) {
const chainId = await dle.getSupportedChainId(i);
supportedChains.push(chainId);
}
console.log(`[DLE Multichain] Поддерживаемые сети:`, supportedChains);
res.json({
success: true,
data: {
chains: supportedChains.map(chainId => Number(chainId))
try {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const contractCode = await provider.getCode(originalContract);
if (contractCode && contractCode !== '0x') {
console.log('✅ [MULTICHAIN] Контракт существует в целевой сети с тем же адресом (CREATE2)');
return res.json({
success: true,
contractAddress: originalContract,
chainId: targetChainId,
source: 'blockchain'
});
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при получении поддерживаемых сетей:', error);
res.status(500).json({
} catch (blockchainError) {
console.warn('⚠️ [MULTICHAIN] Ошибка проверки контракта в блокчейне:', blockchainError.message);
}
// Контракт не найден
console.log('❌ [MULTICHAIN] Контракт не найден в целевой сети');
return res.json({
success: false,
error: 'Ошибка при получении поддерживаемых сетей: ' + error.message
error: 'Контракт не найден в целевой сети'
});
} catch (error) {
console.error('❌ [MULTICHAIN] Ошибка поиска мультичейн контракта:', error);
return res.status(500).json({
success: false,
error: 'Внутренняя ошибка сервера'
});
}
});
// Проверить поддержку сети
router.post('/is-chain-supported', async (req, res) => {
try {
const { dleAddress, chainId } = req.body;
if (!dleAddress || chainId === undefined) {
return res.status(400).json({
success: false,
error: 'Адрес DLE и ID сети обязательны'
});
}
console.log(`[DLE Multichain] Проверка поддержки сети ${chainId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function isChainSupported(uint256 _chainId) external view returns (bool)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Проверяем поддержку сети
const isSupported = await dle.isChainSupported(chainId);
console.log(`[DLE Multichain] Поддержка сети ${chainId}: ${isSupported}`);
res.json({
success: true,
data: {
chainId: Number(chainId),
isSupported: isSupported
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при проверке поддержки сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при проверке поддержки сети: ' + error.message
});
}
});
// Получить количество поддерживаемых сетей
router.post('/get-supported-chain-count', async (req, res) => {
try {
const { dleAddress } = req.body;
if (!dleAddress) {
return res.status(400).json({
success: false,
error: 'Адрес DLE обязателен'
});
}
console.log(`[DLE Multichain] Получение количества поддерживаемых сетей для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getSupportedChainCount() external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем количество поддерживаемых сетей
const count = await dle.getSupportedChainCount();
console.log(`[DLE Multichain] Количество поддерживаемых сетей: ${count}`);
res.json({
success: true,
data: {
count: Number(count)
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при получении количества поддерживаемых сетей:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении количества поддерживаемых сетей: ' + error.message
});
}
});
// Получить ID сети по индексу
router.post('/get-supported-chain-id', async (req, res) => {
try {
const { dleAddress, index } = req.body;
if (!dleAddress || index === undefined) {
return res.status(400).json({
success: false,
error: 'Адрес DLE и индекс обязательны'
});
}
console.log(`[DLE Multichain] Получение ID сети по индексу ${index} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getSupportedChainId(uint256 _index) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем ID сети по индексу
const chainId = await dle.getSupportedChainId(index);
console.log(`[DLE Multichain] ID сети по индексу ${index}: ${chainId}`);
res.json({
success: true,
data: {
index: Number(index),
chainId: Number(chainId)
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при получении ID сети по индексу:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении ID сети по индексу: ' + error.message
});
}
});
// Проверить подключение к сети
router.post('/check-chain-connection', async (req, res) => {
try {
const { dleAddress, chainId } = req.body;
if (!dleAddress || chainId === undefined) {
return res.status(400).json({
success: false,
error: 'Адрес DLE и ID сети обязательны'
});
}
console.log(`[DLE Multichain] Проверка подключения к сети ${chainId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function checkChainConnection(uint256 _chainId) external view returns (bool)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Проверяем подключение к сети
const isAvailable = await dle.checkChainConnection(chainId);
console.log(`[DLE Multichain] Подключение к сети ${chainId}: ${isAvailable}`);
res.json({
success: true,
data: {
chainId: Number(chainId),
isAvailable: isAvailable
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при проверке подключения к сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при проверке подключения к сети: ' + error.message
});
}
});
// Проверить готовность к синхронизации
router.post('/check-sync-readiness', async (req, res) => {
try {
const { dleAddress, proposalId } = req.body;
if (!dleAddress || proposalId === undefined) {
return res.status(400).json({
success: false,
error: 'Адрес DLE и ID предложения обязательны'
});
}
console.log(`[DLE Multichain] Проверка готовности к синхронизации предложения ${proposalId} для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function checkSyncReadiness(uint256 _proposalId) external view returns (bool)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Проверяем готовность к синхронизации
const allChainsReady = await dle.checkSyncReadiness(proposalId);
console.log(`[DLE Multichain] Готовность к синхронизации предложения ${proposalId}: ${allChainsReady}`);
res.json({
success: true,
data: {
proposalId: Number(proposalId),
allChainsReady: allChainsReady
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при проверке готовности к синхронизации:', error);
res.status(500).json({
success: false,
error: 'Ошибка при проверке готовности к синхронизации: ' + error.message
});
}
});
// Синхронизировать во все сети
router.post('/sync-to-all-chains', async (req, res) => {
try {
const { dleAddress, proposalId, userAddress, privateKey } = req.body;
if (!dleAddress || proposalId === undefined || !userAddress || !privateKey) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны, включая приватный ключ'
});
}
console.log(`[DLE Multichain] Синхронизация предложения ${proposalId} во все сети для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [
"function syncToAllChains(uint256 _proposalId) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
// Синхронизируем во все сети
const tx = await dle.syncToAllChains(proposalId);
const receipt = await tx.wait();
console.log(`[DLE Multichain] Синхронизация выполнена:`, receipt);
res.json({
success: true,
data: {
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при синхронизации во все сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при синхронизации во все сети: ' + error.message
});
}
});
// Исполнить предложение по подписям
router.post('/execute-proposal-by-signatures', async (req, res) => {
try {
const { dleAddress, proposalId, signatures, userAddress, privateKey } = req.body;
if (!dleAddress || proposalId === undefined || !signatures || !userAddress || !privateKey) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны, включая приватный ключ'
});
}
console.log(`[DLE Multichain] Исполнение предложения ${proposalId} по подписям для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [
"function executeProposalBySignatures(uint256 _proposalId, bytes[] calldata _signatures) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
// Исполняем предложение по подписям
const tx = await dle.executeProposalBySignatures(proposalId, signatures);
const receipt = await tx.wait();
console.log(`[DLE Multichain] Предложение исполнено по подписям:`, receipt);
res.json({
success: true,
data: {
transactionHash: receipt.hash
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при исполнении предложения по подписям:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении предложения по подписям: ' + error.message
});
}
});
module.exports = router;
module.exports = router;

View File

@@ -0,0 +1,346 @@
/**
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
* All rights reserved.
*
* This software is proprietary and confidential.
* Unauthorized copying, modification, or distribution is prohibited.
*
* For licensing inquiries: info@hb3-accelerator.com
* Website: https://hb3-accelerator.com
* GitHub: https://github.com/HB3-ACCELERATOR
*/
const express = require('express');
const router = express.Router();
const { ethers } = require('ethers');
const rpcProviderService = require('../services/rpcProviderService');
const DeployParamsService = require('../services/deployParamsService');
/**
* Получить информацию о мультиконтрактном предложении
* @route POST /api/dle-multichain/get-proposal-multichain-info
*/
router.post('/get-proposal-multichain-info', async (req, res) => {
try {
const { dleAddress, proposalId, governanceChainId } = req.body;
if (!dleAddress || proposalId === undefined || !governanceChainId) {
return res.status(400).json({
success: false,
error: 'Адрес DLE, ID предложения и ID сети голосования обязательны'
});
}
console.log(`[DLE Multichain] Получение информации о предложении ${proposalId} для DLE: ${dleAddress}`);
// Получаем RPC URL для сети голосования
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(governanceChainId);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: `RPC URL для сети ${governanceChainId} не найден`
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function proposals(uint256) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, bytes memory operation, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targetChains)",
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function getSupportedChainCount() external view returns (uint256)",
"function getSupportedChainId(uint256 _index) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем данные предложения
const proposal = await dle.proposals(proposalId);
const state = await dle.getProposalState(proposalId);
const result = await dle.checkProposalResult(proposalId);
// Получаем поддерживаемые сети
const chainCount = await dle.getSupportedChainCount();
const supportedChains = [];
for (let i = 0; i < chainCount; i++) {
const chainId = await dle.getSupportedChainId(i);
supportedChains.push(Number(chainId));
}
const proposalInfo = {
id: Number(proposal.id),
description: proposal.description,
forVotes: Number(proposal.forVotes),
againstVotes: Number(proposal.againstVotes),
executed: proposal.executed,
canceled: proposal.canceled,
deadline: Number(proposal.deadline),
initiator: proposal.initiator,
operation: proposal.operation,
governanceChainId: Number(proposal.governanceChainId),
targetChains: proposal.targetChains.map(chain => Number(chain)),
snapshotTimepoint: Number(proposal.snapshotTimepoint),
state: Number(state),
isPassed: result.passed,
quorumReached: result.quorumReached,
supportedChains: supportedChains,
canExecuteInTargetChains: result.passed && result.quorumReached && !proposal.executed && !proposal.canceled
};
console.log(`[DLE Multichain] Информация о предложении получена:`, proposalInfo);
res.json({
success: true,
data: proposalInfo
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при получении информации о предложении:', error);
res.status(500).json({
success: false,
error: 'Ошибка при получении информации о предложении: ' + error.message
});
}
});
/**
* Исполнить предложение во всех целевых сетях
* @route POST /api/dle-multichain/execute-in-all-target-chains
*/
router.post('/execute-in-all-target-chains', async (req, res) => {
try {
const { dleAddress, proposalId, deploymentId, userAddress } = req.body;
if (!dleAddress || proposalId === undefined || !deploymentId || !userAddress) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Multichain] Исполнение предложения ${proposalId} во всех целевых сетях для DLE: ${dleAddress}`);
// Получаем параметры деплоя
const deployParamsService = new DeployParamsService();
const deployParams = await deployParamsService.getDeployParams(deploymentId);
if (!deployParams || !deployParams.privateKey) {
return res.status(400).json({
success: false,
error: 'Приватный ключ не найден в параметрах деплоя'
});
}
// Получаем информацию о предложении
const proposalInfoResponse = await fetch(`${req.protocol}://${req.get('host')}/api/dle-multichain/get-proposal-multichain-info`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
dleAddress,
proposalId,
governanceChainId: deployParams.currentChainId
})
});
const proposalInfo = await proposalInfoResponse.json();
if (!proposalInfo.success) {
return res.status(400).json({
success: false,
error: 'Не удалось получить информацию о предложении'
});
}
const { targetChains, canExecuteInTargetChains } = proposalInfo.data;
if (!canExecuteInTargetChains) {
return res.status(400).json({
success: false,
error: 'Предложение не готово к исполнению в целевых сетях'
});
}
if (targetChains.length === 0) {
return res.status(400).json({
success: false,
error: 'У предложения нет целевых сетей для исполнения'
});
}
// Исполняем в каждой целевой сети
const executionResults = [];
for (const targetChainId of targetChains) {
try {
console.log(`[DLE Multichain] Исполнение в сети ${targetChainId}`);
const result = await executeProposalInChain(
dleAddress,
proposalId,
targetChainId,
deployParams.privateKey,
userAddress
);
executionResults.push({
chainId: targetChainId,
success: true,
transactionHash: result.transactionHash
});
} catch (error) {
console.error(`[DLE Multichain] Ошибка исполнения в сети ${targetChainId}:`, error.message);
executionResults.push({
chainId: targetChainId,
success: false,
error: error.message
});
}
}
const successCount = executionResults.filter(r => r.success).length;
const totalCount = executionResults.length;
console.log(`[DLE Multichain] Исполнение завершено: ${successCount}/${totalCount} успешно`);
res.json({
success: true,
data: {
proposalId,
targetChains,
executionResults,
summary: {
total: totalCount,
successful: successCount,
failed: totalCount - successCount
}
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при исполнении во всех целевых сетях:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении во всех целевых сетях: ' + error.message
});
}
});
/**
* Исполнить предложение в конкретной целевой сети
* @route POST /api/dle-multichain/execute-in-target-chain
*/
router.post('/execute-in-target-chain', async (req, res) => {
try {
const { dleAddress, proposalId, targetChainId, deploymentId, userAddress } = req.body;
if (!dleAddress || proposalId === undefined || !targetChainId || !deploymentId || !userAddress) {
return res.status(400).json({
success: false,
error: 'Все поля обязательны'
});
}
console.log(`[DLE Multichain] Исполнение предложения ${proposalId} в сети ${targetChainId} для DLE: ${dleAddress}`);
// Получаем параметры деплоя
const deployParamsService = new DeployParamsService();
const deployParams = await deployParamsService.getDeployParams(deploymentId);
if (!deployParams || !deployParams.privateKey) {
return res.status(400).json({
success: false,
error: 'Приватный ключ не найден в параметрах деплоя'
});
}
// Исполняем в целевой сети
const result = await executeProposalInChain(
dleAddress,
proposalId,
targetChainId,
deployParams.privateKey,
userAddress
);
res.json({
success: true,
data: {
proposalId,
targetChainId,
transactionHash: result.transactionHash,
blockNumber: result.blockNumber
}
});
} catch (error) {
console.error('[DLE Multichain] Ошибка при исполнении в целевой сети:', error);
res.status(500).json({
success: false,
error: 'Ошибка при исполнении в целевой сети: ' + error.message
});
}
});
/**
* Вспомогательная функция для исполнения предложения в конкретной сети
*/
async function executeProposalInChain(dleAddress, proposalId, chainId, privateKey, userAddress) {
// Получаем RPC URL для целевой сети
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(chainId);
if (!rpcUrl) {
throw new Error(`RPC URL для сети ${chainId} не найден`);
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
const wallet = new ethers.Wallet(privateKey, provider);
const dleAbi = [
"function executeProposalBySignatures(uint256 _proposalId, address[] calldata signers, bytes[] calldata signatures) external"
];
const dle = new ethers.Contract(dleAddress, dleAbi, wallet);
// Для простоты используем подпись от одного адреса (кошелька с приватным ключом)
// В реальности нужно собрать подписи от держателей токенов
const signers = [wallet.address];
const signatures = []; // TODO: Реализовать сбор подписей
// Временная заглушка - используем прямое исполнение если это возможно
// В реальности нужно реализовать сбор подписей от держателей токенов
try {
// Пытаемся исполнить напрямую (если это сеть голосования)
const directExecuteAbi = [
"function executeProposal(uint256 _proposalId) external"
];
const directDle = new ethers.Contract(dleAddress, directExecuteAbi, wallet);
const tx = await directDle.executeProposal(proposalId);
const receipt = await tx.wait();
return {
transactionHash: receipt.hash,
blockNumber: receipt.blockNumber
};
} catch (directError) {
// Если прямое исполнение невозможно, используем подписи
if (signatures.length === 0) {
throw new Error('Необходимо собрать подписи от держателей токенов для исполнения в целевой сети');
}
const tx = await dle.executeProposalBySignatures(proposalId, signers, signatures);
const receipt = await tx.wait();
return {
transactionHash: receipt.hash,
blockNumber: receipt.blockNumber
};
}
}
module.exports = router;

View File

@@ -29,137 +29,297 @@ router.post('/get-proposals', async (req, res) => {
console.log(`[DLE Proposals] Получение списка предложений для DLE: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// ABI для чтения предложений (используем правильные функции из смарт-контракта)
const dleAbi = [
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"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)",
"function quorumPercentage() external view returns (uint256)",
"function getPastTotalSupply(uint256 timepoint) external view returns (uint256)",
"event ProposalCreated(uint256 proposalId, address initiator, string description)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем события ProposalCreated для определения количества предложений
const currentBlock = await provider.getBlockNumber();
const fromBlock = Math.max(0, currentBlock - 10000); // Последние 10000 блоков
const events = await dle.queryFilter('ProposalCreated', fromBlock, currentBlock);
console.log(`[DLE Proposals] Найдено событий ProposalCreated: ${events.length}`);
console.log(`[DLE Proposals] Диапазон блоков: ${fromBlock} - ${currentBlock}`);
const proposals = [];
// Читаем информацию о каждом предложении
for (let i = 0; i < events.length; i++) {
// Получаем поддерживаемые сети DLE из контракта
let supportedChains = [];
try {
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
const proposalId = events[i].args.proposalId;
console.log(`[DLE Proposals] Читаем предложение ID: ${proposalId}`);
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
console.log(`[DLE Proposals] Не удалось найти сеть для адреса ${dleAddress}`);
// Fallback к известным сетям
supportedChains = [11155111, 17000, 421614, 84532];
console.log(`[DLE Proposals] Используем fallback сети:`, supportedChains);
return;
}
if (rpcUrl) {
const provider = new ethers.JsonRpcProvider(rpcUrl);
const dleAbi = [
"function getSupportedChainCount() external view returns (uint256)",
"function getSupportedChainId(uint256 _index) external view returns (uint256)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Пробуем несколько раз для новых предложений
let proposalState, isPassed, quorumReached, forVotes, againstVotes, quorumRequired;
let retryCount = 0;
const maxRetries = 1;
const chainCount = await dle.getSupportedChainCount();
console.log(`[DLE Proposals] Количество поддерживаемых сетей: ${chainCount}`);
while (retryCount < maxRetries) {
for (let i = 0; i < Number(chainCount); i++) {
const chainId = await dle.getSupportedChainId(i);
supportedChains.push(Number(chainId));
}
console.log(`[DLE Proposals] Поддерживаемые сети из контракта:`, supportedChains);
}
} catch (error) {
console.log(`[DLE Proposals] Ошибка получения поддерживаемых сетей из контракта:`, error.message);
// Fallback к известным сетям
supportedChains = [11155111, 17000, 421614, 84532];
console.log(`[DLE Proposals] Используем fallback сети:`, supportedChains);
}
const allProposals = [];
// Ищем предложения во всех поддерживаемых сетях
for (const chainId of supportedChains) {
try {
console.log(`[DLE Proposals] Поиск предложений в сети ${chainId}...`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(chainId);
if (!rpcUrl) {
console.log(`[DLE Proposals] RPC URL для сети ${chainId} не найден, пропускаем`);
continue;
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// ABI для чтения предложений (используем getProposalSummary для мультиконтрактов)
const dleAbi = [
"function getProposalState(uint256 _proposalId) external view returns (uint8 state)",
"function checkProposalResult(uint256 _proposalId) external view returns (bool passed, bool quorumReached)",
"function getProposalSummary(uint256 _proposalId) external view returns (uint256 id, string memory description, uint256 forVotes, uint256 againstVotes, bool executed, bool canceled, uint256 deadline, address initiator, uint256 governanceChainId, uint256 snapshotTimepoint, uint256[] memory targetChains)",
"function quorumPercentage() external view returns (uint256)",
"function getPastTotalSupply(uint256 timepoint) external view returns (uint256)",
"function totalSupply() external view returns (uint256)",
"event ProposalCreated(uint256 proposalId, address initiator, string description)"
];
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Получаем события ProposalCreated для определения количества предложений
const currentBlock = await provider.getBlockNumber();
const fromBlock = Math.max(0, currentBlock - 10000); // Последние 10000 блоков
const events = await dle.queryFilter('ProposalCreated', fromBlock, currentBlock);
console.log(`[DLE Proposals] Найдено событий ProposalCreated в сети ${chainId}: ${events.length}`);
console.log(`[DLE Proposals] Диапазон блоков: ${fromBlock} - ${currentBlock}`);
// Читаем информацию о каждом предложении
for (let i = 0; i < events.length; i++) {
try {
proposalState = await dle.getProposalState(proposalId);
const result = await dle.checkProposalResult(proposalId);
isPassed = result.passed;
quorumReached = result.quorumReached;
const proposalId = events[i].args.proposalId;
console.log(`[DLE Proposals] Читаем предложение ID: ${proposalId}`);
// Пробуем несколько раз для новых предложений
let proposalState, isPassed, quorumReached, forVotes, againstVotes, quorumRequired, currentTotalSupply, quorumPct;
let retryCount = 0;
const maxRetries = 1;
while (retryCount < maxRetries) {
try {
proposalState = await dle.getProposalState(proposalId);
const result = await dle.checkProposalResult(proposalId);
isPassed = result.passed;
quorumReached = result.quorumReached;
// Получаем данные о голосах из структуры Proposal (включая мультиконтрактные поля)
try {
const proposalData = await dle.getProposalSummary(proposalId);
forVotes = Number(proposalData.forVotes);
againstVotes = Number(proposalData.againstVotes);
// Вычисляем требуемый кворум
quorumPct = Number(await dle.quorumPercentage());
const pastSupply = Number(await dle.getPastTotalSupply(proposalData.snapshotTimepoint));
quorumRequired = Math.floor((pastSupply * quorumPct) / 100);
// Получаем текущий totalSupply для отображения
currentTotalSupply = Number(await dle.totalSupply());
console.log(`[DLE Proposals] Кворум для предложения ${proposalId}:`, {
quorumPercentage: quorumPct,
pastSupply: pastSupply,
quorumRequired: quorumRequired,
quorumPercentageFormatted: `${quorumPct}%`,
snapshotTimepoint: proposalData.snapshotTimepoint,
pastSupplyFormatted: `${(pastSupply / 10**18).toFixed(2)} DLE`,
quorumRequiredFormatted: `${(quorumRequired / 10**18).toFixed(2)} DLE`
});
} catch (voteError) {
console.log(`[DLE Proposals] Ошибка получения голосов для предложения ${proposalId}:`, voteError.message);
forVotes = 0;
againstVotes = 0;
quorumRequired = 0;
currentTotalSupply = 0;
quorumPct = 0;
}
break; // Успешно прочитали
} catch (error) {
retryCount++;
console.log(`[DLE Proposals] Попытка ${retryCount} чтения предложения ${proposalId} не удалась:`, error.message);
if (retryCount < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 2000)); // Ждем 2 секунды
} else {
throw error; // Превышено количество попыток
}
}
}
console.log(`[DLE Proposals] Данные предложения ${proposalId}:`, {
id: Number(proposalId),
description: events[i].args.description,
state: Number(proposalState),
isPassed: isPassed,
quorumReached: quorumReached,
forVotes: Number(forVotes),
againstVotes: Number(againstVotes),
quorumRequired: Number(quorumRequired),
initiator: events[i].args.initiator
});
// Фильтруем предложения по времени - только за последние 30 дней
const block = await provider.getBlock(events[i].blockNumber);
const proposalTime = block.timestamp;
const currentTime = Math.floor(Date.now() / 1000);
const thirtyDaysAgo = currentTime - (30 * 24 * 60 * 60); // 30 дней назад
if (proposalTime < thirtyDaysAgo) {
console.log(`[DLE Proposals] Пропускаем старое предложение ${proposalId} (${new Date(proposalTime * 1000).toISOString()})`);
continue;
}
// Показываем все предложения, включая выполненные и отмененные
// Согласно контракту: 0=Pending, 1=Succeeded, 2=Defeated, 3=Executed, 4=Canceled, 5=ReadyForExecution
// Убрали фильтрацию выполненных и отмененных предложений для отображения в UI
// Создаем уникальный ID, включающий chainId
const uniqueId = `${chainId}-${proposalId}`;
// Получаем мультиконтрактные данные из proposalData (если доступны)
let operation = null;
let governanceChainId = null;
let targetChains = [];
let decodedOperation = null;
let operationDescription = null;
// Получаем данные о голосах из структуры Proposal
try {
const proposalData = await dle.proposals(proposalId);
forVotes = Number(proposalData.forVotes);
againstVotes = Number(proposalData.againstVotes);
const proposalData = await dle.getProposalSummary(proposalId);
governanceChainId = Number(proposalData.governanceChainId);
targetChains = proposalData.targetChains.map(chain => Number(chain));
// Вычисляем требуемый кворум
const quorumPct = Number(await dle.quorumPercentage());
const pastSupply = Number(await dle.getPastTotalSupply(proposalData.snapshotTimepoint));
quorumRequired = Math.floor((pastSupply * quorumPct) / 100);
} catch (voteError) {
console.log(`[DLE Proposals] Ошибка получения голосов для предложения ${proposalId}:`, voteError.message);
forVotes = 0;
againstVotes = 0;
quorumRequired = 0;
// Получаем operation из отдельного вызова (если нужно)
// operation не возвращается в getProposalSummary, но это не критично для мультиконтрактов
operation = null; // Пока не реализовано
// Декодируем операцию (если доступна)
if (operation && operation !== '0x') {
const { decodeOperation, formatOperation } = require('../utils/operationDecoder');
decodedOperation = decodeOperation(operation);
operationDescription = formatOperation(decodedOperation);
}
} catch (error) {
console.log(`[DLE Proposals] Не удалось получить мультиконтрактные данные для предложения ${proposalId}:`, error.message);
}
const proposalInfo = {
id: Number(proposalId),
uniqueId: uniqueId,
description: events[i].args.description,
state: Number(proposalState),
isPassed: isPassed,
quorumReached: quorumReached,
forVotes: Number(forVotes),
againstVotes: Number(againstVotes),
quorumRequired: Number(quorumRequired),
totalSupply: Number(currentTotalSupply || 0), // Добавляем totalSupply
contractQuorumPercentage: Number(quorumPct), // Добавляем процент кворума из контракта
initiator: events[i].args.initiator,
blockNumber: events[i].blockNumber,
transactionHash: events[i].transactionHash,
chainId: chainId, // Добавляем информацию о сети
timestamp: proposalTime,
createdAt: new Date(proposalTime * 1000).toISOString(),
executed: Number(proposalState) === 3, // 3 = Executed
canceled: Number(proposalState) === 4, // 4 = Canceled
// Мультиконтрактные поля
operation: operation,
governanceChainId: governanceChainId,
targetChains: targetChains,
isMultichain: targetChains && targetChains.length > 0,
decodedOperation: decodedOperation,
operationDescription: operationDescription
};
// Проверяем, нет ли уже такого предложения (по уникальному ID)
const existingProposal = allProposals.find(p => p.uniqueId === uniqueId);
if (!existingProposal) {
allProposals.push(proposalInfo);
} else {
console.log(`[DLE Proposals] Пропускаем дубликат предложения ${uniqueId}`);
}
} catch (error) {
console.log(`[DLE Proposals] Ошибка при чтении предложения ${i}:`, error.message);
// Если это ошибка декодирования, возможно предложение еще не полностью записано
if (error.message.includes('could not decode result data')) {
console.log(`[DLE Proposals] Предложение ${i} еще не полностью синхронизировано, пропускаем`);
continue;
}
break; // Успешно прочитали
} catch (error) {
retryCount++;
console.log(`[DLE Proposals] Попытка ${retryCount} чтения предложения ${proposalId} не удалась:`, error.message);
if (retryCount < maxRetries) {
await new Promise(resolve => setTimeout(resolve, 2000)); // Ждем 2 секунды
} else {
throw error; // Превышено количество попыток
}
// Продолжаем с следующим предложением
}
}
console.log(`[DLE Proposals] Данные предложения ${proposalId}:`, {
id: Number(proposalId),
description: events[i].args.description,
state: Number(proposalState),
isPassed: isPassed,
quorumReached: quorumReached,
forVotes: Number(forVotes),
againstVotes: Number(againstVotes),
quorumRequired: Number(quorumRequired),
initiator: events[i].args.initiator
});
console.log(`[DLE Proposals] Найдено предложений в сети ${chainId}: ${events.length}`);
const proposalInfo = {
id: Number(proposalId),
description: events[i].args.description,
state: Number(proposalState),
isPassed: isPassed,
quorumReached: quorumReached,
forVotes: Number(forVotes),
againstVotes: Number(againstVotes),
quorumRequired: Number(quorumRequired),
initiator: events[i].args.initiator,
blockNumber: events[i].blockNumber,
transactionHash: events[i].transactionHash
};
proposals.push(proposalInfo);
} catch (error) {
console.log(`[DLE Proposals] Ошибка при чтении предложения ${i}:`, error.message);
// Если это ошибка декодирования, возможно предложение еще не полностью записано
if (error.message.includes('could not decode result data')) {
console.log(`[DLE Proposals] Предложение ${i} еще не полностью синхронизировано, пропускаем`);
continue;
}
// Продолжаем с следующим предложением
console.log(`[DLE Proposals] Ошибка при поиске предложений в сети ${chainId}:`, error.message);
// Продолжаем с следующей сетью
}
}
// Сортируем по ID предложения (новые сверху)
proposals.sort((a, b) => b.id - a.id);
// Сортируем по времени создания (новые сверху), затем по ID
allProposals.sort((a, b) => {
if (a.timestamp !== b.timestamp) {
return b.timestamp - a.timestamp;
}
return b.id - a.id;
});
console.log(`[DLE Proposals] Найдено предложений: ${proposals.length}`);
console.log(`[DLE Proposals] Найдено предложений: ${allProposals.length}`);
res.json({
success: true,
data: {
proposals: proposals,
totalCount: proposals.length
proposals: allProposals,
totalCount: allProposals.length
}
});
@@ -186,8 +346,41 @@ router.post('/get-proposal-info', async (req, res) => {
console.log(`[DLE Proposals] Получение информации о предложении ${proposalId} в DLE: ${dleAddress}`);
// Получаем RPC URL для Sepolia
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -864,6 +1057,9 @@ router.post('/vote-proposal', async (req, res) => {
const dle = new ethers.Contract(dleAddress, dleAbi, provider);
// Пропускаем проверку hasVoted - функция не существует в контракте
console.log(`[DLE Proposals] Пропускаем проверку hasVoted - полагаемся на смарт-контракт`);
// Подготавливаем данные для транзакции (не отправляем)
const txData = await dle.vote.populateTransaction(proposalId, support);
@@ -889,6 +1085,53 @@ router.post('/vote-proposal', async (req, res) => {
}
});
// Проверить статус голосования пользователя
router.post('/check-vote-status', async (req, res) => {
try {
const { dleAddress, proposalId, voterAddress } = req.body;
if (!dleAddress || proposalId === undefined || !voterAddress) {
return res.status(400).json({
success: false,
error: 'Необходимы dleAddress, proposalId и voterAddress'
});
}
console.log(`[DLE Proposals] Проверка статуса голосования для ${voterAddress} по предложению ${proposalId} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'RPC URL для Sepolia не найден'
});
}
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Функция hasVoted не существует в контракте DLE
console.log(`[DLE Proposals] Функция hasVoted не поддерживается в контракте DLE`);
const hasVoted = false; // Всегда возвращаем false, так как функция не существует
res.json({
success: true,
data: {
hasVoted: hasVoted,
voterAddress: voterAddress,
proposalId: proposalId
}
});
} catch (error) {
console.error('[DLE Proposals] Ошибка при проверке статуса голосования:', error);
res.status(500).json({
success: false,
error: 'Ошибка при проверке статуса голосования: ' + error.message
});
}
});
// Endpoint для отслеживания подтверждения транзакций голосования
router.post('/track-vote-transaction', async (req, res) => {
try {

View File

@@ -29,7 +29,41 @@ router.post('/get-token-balance', async (req, res) => {
console.log(`[DLE Tokens] Получение баланса токенов для аккаунта: ${account} в DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -81,7 +115,41 @@ router.post('/get-total-supply', async (req, res) => {
console.log(`[DLE Tokens] Получение общего предложения токенов для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
@@ -132,7 +200,41 @@ router.post('/get-token-holders', async (req, res) => {
console.log(`[DLE Tokens] Получение держателей токенов для DLE: ${dleAddress}`);
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
// Определяем корректную сеть для данного адреса
let rpcUrl, targetChainId;
let candidateChainIds = [17000, 11155111, 421614, 84532]; // Fallback
try {
// Получаем поддерживаемые сети из параметров деплоя
const latestParams = await deployParamsService.getLatestDeployParams(1);
if (latestParams.length > 0) {
const params = latestParams[0];
candidateChainIds = params.supportedChainIds || candidateChainIds;
}
} catch (error) {
console.error('❌ Ошибка получения параметров деплоя, используем fallback:', error);
}
for (const cid of candidateChainIds) {
try {
const url = await rpcProviderService.getRpcUrlByChainId(cid);
if (!url) continue;
const prov = new ethers.JsonRpcProvider(url);
const code = await prov.getCode(dleAddress);
if (code && code !== '0x') {
rpcUrl = url;
targetChainId = cid;
break;
}
} catch (_) {}
}
if (!rpcUrl) {
return res.status(500).json({
success: false,
error: 'Не удалось найти сеть, где по адресу есть контракт'
});
}
if (!rpcUrl) {
return res.status(500).json({
success: false,

View File

@@ -12,8 +12,8 @@
const express = require('express');
const router = express.Router();
const DLEV2Service = require('../services/dleV2Service');
const dleV2Service = new DLEV2Service();
const UnifiedDeploymentService = require('../services/unifiedDeploymentService');
const unifiedDeploymentService = new UnifiedDeploymentService();
const logger = require('../utils/logger');
const auth = require('../middleware/auth');
const path = require('path');
@@ -38,7 +38,7 @@ async function executeDeploymentInBackground(deploymentId, dleParams) {
deploymentTracker.addLog(deploymentId, '🚀 Начинаем деплой DLE контракта', 'info');
// Выполняем деплой с передачей deploymentId для WebSocket обновлений
const result = await dleV2Service.createDLE(dleParams, deploymentId);
const result = await unifiedDeploymentService.createDLE(dleParams, deploymentId);
// Завершаем успешно
deploymentTracker.completeDeployment(deploymentId, result.data);
@@ -114,7 +114,7 @@ router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res, next) =>
*/
router.get('/', async (req, res, next) => {
try {
const dles = dleV2Service.getAllDLEs();
const dles = await unifiedDeploymentService.getAllDeployments();
res.json({
success: true,
@@ -490,13 +490,8 @@ router.get('/verify/status/:address', auth.requireAuth, async (req, res) => {
router.post('/verify/refresh/:address', auth.requireAuth, auth.requireAdmin, async (req, res) => {
try {
const { address } = req.params;
let { etherscanApiKey } = req.body || {};
if (!etherscanApiKey) {
try {
const { getSecret } = require('../services/secretStore');
etherscanApiKey = await getSecret('ETHERSCAN_V2_API_KEY');
} catch(_) {}
}
const ApiKeyManager = require('../utils/apiKeyManager');
const etherscanApiKey = ApiKeyManager.getEtherscanApiKey({}, req.body);
const data = verificationStore.read(address);
if (!data || !data.chains) return res.json({ success: true, data });
@@ -504,7 +499,7 @@ router.post('/verify/refresh/:address', auth.requireAuth, auth.requireAdmin, asy
const needResubmit = Object.values(data.chains).some(c => !c.guid || /Missing or unsupported chainid/i.test(c.status || ''));
if (needResubmit && etherscanApiKey) {
// Найти карточку DLE
const list = dleV2Service.getAllDLEs();
const list = unifiedDeploymentService.getAllDLEs();
const card = list.find(x => x?.dleAddress && x.dleAddress.toLowerCase() === address.toLowerCase());
if (card) {
const deployParams = {
@@ -520,11 +515,11 @@ router.post('/verify/refresh/:address', auth.requireAuth, auth.requireAdmin, asy
initialPartners: Array.isArray(card.initialPartners) ? card.initialPartners : [],
initialAmounts: Array.isArray(card.initialAmounts) ? card.initialAmounts : [],
supportedChainIds: Array.isArray(card.networks) ? card.networks.map(n => n.chainId).filter(Boolean) : (card.governanceSettings?.supportedChainIds || []),
currentChainId: card.governanceSettings?.currentChainId || (Array.isArray(card.networks) && card.networks[0]?.chainId) || 1
currentChainId: card.governanceSettings?.currentChainId || 1 // governance chain, не первая сеть
};
const deployResult = { success: true, data: { dleAddress: card.dleAddress, networks: card.networks || [] } };
try {
await dleV2Service.autoVerifyAcrossChains({ deployParams, deployResult, apiKey: etherscanApiKey });
await unifiedDeploymentService.autoVerifyAcrossChains({ deployParams, deployResult, apiKey: etherscanApiKey });
} catch (_) {}
}
}
@@ -552,12 +547,14 @@ router.post('/verify/refresh/:address', auth.requireAuth, auth.requireAdmin, asy
router.post('/verify/resubmit/:address', auth.requireAuth, auth.requireAdmin, async (req, res) => {
try {
const { address } = req.params;
const { etherscanApiKey } = req.body || {};
if (!etherscanApiKey && !process.env.ETHERSCAN_API_KEY) {
const ApiKeyManager = require('../utils/apiKeyManager');
const etherscanApiKey = ApiKeyManager.getEtherscanApiKey({}, req.body);
if (!etherscanApiKey) {
return res.status(400).json({ success: false, message: 'etherscanApiKey обязателен' });
}
// Найти карточку DLE по адресу
const list = dleV2Service.getAllDLEs();
const list = unifiedDeploymentService.getAllDLEs();
const card = list.find(x => x?.dleAddress && x.dleAddress.toLowerCase() === address.toLowerCase());
if (!card) return res.status(404).json({ success: false, message: 'Карточка DLE не найдена' });
@@ -575,13 +572,13 @@ router.post('/verify/resubmit/:address', auth.requireAuth, auth.requireAdmin, as
initialPartners: Array.isArray(card.initialPartners) ? card.initialPartners : [],
initialAmounts: Array.isArray(card.initialAmounts) ? card.initialAmounts : [],
supportedChainIds: Array.isArray(card.networks) ? card.networks.map(n => n.chainId).filter(Boolean) : (card.governanceSettings?.supportedChainIds || []),
currentChainId: card.governanceSettings?.currentChainId || (Array.isArray(card.networks) && card.networks[0]?.chainId) || 1
currentChainId: card.governanceSettings?.currentChainId || 1 // governance chain, не первая сеть
};
// Сформировать deployResult из карточки
const deployResult = { success: true, data: { dleAddress: card.dleAddress, networks: card.networks || [] } };
await dleV2Service.autoVerifyAcrossChains({ deployParams, deployResult, apiKey: etherscanApiKey });
await unifiedDeploymentService.autoVerifyAcrossChains({ deployParams, deployResult, apiKey: etherscanApiKey });
const updated = verificationStore.read(address);
return res.json({ success: true, data: updated });
} catch (e) {
@@ -597,7 +594,7 @@ router.post('/precheck', auth.requireAuth, auth.requireAdmin, async (req, res) =
if (!Array.isArray(supportedChainIds) || supportedChainIds.length === 0) {
return res.status(400).json({ success: false, message: 'Не переданы сети для проверки' });
}
const result = await dleV2Service.checkBalances(supportedChainIds, privateKey);
const result = await unifiedDeploymentService.checkBalances(supportedChainIds, privateKey);
return res.json({ success: true, data: result });
} catch (e) {
return res.status(500).json({ success: false, message: e.message });

View File

@@ -60,8 +60,10 @@ router.post('/deploy-module-from-db', async (req, res) => {
process.env.PRIVATE_KEY = params.privateKey || params.private_key;
}
if (params.etherscanApiKey || params.etherscan_api_key) {
process.env.ETHERSCAN_API_KEY = params.etherscanApiKey || params.etherscan_api_key;
const ApiKeyManager = require('../utils/apiKeyManager');
const etherscanKey = ApiKeyManager.getAndSetEtherscanApiKey(params);
if (etherscanKey) {
}
// Запускаем деплой модулей через скрипт