ваше сообщение коммита
This commit is contained in:
@@ -29,20 +29,38 @@ router.post('/read-dle-info', async (req, res) => {
|
||||
|
||||
console.log(`[Blockchain] Чтение данных DLE из блокчейна: ${dleAddress}`);
|
||||
|
||||
// Получаем RPC URL для Sepolia
|
||||
const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111);
|
||||
if (!rpcUrl) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
error: 'RPC URL для Sepolia не найден'
|
||||
});
|
||||
// Определяем корректную сеть для данного адреса (или используем chainId из запроса)
|
||||
let provider, rpcUrl, targetChainId = req.body.chainId;
|
||||
const candidateChainIds = [11155111, 17000, 421614, 84532];
|
||||
if (targetChainId) {
|
||||
rpcUrl = await rpcProviderService.getRpcUrlByChainId(Number(targetChainId));
|
||||
if (!rpcUrl) {
|
||||
return res.status(500).json({ success: false, error: `RPC URL для сети ${targetChainId} не найден` });
|
||||
}
|
||||
provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
const code = await provider.getCode(dleAddress);
|
||||
if (!code || code === '0x') {
|
||||
return res.status(400).json({ success: false, error: `По адресу ${dleAddress} нет контракта в сети ${targetChainId}` });
|
||||
}
|
||||
} else {
|
||||
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; }
|
||||
} catch (_) {}
|
||||
}
|
||||
if (!provider) {
|
||||
return res.status(400).json({ success: false, error: 'Не удалось найти сеть, где по адресу есть контракт' });
|
||||
}
|
||||
}
|
||||
|
||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
||||
|
||||
// ABI для чтения данных DLE
|
||||
const dleAbi = [
|
||||
"function getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, uint256 oktmo, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))",
|
||||
// Актуальная сигнатура без oktmo
|
||||
"function getDLEInfo() external view returns (tuple(string name, string symbol, string location, string coordinates, uint256 jurisdiction, string[] okvedCodes, uint256 kpp, uint256 creationTimestamp, bool isActive))",
|
||||
"function totalSupply() external view returns (uint256)",
|
||||
"function balanceOf(address account) external view returns (uint256)",
|
||||
"function quorumPercentage() external view returns (uint256)",
|
||||
@@ -81,7 +99,8 @@ router.post('/read-dle-info', async (req, res) => {
|
||||
location: dleInfo.location,
|
||||
coordinates: dleInfo.coordinates,
|
||||
jurisdiction: Number(dleInfo.jurisdiction),
|
||||
oktmo: Number(dleInfo.oktmo),
|
||||
// Поле oktmo удалено в актуальной версии контракта; сохраняем 0 для обратной совместимости
|
||||
oktmo: 0,
|
||||
okvedCodes: dleInfo.okvedCodes,
|
||||
kpp: Number(dleInfo.kpp),
|
||||
creationTimestamp: Number(dleInfo.creationTimestamp),
|
||||
@@ -90,6 +109,7 @@ router.post('/read-dle-info', async (req, res) => {
|
||||
deployerBalance: ethers.formatUnits(deployerBalance, 18),
|
||||
quorumPercentage: Number(quorumPercentage),
|
||||
currentChainId: Number(currentChainId),
|
||||
rpcUsed: rpcUrl,
|
||||
participantCount: participantCount
|
||||
};
|
||||
|
||||
@@ -153,6 +173,10 @@ router.post('/get-supported-chains', async (req, res) => {
|
||||
{ chainId: 43114, name: 'Avalanche', description: 'Avalanche C-Chain' },
|
||||
{ chainId: 250, name: 'Fantom', description: 'Fantom Opera' },
|
||||
{ chainId: 11155111, name: 'Sepolia', description: 'Ethereum Testnet Sepolia' },
|
||||
{ chainId: 17000, name: 'Holesky', description: 'Ethereum Testnet Holesky' },
|
||||
{ chainId: 80002, name: 'Polygon Amoy', description: 'Polygon Testnet Amoy' },
|
||||
{ chainId: 84532, name: 'Base Sepolia', description: 'Base Sepolia Testnet' },
|
||||
{ chainId: 421614, name: 'Arbitrum Sepolia', description: 'Arbitrum Sepolia Testnet' },
|
||||
{ chainId: 80001, name: 'Mumbai', description: 'Polygon Testnet Mumbai' },
|
||||
{ chainId: 97, name: 'BSC Testnet', description: 'Binance Smart Chain Testnet' },
|
||||
{ chainId: 421613, name: 'Arbitrum Goerli', description: 'Arbitrum Testnet Goerli' }
|
||||
|
||||
83
backend/routes/compile.js
Normal file
83
backend/routes/compile.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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 { spawn } = require('child_process');
|
||||
const path = require('path');
|
||||
const logger = require('../utils/logger');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
/**
|
||||
* @route POST /api/compile-contracts
|
||||
* @desc Компилировать смарт-контракты через Hardhat
|
||||
* @access Private (только для авторизованных пользователей с ролью admin)
|
||||
*/
|
||||
router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res) => {
|
||||
try {
|
||||
console.log('🔨 Запуск компиляции смарт-контрактов...');
|
||||
|
||||
const hardhatProcess = spawn('npx', ['hardhat', 'compile'], {
|
||||
cwd: path.join(__dirname, '..'),
|
||||
stdio: 'pipe'
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
hardhatProcess.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
console.log(`[COMPILE] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
hardhatProcess.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
console.error(`[COMPILE_ERR] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
hardhatProcess.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
console.log('✅ Компиляция завершена успешно');
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Смарт-контракты скомпилированы успешно',
|
||||
data: { stdout, stderr }
|
||||
});
|
||||
} else {
|
||||
console.error('❌ Ошибка компиляции:', stderr);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Ошибка компиляции смарт-контрактов',
|
||||
error: stderr
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
hardhatProcess.on('error', (error) => {
|
||||
console.error('❌ Ошибка запуска компиляции:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Ошибка запуска компиляции',
|
||||
error: error.message
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
logger.error('Ошибка компиляции контрактов:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message || 'Произошла ошибка при компиляции контрактов'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -357,14 +357,12 @@ router.post('/predict-addresses', auth.requireAuth, auth.requireAdmin, async (re
|
||||
}
|
||||
|
||||
// Используем служебные секреты для фабрики и SALT
|
||||
// Ожидаем, что на сервере настроены переменные окружения или конфиги на сеть
|
||||
// Factory больше не используется - адреса DLE теперь вычисляются через CREATE с выровненным nonce
|
||||
const result = {};
|
||||
for (const chainId of selectedNetworks) {
|
||||
const factory = process.env[`FACTORY_ADDRESS_${chainId}`] || process.env.FACTORY_ADDRESS;
|
||||
const saltHex = process.env[`CREATE2_SALT_${chainId}`] || process.env.CREATE2_SALT;
|
||||
const initCodeHash = process.env[`INIT_CODE_HASH_${chainId}`] || process.env.INIT_CODE_HASH;
|
||||
if (!factory || !saltHex || !initCodeHash) continue;
|
||||
result[chainId] = create2.computeCreate2Address(factory, saltHex, initCodeHash);
|
||||
// Адрес DLE будет одинаковым во всех сетях благодаря выравниванию nonce
|
||||
// Вычисляется в deploy-multichain.js во время деплоя
|
||||
result[chainId] = 'Вычисляется во время деплоя';
|
||||
}
|
||||
|
||||
return res.json({ success: true, data: result });
|
||||
@@ -513,4 +511,5 @@ router.post('/precheck', auth.requireAuth, auth.requireAdmin, async (req, res) =
|
||||
} catch (e) {
|
||||
return res.status(500).json({ success: false, message: e.message });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
30
backend/routes/ens.js
Normal file
30
backend/routes/ens.js
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* ENS utilities: resolve avatar URL for a given ENS name
|
||||
*/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { ethers } = require('ethers');
|
||||
|
||||
function getMainnetProvider() {
|
||||
const url = process.env.MAINNET_RPC_URL || process.env.ETH_MAINNET_RPC || 'https://ethereum.publicnode.com';
|
||||
return new ethers.JsonRpcProvider(url);
|
||||
}
|
||||
|
||||
// GET /api/ens/avatar?name=vc-hb3-accelerator.eth
|
||||
router.get('/avatar', async (req, res) => {
|
||||
try {
|
||||
const name = String(req.query.name || '').trim();
|
||||
if (!name || !name.endsWith('.eth')) {
|
||||
return res.status(400).json({ success: false, message: 'ENS name is required (e.g., example.eth)' });
|
||||
}
|
||||
const provider = getMainnetProvider();
|
||||
const url = await provider.getAvatar(name);
|
||||
return res.json({ success: true, data: { url: url || null } });
|
||||
} catch (e) {
|
||||
return res.status(500).json({ success: false, message: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
51
backend/routes/uploads.js
Normal file
51
backend/routes/uploads.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Загрузка файлов (логотипы) через Multer
|
||||
*/
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const multer = require('multer');
|
||||
const auth = require('../middleware/auth');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Хранилище на диске: uploads/logos
|
||||
const storage = multer.diskStorage({
|
||||
destination: function (req, file, cb) {
|
||||
const dir = path.join(__dirname, '..', 'uploads', 'logos');
|
||||
try { fs.mkdirSync(dir, { recursive: true }); } catch (_) {}
|
||||
cb(null, dir);
|
||||
},
|
||||
filename: function (req, file, cb) {
|
||||
const ext = (file.originalname || '').split('.').pop();
|
||||
const safeExt = ext && ext.length <= 10 ? ext : 'png';
|
||||
const name = `logo_${Date.now()}_${Math.random().toString(36).slice(2, 8)}.${safeExt}`;
|
||||
cb(null, name);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage,
|
||||
limits: { fileSize: 5 * 1024 * 1024 },
|
||||
fileFilter: (req, file, cb) => {
|
||||
const ok = /(png|jpg|jpeg|gif|webp)$/i.test(file.originalname || '') && /^image\//i.test(file.mimetype || '');
|
||||
if (!ok) return cb(new Error('Only image files are allowed'));
|
||||
cb(null, true);
|
||||
}
|
||||
});
|
||||
|
||||
// POST /api/uploads/logo (form field: logo)
|
||||
router.post('/logo', auth.requireAuth, auth.requireAdmin, upload.single('logo'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) return res.status(400).json({ success: false, message: 'Файл не получен' });
|
||||
const rel = path.posix.join('uploads', 'logos', path.basename(req.file.filename));
|
||||
const urlPath = `/uploads/logos/${path.basename(req.file.filename)}`;
|
||||
return res.json({ success: true, data: { path: rel, url: urlPath } });
|
||||
} catch (e) {
|
||||
return res.status(500).json({ success: false, message: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user