diff --git a/.cursor/rules/context7-mcp.mdc b/.cursor/rules/context7-mcp.mdc new file mode 100644 index 0000000..fe60d5b --- /dev/null +++ b/.cursor/rules/context7-mcp.mdc @@ -0,0 +1,19 @@ +--- +description: +globs: +alwaysApply: true +--- +Description: +Перед написанием или предложением любого кода, связанного с использованием внешних библиотек или фреймворков, **обязательно** обратись к `context7-mcp` для получения актуальной документации. Это поможет избежать устаревших API, вымышленных функций и обеспечит использование best practices. + +Задачи, для которых это правило полезно: +- Генерация кода с использованием любых npm-пакетов, Python-библиотек, Go-модулей и т.д. +- Ответы на вопросы о конкретных функциях или методах библиотек. +- Предоставление примеров использования API. +- Обновление существующего кода, использующего внешние зависимости. + +Инструменты для использования: +1. `resolve-library-id`: для определения точного ID библиотеки. +2. `get-library-docs`: для получения актуальной документации по найденному ID. + +Убедись, что предлагаемый код соответствует последней версии документации, полученной через context7-mcp. diff --git a/backend/config/auth-tokens.json b/backend/config/auth-tokens.json deleted file mode 100644 index 257fed3..0000000 --- a/backend/config/auth-tokens.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "name": "Ethereum Token", - "address": "0xd95a45fc46a7300e6022885afec3d618d7d3f27c", - "network": "eth", - "minBalance": "1.0" - }, - { - "name": "BSC Token", - "address": "0x4B294265720B09ca39BFBA18c7E368413c0f68eB", - "network": "bsc", - "minBalance": "10.0" - }, - { - "name": "Arbitrum Token", - "address": "0xdce769b847a0a697239777d0b1c7dd33b6012ba0", - "network": "arbitrum", - "minBalance": "0.5" - }, - { - "name": "Custom Token", - "address": "0x351f59de4fedbdf7601f5592b93db3b9330c1c1d", - "network": "eth", - "minBalance": "5.0" - }, - { - "name": "test2", - "address": "0xef49261169B454f191678D2aFC5E91Ad2e85dfD8", - "minBalance": "1.0", - "network": "sepolia" - } -] \ No newline at end of file diff --git a/backend/config/rpc-settings.json b/backend/config/rpc-settings.json deleted file mode 100644 index 9aaa493..0000000 --- a/backend/config/rpc-settings.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "networkId": "bsc", - "rpcUrl": "https://bsc-dataseed1.binance.org", - "chainId": 56 - }, - { - "networkId": "arbitrum", - "rpcUrl": "https://arb1.arbitrum.io/rpc", - "chainId": 42161 - }, - { - "networkId": "polygon", - "rpcUrl": "https://polygon-rpc.com", - "chainId": 137 - }, - { - "networkId": "sepolia", - "rpcUrl": "https://eth-sepolia.nodereal.io/v1/56dec8028bae4f26b76099a42dae2b52", - "chainId": 11155111 - }, - { - "networkId": "ethereum", - "rpcUrl": "https://eth-sepolia.nodereal.io/v1/56dec8028bae4f26b76099a42dae2b52", - "chainId": 1 - } -] \ No newline at end of file diff --git a/backend/db/migrations/019_create_rpc_providers_and_auth_tokens.sql b/backend/db/migrations/019_create_rpc_providers_and_auth_tokens.sql new file mode 100644 index 0000000..e21c396 --- /dev/null +++ b/backend/db/migrations/019_create_rpc_providers_and_auth_tokens.sql @@ -0,0 +1,23 @@ +-- Миграция: создание таблиц для RPC провайдеров и токенов аутентификации + +-- Таблица RPC провайдеров +CREATE TABLE IF NOT EXISTS rpc_providers ( + id SERIAL PRIMARY KEY, + network_id VARCHAR(64) NOT NULL UNIQUE, + rpc_url TEXT NOT NULL, + chain_id INTEGER, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP +); + +-- Таблица токенов аутентификации +CREATE TABLE IF NOT EXISTS auth_tokens ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + address VARCHAR(64) NOT NULL, + network VARCHAR(64) NOT NULL, + min_balance NUMERIC(36, 18) NOT NULL, + created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT auth_tokens_address_network_unique UNIQUE (address, network) +); \ No newline at end of file diff --git a/backend/hardhat.config.js b/backend/hardhat.config.js index b20ed27..61d3cde 100644 --- a/backend/hardhat.config.js +++ b/backend/hardhat.config.js @@ -14,7 +14,7 @@ module.exports = { }, networks: { sepolia: { - url: process.env.ETHEREUM_NETWORK_URL, + url: process.env.RPC_URL_ETH, accounts: [process.env.PRIVATE_KEY], }, }, diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js index 950adef..28310b2 100644 --- a/backend/middleware/auth.js +++ b/backend/middleware/auth.js @@ -104,7 +104,7 @@ async function requireAdmin(req, res, next) { // Проверка через кошелек if (req.session.address) { - const isAdmin = await authService.checkAdminToken(req.session.address); + const isAdmin = await authService.checkAdminTokens(req.session.address); if (isAdmin) { // Обновляем сессию req.session.isAdmin = true; diff --git a/backend/routes/settings.js b/backend/routes/settings.js index d190e40..520733f 100644 --- a/backend/routes/settings.js +++ b/backend/routes/settings.js @@ -2,52 +2,17 @@ const express = require('express'); const router = express.Router(); const { requireAdmin } = require('../middleware/auth'); const logger = require('../utils/logger'); -const fs = require('fs'); -const path = require('path'); const { ethers } = require('ethers'); +const rpcProviderService = require('../services/rpcProviderService'); +const authTokenService = require('../services/authTokenService'); // Логируем версию ethers для отладки logger.info(`Ethers version: ${ethers.version || 'unknown'}`); -// Путь к файлу с настройками -const RPC_CONFIG_PATH = path.join(__dirname, '../config/rpc-settings.json'); -const AUTH_TOKENS_PATH = path.join(__dirname, '../config/auth-tokens.json'); - -// Вспомогательная функция для чтения настроек из файла -const readSettingsFile = (filePath, defaultValue = []) => { - try { - if (fs.existsSync(filePath)) { - const data = fs.readFileSync(filePath, 'utf8'); - return JSON.parse(data); - } - return defaultValue; - } catch (error) { - logger.error(`Ошибка при чтении файла настроек ${filePath}:`, error); - return defaultValue; - } -}; - -// Вспомогательная функция для записи настроек в файл -const writeSettingsFile = async (filePath, data) => { - try { - // Создаем директорию, если не существует - const dirname = path.dirname(filePath); - if (!fs.existsSync(dirname)) { - fs.mkdirSync(dirname, { recursive: true }); - } - - await fs.promises.writeFile(filePath, JSON.stringify(data, null, 2), 'utf8'); - return true; - } catch (error) { - logger.error(`Ошибка при записи файла настроек ${filePath}:`, error); - return false; - } -}; - // Получение RPC настроек router.get('/rpc', requireAdmin, async (req, res) => { try { - const rpcConfigs = readSettingsFile(RPC_CONFIG_PATH); + const rpcConfigs = await rpcProviderService.getAllRpcProviders(); res.json({ success: true, data: rpcConfigs }); } catch (error) { logger.error('Ошибка при получении RPC настроек:', error); @@ -55,32 +20,47 @@ router.get('/rpc', requireAdmin, async (req, res) => { } }); -// Сохранение RPC настроек +// Добавление/обновление одного или нескольких RPC router.post('/rpc', requireAdmin, async (req, res) => { try { - const { rpcConfigs } = req.body; - - if (!Array.isArray(rpcConfigs)) { - return res.status(400).json({ success: false, error: 'Неверный формат данных' }); + // Если пришёл массив rpcConfigs — bulk-режим + if (Array.isArray(req.body.rpcConfigs)) { + const rpcConfigs = req.body.rpcConfigs; + if (!rpcConfigs.length) { + return res.status(400).json({ success: false, error: 'rpcConfigs не может быть пустым массивом' }); + } + await rpcProviderService.saveAllRpcProviders(rpcConfigs); + return res.json({ success: true, message: 'RPC провайдеры успешно сохранены (bulk)' }); } - - const success = await writeSettingsFile(RPC_CONFIG_PATH, rpcConfigs); - - if (success) { - res.json({ success: true, message: 'RPC настройки успешно сохранены' }); - } else { - res.status(500).json({ success: false, error: 'Ошибка при сохранении RPC настроек' }); + // Иначе — одиночный режим (старый) + const { networkId, rpcUrl, chainId } = req.body; + if (!networkId || !rpcUrl) { + return res.status(400).json({ success: false, error: 'networkId и rpcUrl обязательны' }); } + await rpcProviderService.upsertRpcProvider({ networkId, rpcUrl, chainId }); + res.json({ success: true, message: 'RPC провайдер сохранён' }); } catch (error) { - logger.error('Ошибка при сохранении RPC настроек:', error); - res.status(500).json({ success: false, error: 'Ошибка сервера при сохранении настроек RPC' }); + logger.error('Ошибка при сохранении RPC:', error); + res.status(500).json({ success: false, error: 'Ошибка сервера при сохранении RPC' }); + } +}); + +// Удаление одного RPC +router.delete('/rpc/:networkId', requireAdmin, async (req, res) => { + try { + const { networkId } = req.params; + await rpcProviderService.deleteRpcProvider(networkId); + res.json({ success: true, message: 'RPC провайдер удалён' }); + } catch (error) { + logger.error('Ошибка при удалении RPC:', error); + res.status(500).json({ success: false, error: 'Ошибка сервера при удалении RPC' }); } }); // Получение токенов для аутентификации router.get('/auth-tokens', requireAdmin, async (req, res) => { try { - const authTokens = readSettingsFile(AUTH_TOKENS_PATH); + const authTokens = await authTokenService.getAllAuthTokens(); res.json({ success: true, data: authTokens }); } catch (error) { logger.error('Ошибка при получении токенов аутентификации:', error); @@ -92,24 +72,44 @@ router.get('/auth-tokens', requireAdmin, async (req, res) => { router.post('/auth-tokens', requireAdmin, async (req, res) => { try { const { authTokens } = req.body; - if (!Array.isArray(authTokens)) { return res.status(400).json({ success: false, error: 'Неверный формат данных' }); } - - const success = await writeSettingsFile(AUTH_TOKENS_PATH, authTokens); - - if (success) { - res.json({ success: true, message: 'Токены аутентификации успешно сохранены' }); - } else { - res.status(500).json({ success: false, error: 'Ошибка при сохранении токенов аутентификации' }); - } + await authTokenService.saveAllAuthTokens(authTokens); + res.json({ success: true, message: 'Токены аутентификации успешно сохранены' }); } catch (error) { logger.error('Ошибка при сохранении токенов аутентификации:', error); res.status(500).json({ success: false, error: 'Ошибка сервера при сохранении токенов аутентификации' }); } }); +// Добавление/обновление одного токена +router.post('/auth-token', requireAdmin, async (req, res) => { + try { + const { name, address, network, minBalance } = req.body; + if (!name || !address || !network) { + return res.status(400).json({ success: false, error: 'name, address и network обязательны' }); + } + await authTokenService.upsertAuthToken({ name, address, network, minBalance }); + res.json({ success: true, message: 'Токен аутентификации сохранён' }); + } catch (error) { + logger.error('Ошибка при сохранении токена аутентификации:', error); + res.status(500).json({ success: false, error: 'Ошибка сервера при сохранении токена' }); + } +}); + +// Удаление одного токена +router.delete('/auth-token/:address/:network', requireAdmin, async (req, res) => { + try { + const { address, network } = req.params; + await authTokenService.deleteAuthToken(address, network); + res.json({ success: true, message: 'Токен аутентификации удалён' }); + } catch (error) { + logger.error('Ошибка при удалении токена аутентификации:', error); + res.status(500).json({ success: false, error: 'Ошибка сервера при удалении токена' }); + } +}); + // Тестирование RPC соединения router.post('/rpc-test', requireAdmin, async (req, res) => { try { @@ -123,7 +123,12 @@ router.post('/rpc-test', requireAdmin, async (req, res) => { try { // Пробуем создать провайдера и получить номер последнего блока (обновлено для ethers v6) - const provider = new ethers.JsonRpcProvider(rpcUrl); + let provider; + if (rpcUrl.startsWith('ws://') || rpcUrl.startsWith('wss://')) { + provider = new ethers.WebSocketProvider(rpcUrl); + } else { + provider = new ethers.JsonRpcProvider(rpcUrl); + } // Устанавливаем таймаут для соединения const timeoutPromise = new Promise((_, reject) => diff --git a/backend/routes/tokens.js b/backend/routes/tokens.js index 4c983df..cf33025 100644 --- a/backend/routes/tokens.js +++ b/backend/routes/tokens.js @@ -1,29 +1,21 @@ const express = require('express'); const router = express.Router(); -const { requireAuth } = require('../middleware/auth'); -const authService = require('../services/auth-service'); const logger = require('../utils/logger'); +const authService = require('../services/auth-service'); -// Получение балансов токенов -router.get('/balances', requireAuth, async (req, res) => { +// Получение балансов токенов пользователя по токенам из базы +router.get('/balances', async (req, res) => { try { - const { address } = req.session; - + const address = req.query.address; if (!address) { - return res.status(400).json({ - error: 'No wallet address in session', - }); + return res.status(400).json({ success: false, error: 'Не указан адрес кошелька' }); } - logger.info(`Fetching token balances for address: ${address}`); - const balances = await authService.getTokenBalances(address); - - res.json(balances); + const balances = await authService.getUserTokenBalances(address); + res.json({ success: true, data: balances }); } catch (error) { logger.error('Error fetching token balances:', error); - res.status(500).json({ - error: 'Failed to fetch token balances', - }); + res.status(500).json({ success: false, error: 'Failed to fetch token balances' }); } }); diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index e966683..0a18c60 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -5,26 +5,13 @@ const crypto = require('crypto'); const { processMessage } = require('./ai-assistant'); // Используем AI Assistant const verificationService = require('./verification-service'); // Используем сервис верификации const identityService = require('./identity-service'); // <-- ДОБАВЛЕН ИМПОРТ - -const ADMIN_CONTRACTS = [ - { address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c', network: 'ethereum' }, - { address: '0x4B294265720B09ca39BFBA18c7E368413c0f68eB', network: 'bsc' }, - { address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0', network: 'arbitrum' }, - { address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d', network: 'polygon' }, -]; +const authTokenService = require('./authTokenService'); +const rpcProviderService = require('./rpcProviderService'); const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; class AuthService { - constructor() { - // Используем существующие переменные окружения с префиксом RPC_URL_ - this.providers = { - ethereum: new ethers.JsonRpcProvider(process.env.RPC_URL_ETH), // Используем RPC_URL_ETH для ethereum - polygon: new ethers.JsonRpcProvider(process.env.RPC_URL_POLYGON), - bsc: new ethers.JsonRpcProvider(process.env.RPC_URL_BSC), - arbitrum: new ethers.JsonRpcProvider(process.env.RPC_URL_ARBITRUM), - }; - } + constructor() {} // Проверка подписи async verifySignature(message, signature, address) { @@ -127,100 +114,86 @@ class AuthService { */ async checkAdminRole(address) { if (!address) return false; - logger.info(`Checking admin role for address: ${address}`); let foundTokens = false; let errorCount = 0; const balances = {}; - const totalNetworks = ADMIN_CONTRACTS.length; - - // Создаем массив промисов для параллельной проверки балансов - const checkPromises = ADMIN_CONTRACTS.map(async (contract) => { + // Получаем токены и RPC из базы + const tokens = await authTokenService.getAllAuthTokens(); + const rpcProviders = await rpcProviderService.getAllRpcProviders(); + const rpcMap = {}; + for (const rpc of rpcProviders) { + rpcMap[rpc.network_id] = rpc.rpc_url; + } + const checkPromises = tokens.map(async (token) => { try { - const provider = this.providers[contract.network]; - if (!provider) { - logger.error(`No provider available for network ${contract.network}`); - balances[contract.network] = 'Error: No provider'; + const rpcUrl = rpcMap[token.network]; + if (!rpcUrl) { + logger.error(`No RPC URL for network ${token.network}`); + balances[token.network] = 'Error: No RPC URL'; errorCount++; return null; } - - // Проверяем доступность провайдера + const provider = new ethers.JsonRpcProvider(rpcUrl); + // Проверяем доступность сети с таймаутом try { - // Проверка доступности сети с таймаутом const networkCheckPromise = provider.getNetwork(); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Network check timeout')), 3000) ); - await Promise.race([networkCheckPromise, timeoutPromise]); } catch (networkError) { - logger.error( - `Provider for ${contract.network} is not available: ${networkError.message}` - ); - balances[contract.network] = 'Error: Network unavailable'; + logger.error(`Provider for ${token.network} is not available: ${networkError.message}`); + balances[token.network] = 'Error: Network unavailable'; errorCount++; return null; } - - const tokenContract = new ethers.Contract(contract.address, ERC20_ABI, provider); - - // Создаем промис с таймаутом + const tokenContract = new ethers.Contract(token.address, ERC20_ABI, provider); const balancePromise = tokenContract.balanceOf(address); const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 3000) ); - - // Ждем первый выполненный промис const balance = await Promise.race([balancePromise, timeoutPromise]); const formattedBalance = ethers.formatUnits(balance, 18); - balances[contract.network] = formattedBalance; - - logger.info(`Token balance on ${contract.network}:`, { + balances[token.network] = formattedBalance; + logger.info(`Token balance on ${token.network}:`, { address, - contract: contract.address, + contract: token.address, balance: formattedBalance, - hasTokens: balance > 0, + minBalance: token.min_balance, + hasTokens: parseFloat(formattedBalance) >= parseFloat(token.min_balance), }); - - if (parseFloat(formattedBalance) > 0) { - logger.info(`Found admin tokens on ${contract.network}`); + if (parseFloat(formattedBalance) >= parseFloat(token.min_balance)) { + logger.info(`Found admin tokens on ${token.network}`); foundTokens = true; } - - return { network: contract.network, balance: formattedBalance }; + return { network: token.network, balance: formattedBalance }; } catch (error) { - logger.error(`Error checking balance in ${contract.network}:`, { + logger.error(`Error checking balance in ${token.network}:`, { address, - contract: contract.address, + contract: token.address, error: error.message || 'Unknown error', }); - balances[contract.network] = 'Error'; + balances[token.network] = 'Error'; errorCount++; return null; } }); - - // Ждем выполнения всех проверок await Promise.all(checkPromises); - - // Если все запросы завершились с ошибкой, считаем, что проверка не удалась - if (errorCount === totalNetworks) { + if (errorCount === tokens.length) { logger.error(`All network checks for ${address} failed. Cannot verify admin status.`); return false; } - if (foundTokens) { logger.info(`Admin role summary for ${address}:`, { networks: Object.keys(balances).filter( - (net) => balances[net] > 0 && balances[net] !== 'Error' + (net) => parseFloat(balances[net]) > 0 && balances[net] !== 'Error' ), balances, }); logger.info(`Admin role granted for ${address}`); return true; } - logger.info(`Admin role denied - no tokens found for ${address}`); return false; } @@ -238,6 +211,7 @@ class AuthService { bsc: '0', arbitrum: '0', polygon: '0', + sepolia: '0', }; } @@ -889,6 +863,46 @@ class AuthService { throw new Error('Ошибка обработки верификации Email'); } } + + /** + * Получение балансов токенов пользователя только по токенам из базы + * @param {string} address - адрес кошелька + * @returns {Promise} - массив объектов с балансами + */ + async getUserTokenBalances(address) { + if (!address) return []; + const tokens = await authTokenService.getAllAuthTokens(); + const rpcProviders = await rpcProviderService.getAllRpcProviders(); + const rpcMap = {}; + for (const rpc of rpcProviders) { + rpcMap[rpc.network_id] = rpc.rpc_url; + } + const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; + const results = []; + for (const token of tokens) { + const rpcUrl = rpcMap[token.network]; + if (!rpcUrl) continue; + const provider = new ethers.JsonRpcProvider(rpcUrl); + const tokenContract = new ethers.Contract(token.address, ERC20_ABI, provider); + let balance = '0'; + try { + const rawBalance = await tokenContract.balanceOf(address); + balance = ethers.formatUnits(rawBalance, 18); + if (!balance || isNaN(Number(balance))) balance = '0'; + } catch (e) { + logger.error(`[getUserTokenBalances] Ошибка получения баланса для ${token.name} (${token.address}) в сети ${token.network}:`, e); + balance = '0'; + } + results.push({ + network: token.network, + tokenAddress: token.address, + tokenName: token.name, + symbol: token.symbol || '', + balance, + }); + } + return results; + } } // Создаем и экспортируем единственный экземпляр diff --git a/backend/services/authTokenService.js b/backend/services/authTokenService.js new file mode 100644 index 0000000..d559353 --- /dev/null +++ b/backend/services/authTokenService.js @@ -0,0 +1,32 @@ +const db = require('../db'); + +async function getAllAuthTokens() { + const { rows } = await db.query('SELECT * FROM auth_tokens ORDER BY id'); + return rows; +} + +async function saveAllAuthTokens(authTokens) { + await db.query('DELETE FROM auth_tokens'); + for (const token of authTokens) { + await db.query( + 'INSERT INTO auth_tokens (name, address, network, min_balance) VALUES ($1, $2, $3, $4)', + [token.name, token.address, token.network, token.minBalance] + ); + } +} + +async function upsertAuthToken(token) { + const minBalance = token.minBalance == null ? 0 : Number(token.minBalance); + await db.query( + `INSERT INTO auth_tokens (name, address, network, min_balance) + VALUES ($1, $2, $3, $4) + ON CONFLICT (address, network) DO UPDATE SET name=EXCLUDED.name, min_balance=EXCLUDED.min_balance`, + [token.name, token.address, token.network, minBalance] + ); +} + +async function deleteAuthToken(address, network) { + await db.query('DELETE FROM auth_tokens WHERE address = $1 AND network = $2', [address, network]); +} + +module.exports = { getAllAuthTokens, saveAllAuthTokens, upsertAuthToken, deleteAuthToken }; \ No newline at end of file diff --git a/backend/services/rpcProviderService.js b/backend/services/rpcProviderService.js new file mode 100644 index 0000000..3ab3974 --- /dev/null +++ b/backend/services/rpcProviderService.js @@ -0,0 +1,31 @@ +const db = require('../db'); + +async function getAllRpcProviders() { + const { rows } = await db.query('SELECT * FROM rpc_providers ORDER BY id'); + return rows; +} + +async function saveAllRpcProviders(rpcConfigs) { + await db.query('DELETE FROM rpc_providers'); + for (const cfg of rpcConfigs) { + await db.query( + 'INSERT INTO rpc_providers (network_id, rpc_url, chain_id) VALUES ($1, $2, $3)', + [cfg.networkId, cfg.rpcUrl, cfg.chainId || null] + ); + } +} + +async function upsertRpcProvider(cfg) { + await db.query( + `INSERT INTO rpc_providers (network_id, rpc_url, chain_id) + VALUES ($1, $2, $3) + ON CONFLICT (network_id) DO UPDATE SET rpc_url=EXCLUDED.rpc_url, chain_id=EXCLUDED.chain_id`, + [cfg.networkId, cfg.rpcUrl, cfg.chainId || null] + ); +} + +async function deleteRpcProvider(networkId) { + await db.query('DELETE FROM rpc_providers WHERE network_id = $1', [networkId]); +} + +module.exports = { getAllRpcProviders, saveAllRpcProviders, upsertRpcProvider, deleteRpcProvider }; \ No newline at end of file diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b9bddde..07e0786 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -18,7 +18,7 @@ diff --git a/frontend/src/components/Sidebar.vue b/frontend/src/components/Sidebar.vue index c07d0c1..e084703 100644 --- a/frontend/src/components/Sidebar.vue +++ b/frontend/src/components/Sidebar.vue @@ -62,29 +62,16 @@
Загрузка балансов...
-
+
Баланс не доступен
-
- ETH: - {{ Number(tokenBalances.eth).toLocaleString() }} - {{ TOKEN_CONTRACTS.eth.symbol }} +
-
- BSC: - {{ Number(tokenBalances.bsc).toLocaleString() }} - {{ TOKEN_CONTRACTS.bsc.symbol }} -
-
- ARB: - {{ Number(tokenBalances.arbitrum).toLocaleString() }} - {{ TOKEN_CONTRACTS.arbitrum.symbol }} -
-
- POL: - {{ Number(tokenBalances.polygon).toLocaleString() }} - {{ TOKEN_CONTRACTS.polygon.symbol }} +
+ {{ token.tokenName }} + {{ token.network }} + {{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}
@@ -96,7 +83,6 @@ + + \ No newline at end of file diff --git a/frontend/src/views/settings/BlockchainSettingsView.vue b/frontend/src/views/settings/BlockchainSettingsView.vue index 5ca5517..d3ff7a0 100644 --- a/frontend/src/views/settings/BlockchainSettingsView.vue +++ b/frontend/src/views/settings/BlockchainSettingsView.vue @@ -235,14 +235,6 @@
- -
- -
-

Сеть для деплоя

@@ -918,20 +910,24 @@ const toggleShowDeployerKey = () => { const loadRpcSettings = async () => { try { const response = await axios.get('/api/settings/rpc'); + console.log('Ответ сервера на /api/settings/rpc:', response.data); if (response.data && response.data.success) { - securitySettings.rpcConfigs = response.data.data || []; + securitySettings.rpcConfigs = (response.data.data || []).map(rpc => ({ + networkId: rpc.network_id, + rpcUrl: rpc.rpc_url, + chainId: rpc.chain_id + })); console.log('[BlockchainSettingsView] RPC конфигурации успешно загружены:', securitySettings.rpcConfigs); } } catch (error) { console.error('[BlockchainSettingsView] Ошибка при загрузке RPC конфигураций:', error); - // Если нужно, установить дефолтные RPC - // setDefaultRpcConfigs(); } }; // Функция сохранения настроек RPC на сервер const saveRpcSettings = async () => { try { + console.log('Отправляемые RPC:', securitySettings.rpcConfigs); const response = await axios.post('/api/settings/rpc', { rpcConfigs: JSON.parse(JSON.stringify(securitySettings.rpcConfigs)) }); diff --git a/frontend/src/views/settings/InterfaceSettingsView.vue b/frontend/src/views/settings/InterfaceSettingsView.vue index 69d8284..d918cc7 100644 --- a/frontend/src/views/settings/InterfaceSettingsView.vue +++ b/frontend/src/views/settings/InterfaceSettingsView.vue @@ -15,7 +15,6 @@
-
diff --git a/frontend/src/views/settings/RpcProvidersSettings.vue b/frontend/src/views/settings/RpcProvidersSettings.vue new file mode 100644 index 0000000..1dec4a8 --- /dev/null +++ b/frontend/src/views/settings/RpcProvidersSettings.vue @@ -0,0 +1,138 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/settings/SecuritySettingsView.vue b/frontend/src/views/settings/SecuritySettingsView.vue index c4ada82..a464df5 100644 --- a/frontend/src/views/settings/SecuritySettingsView.vue +++ b/frontend/src/views/settings/SecuritySettingsView.vue @@ -38,134 +38,27 @@ - -
-

Настройки RPC Провайдеров

-

Эти настройки сохраняются в .env файле на бэкенде.

- - -
-

Добавленные RPC конфигурации:

-
-
-
ID Сети: {{ rpc.networkId }}
-
URL: {{ rpc.rpcUrl }}
-
Chain ID: {{ rpc.chainId }}
-
-
- - -
-
-
-

Нет добавленных RPC конфигураций.

- - -
-

Добавить новую RPC конфигурацию:

-
- - -
- - - - - - Chain ID - уникальный идентификатор блокчейн-сети (целое число) -
- ID сети должен совпадать со значением в выпадающем списке сетей при создании DLE -
-
- - - - - Предложение: {{ defaultRpcUrlSuggestion }} - - -
- -
-
- - -
-

Настройки Аутентификации

-

Эти настройки сохраняются в .env файле на бэкенде.

- - -
-

Токены для проверки при авторизации:

-
-
-
Название: {{ token.name }}
-
Адрес: {{ token.address }}
-
Сеть: {{ token.network }}
-
Мин. баланс: {{ token.minBalance }}
-
- -
-
-

Нет добавленных токенов для проверки при авторизации.

- - -
-

Добавить новый токен для проверки при авторизации:

-
- - -
-
- - -
-
- - -
-
- - - Минимальный баланс токена для успешной авторизации -
- -
-
- -
- -
+ +