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

This commit is contained in:
2025-08-29 18:37:57 +03:00
parent 8e50c6c4d8
commit 4e4cb611a1
53 changed files with 4380 additions and 5902 deletions

View File

@@ -30,11 +30,11 @@ map $http_user_agent $bad_bot {
~*drupalscan 1;
~*magento 1;
~*wordpress 1;
~*"Chrome/[1-7][0-9]\." 1;
~*"Firefox/[1-6][0-9]\." 1;
~*"Safari/[1-9]\." 1;
~*"MSIE [1-9]\." 1;
~*"Trident/[1-6]\." 1;
~*Chrome/[1-7][0-9]\. 1;
~*Firefox/[1-6][0-9]\. 1;
~*Safari/[1-9]\. 1;
~*MSIE\ [1-9]\. 1;
~*Trident/[1-6]\. 1;
}
# Блокировка подозрительных IP (добавляем атакующий IP)

View File

@@ -56,10 +56,13 @@ export default function useBlockchainNetworks() {
{ value: 'holesky', label: 'Holesky (Ethereum testnet)', chainId: 17000 },
{ value: 'bsc-testnet', label: 'BSC Testnet', chainId: 97 },
{ value: 'mumbai', label: 'Mumbai (Polygon testnet)', chainId: 80001 },
{ value: 'polygon-amoy', label: 'Polygon Amoy (testnet)', chainId: 80002 },
{ value: 'arbitrum-goerli', label: 'Arbitrum Goerli', chainId: 421613 },
{ value: 'arbitrum-sepolia', label: 'Arbitrum Sepolia', chainId: 421614 },
{ value: 'optimism-goerli', label: 'Optimism Goerli', chainId: 420 },
{ value: 'avalanche-fuji', label: 'Avalanche Fuji', chainId: 43113 },
{ value: 'fantom-testnet', label: 'Fantom Testnet', chainId: 4002 }
{ value: 'fantom-testnet', label: 'Fantom Testnet', chainId: 4002 },
{ value: 'base-sepolia', label: 'Base Sepolia Testnet', chainId: 84532 }
]
},
{

View File

@@ -63,7 +63,28 @@
</div>
<div class="dle-details">
<div class="detail-item">
<div class="detail-item" v-if="dle.deployedMultichain">
<strong>🌐 Мультичейн деплой:</strong>
<span class="multichain-badge">{{ dle.totalNetworks }}/{{ dle.supportedChainIds?.length || dle.totalNetworks }} сетей</span>
</div>
<div class="detail-item" v-if="dle.networks && dle.networks.length">
<strong>Адреса по сетям:</strong>
<ul class="networks-list">
<li v-for="net in dle.networks" :key="net.chainId" class="network-item">
<span class="chain-name">{{ getChainName(net.chainId) }}:</span>
<a
:href="getExplorerUrl(net.chainId, net.dleAddress)"
target="_blank"
class="address-link"
@click.stop
>
{{ shortenAddress(net.dleAddress) }}
<i class="fas fa-external-link-alt"></i>
</a>
</li>
</ul>
</div>
<div class="detail-item" v-else>
<strong>Адрес контракта:</strong>
<a
:href="`https://sepolia.etherscan.io/address/${dle.dleAddress}`"
@@ -75,15 +96,6 @@
<i class="fas fa-external-link-alt"></i>
</a>
</div>
<div class="detail-item" v-if="dle.networks && dle.networks.length">
<strong>Адреса по сетям:</strong>
<ul class="networks-list">
<li v-for="net in dle.networks" :key="net.chainId">
Chain {{ net.chainId }}:
<span class="address">{{ shortenAddress(net.address) }}</span>
</li>
</ul>
</div>
<div class="detail-item">
<strong>Местоположение:</strong> {{ dle.location }}
</div>
@@ -347,6 +359,35 @@ function shortenAddress(address) {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
function getChainName(chainId) {
const chainNames = {
1: 'Ethereum',
11155111: 'Sepolia',
17000: 'Holesky',
421614: 'Arbitrum Sepolia',
84532: 'Base Sepolia',
137: 'Polygon',
56: 'BSC',
42161: 'Arbitrum'
};
return chainNames[chainId] || `Chain ${chainId}`;
}
function getExplorerUrl(chainId, address) {
const explorers = {
1: 'https://etherscan.io',
11155111: 'https://sepolia.etherscan.io',
17000: 'https://holesky.etherscan.io',
421614: 'https://sepolia.arbiscan.io',
84532: 'https://sepolia.basescan.org',
137: 'https://polygonscan.com',
56: 'https://bscscan.com',
42161: 'https://arbiscan.io'
};
const baseUrl = explorers[chainId] || 'https://etherscan.io';
return `${baseUrl}/address/${address}`;
}
function openDleOnEtherscan(address) {
window.open(`https://sepolia.etherscan.io/address/${address}`, '_blank');
}
@@ -724,6 +765,40 @@ onBeforeUnmount(() => {
opacity: 0.7;
}
.multichain-badge {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.875rem;
font-weight: 600;
display: inline-block;
}
.networks-list {
list-style: none;
padding: 0;
margin: 0.5rem 0;
}
.network-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.25rem 0;
border-bottom: 1px solid #f0f0f0;
}
.network-item:last-child {
border-bottom: none;
}
.chain-name {
font-weight: 600;
color: #333;
min-width: 120px;
}
.status {
padding: 0.25rem 0.75rem;
border-radius: 12px;

View File

@@ -325,6 +325,41 @@
<small class="form-help">3-10 символов для токена управления (Governance Token)</small>
</div>
<!-- Логотип токена -->
<div class="form-group">
<label class="form-label" for="tokenLogo">Логотип токена (изображение):</label>
<input
id="tokenLogo"
type="file"
accept="image/*"
class="form-control"
@change="onLogoSelected"
>
<small class="form-help">Поддерживаются PNG/JPG/GIF/WEBP, до 5MB</small>
<div v-if="logoPreviewUrl" class="logo-preview" style="margin-top:8px;display:flex;gap:10px;align-items:center;">
<img :src="logoPreviewUrl" alt="logo preview" style="width:48px;height:48px;border-radius:6px;object-fit:contain;border:1px solid #e9ecef;" />
<span class="address">{{ logoFile?.name || 'Предпросмотр' }}</span>
</div>
</div>
<!-- ENS домен для логотипа -->
<div class="form-group">
<label class="form-label" for="ensDomain">ENSдомен для логотипа (опционально):</label>
<input
id="ensDomain"
type="text"
v-model="ensDomain"
placeholder="например: vc-hb3-accelerator.eth"
class="form-control"
@blur="resolveEnsAvatar"
>
<small class="form-help">Если указан, попытаемся получить аватар ENS и использовать его как logoURI</small>
<div v-if="ensResolvedUrl" style="margin-top:8px;display:flex;gap:10px;align-items:center;">
<img :src="ensResolvedUrl" alt="ens avatar" style="width:32px;height:32px;border-radius:50%;object-fit:cover;border:1px solid #e9ecef;" />
<span class="address">{{ ensResolvedUrl }}</span>
</div>
</div>
@@ -866,6 +901,7 @@ import { reactive, ref, computed, onMounted, onUnmounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { useAuthContext } from '@/composables/useAuth';
import axios from 'axios';
import api from '@/api/axios';
const router = useRouter();
@@ -2393,6 +2429,7 @@ const maskedPrivateKey = computed(() => {
// Функция деплоя смарт-контрактов DLE
const deploySmartContracts = async () => {
console.log('🚀 Начало деплоя DLE...');
try {
// Валидация данных
if (!isFormValid.value) {
@@ -2451,43 +2488,115 @@ const deploySmartContracts = async () => {
const preData = pre.data?.data;
if (pre.data?.success && preData) {
const lacks = (preData.insufficient || []);
const warnings = (preData.warnings || []);
if (lacks.length > 0) {
const lines = (preData.balances || []).map(b => `- Chain ${b.chainId}: ${b.balanceEth} ETH${b.ok ? '' : ' (недостаточно)'}`);
alert('Недостаточно средств в некоторых сетях:\n' + lines.join('\n'));
showDeployProgress.value = false;
return;
const lines = (preData.balances || []).map(b => {
const status = b.ok ? '✅' : '❌';
const warning = warnings.includes(b.chainId) ? ' ⚠️' : '';
return `${status} Chain ${b.chainId}: ${b.balanceEth} ETH (мин. ${b.minRequiredEth} ETH)${warning}`;
});
const message = `Проверка балансов завершена:\n\n${lines.join('\n')}\n\n${lacks.length > 0 ? '❌ Недостаточно средств в некоторых сетях!' : ''}\n${warnings.length > 0 ? '⚠️ Предупреждения в некоторых сетях!' : ''}`;
if (lacks.length > 0) {
alert(message);
showDeployProgress.value = false;
return;
} else if (warnings.length > 0) {
const proceed = confirm(message + '\n\nПродолжить деплой?');
if (!proceed) {
showDeployProgress.value = false;
return;
}
}
}
console.log('✅ Проверка балансов пройдена:', preData.summary);
}
} catch (e) {
console.warn('⚠️ Ошибка проверки балансов:', e.message);
// Если precheck недоступен, не блокируем — продолжаем
}
deployProgress.value = 30;
deployStatus.value = 'Компиляция смарт-контрактов...';
// Автокомпиляция контрактов перед деплоем
console.log('🔨 Запуск автокомпиляции...');
try {
const compileResponse = await axios.post('/compile-contracts');
console.log('✅ Контракты скомпилированы:', compileResponse.data);
} catch (compileError) {
console.warn('⚠️ Ошибка автокомпиляции:', compileError.message);
// Продолжаем деплой даже если компиляция не удалась
}
deployProgress.value = 40;
deployStatus.value = 'Отправка данных на сервер...';
// Вызов API для деплоя
const response = await axios.post('/dle-v2', deployData);
deployProgress.value = 70;
deployProgress.value = 50;
deployStatus.value = 'Деплой смарт-контракта в блокчейне...';
const response = await axios.post('/dle-v2', deployData);
deployProgress.value = 80;
deployStatus.value = 'Проверка результатов деплоя...';
if (response.data.success) {
deployProgress.value = 100;
deployStatus.value = '✅ DLE успешно развернут!';
const result = response.data.data;
// Сохраняем адрес контракта
// dleSettings.predictedAddress = response.data.data?.dleAddress || 'Адрес будет доступен после деплоя';
// Небольшая задержка для показа успешного завершения
setTimeout(() => {
showDeployProgress.value = false;
// Перенаправляем на главную страницу управления
router.push('/management');
}, 2000);
// Проверяем результаты мульти-чейн деплоя
if (result.networks && Array.isArray(result.networks)) {
const successfulNetworks = result.networks.filter(n => n.success);
const failedNetworks = result.networks.filter(n => !n.success);
if (failedNetworks.length > 0) {
console.warn('Некоторые сети не удалось развернуть:', failedNetworks);
}
if (successfulNetworks.length > 0) {
// Проверяем, что все адреса одинаковые
const addresses = successfulNetworks.map(n => n.address);
const uniqueAddresses = [...new Set(addresses)];
if (uniqueAddresses.length === 1) {
deployProgress.value = 100;
deployStatus.value = `✅ DLE успешно развернут в ${successfulNetworks.length} сетях с одинаковым адресом!`;
console.log('🎉 Мульти-чейн деплой завершен успешно!');
console.log('Адрес DLE:', uniqueAddresses[0]);
console.log('Сети:', successfulNetworks.map(n => `Chain ${n.chainId}: ${n.address}`));
// Небольшая задержка для показа успешного завершения
setTimeout(() => {
showDeployProgress.value = false;
// Перенаправляем на главную страницу управления
router.push('/management');
}, 3000);
} else {
showDeployProgress.value = false;
alert('❌ ОШИБКА: Адреса DLE в разных сетях не совпадают! Это может указывать на проблему с CREATE2.');
}
} else {
showDeployProgress.value = false;
alert('❌ Не удалось развернуть DLE ни в одной сети');
}
} else {
// Fallback для одиночного деплоя
deployProgress.value = 100;
deployStatus.value = '✅ DLE успешно развернут!';
setTimeout(() => {
showDeployProgress.value = false;
router.push('/management');
}, 2000);
}
} else {
showDeployProgress.value = false;
alert('❌ Ошибка при деплое: ' + response.data.error);
alert('❌ Ошибка при деплое: ' + (response.data.message || response.data.error));
}
} catch (error) {
@@ -2499,16 +2608,15 @@ const deploySmartContracts = async () => {
// Валидация формы
const isFormValid = computed(() => {
return (
return Boolean(
dleSettings.jurisdiction &&
dleSettings.name &&
dleSettings.tokenSymbol ||
dleSettings.tokenStandard !== 'ERC20' ||
dleSettings.partners.length > 0 &&
dleSettings.tokenSymbol &&
(dleSettings.partners.length > 0) &&
dleSettings.partners.every(partner => partner.address && partner.amount > 0) &&
dleSettings.governanceQuorum > 0 &&
dleSettings.governanceQuorum <= 100 &&
dleSettings.selectedNetworks.length > 0 &&
(dleSettings.selectedNetworks.length > 0) &&
// Проверка приватного ключа
unifiedPrivateKey.value &&
keyValidation.unified?.isValid &&
@@ -2523,6 +2631,88 @@ const validateCoordinates = (coordinates) => {
const coordRegex = /^-?\d+\.\d+,-?\d+\.\d+$/;
return coordRegex.test(coordinates);
};
const logoFile = ref(null);
const logoPreviewUrl = ref('');
const ensDomain = ref('');
const ensResolvedUrl = ref('');
function onLogoSelected(e) {
const file = e?.target?.files?.[0];
logoFile.value = file || null;
logoPreviewUrl.value = '';
if (file) {
try { logoPreviewUrl.value = URL.createObjectURL(file); } catch (_) {}
}
}
async function resolveEnsAvatar() {
ensResolvedUrl.value = '';
const name = (ensDomain.value || '').trim();
if (!name) return;
try {
const resp = await api.get(`/ens/avatar`, { params: { name } });
const url = resp.data?.data?.url;
if (url) {
ensResolvedUrl.value = url;
// если файл не выбран используем ENS для предпросмотра
if (!logoFile.value) logoPreviewUrl.value = url;
} else {
// фолбэк на дефолт
ensResolvedUrl.value = '/uploads/logos/default-token.svg';
if (!logoFile.value) logoPreviewUrl.value = ensResolvedUrl.value;
}
} catch (_) {
ensResolvedUrl.value = '/uploads/logos/default-token.svg';
if (!logoFile.value) logoPreviewUrl.value = ensResolvedUrl.value;
}
}
async function submitDeploy() {
try {
// Подготовка данных формы
const deployData = {
name: dleSettings.name,
symbol: dleSettings.tokenSymbol,
location: locationText.value,
coordinates: dleSettings.coordinates || '',
jurisdiction: Number(dleSettings.jurisdiction) || 1,
oktmo: Number(dleSettings.selectedOktmo) || null,
okvedCodes: Array.isArray(dleSettings.selectedOkved) ? dleSettings.selectedOkved.map(x => String(x)) : [],
kpp: dleSettings.kppCode ? Number(dleSettings.kppCode) : null,
initialPartners: dleSettings.partners.map(p => p.address).filter(Boolean),
initialAmounts: dleSettings.partners.map(p => p.amount).filter(a => a > 0),
supportedChainIds: dleSettings.selectedNetworks || [],
currentChainId: dleSettings.selectedNetworks[0] || 1,
privateKey: unifiedPrivateKey.value,
etherscanApiKey: etherscanApiKey.value,
autoVerifyAfterDeploy: autoVerifyAfterDeploy.value
};
// Если выбран логотип — загружаем и подставляем logoURI
if (logoFile.value) {
const form = new FormData();
form.append('logo', logoFile.value);
const uploadResp = await axios.post('/uploads/logo', form, { headers: { 'Content-Type': 'multipart/form-data' } });
const uploaded = uploadResp.data?.data?.url || uploadResp.data?.data?.path;
if (uploaded) {
deployData.logoURI = uploaded;
}
} else if (ensResolvedUrl.value) {
deployData.logoURI = ensResolvedUrl.value;
} else {
// фолбэк на дефолт
deployData.logoURI = '/uploads/logos/default-token.svg';
}
console.log('Данные для деплоя DLE:', deployData);
// ... остальные данные остаются без изменений
} catch (error) {
console.error('Ошибка при отправке данных:', error);
// Обработка ошибки
}
}
</script>
<style scoped>
@@ -4416,4 +4606,6 @@ const validateCoordinates = (coordinates) => {
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.logo-preview img { box-shadow: 0 1px 4px rgba(0,0,0,0.06); background:#fff; }
</style>

View File

@@ -541,6 +541,9 @@ const getChainName = (chainId) => {
1: 'Ethereum Mainnet',
11155111: 'Sepolia Testnet',
17000: 'Holesky Testnet',
84532: 'Base Sepolia Testnet',
80002: 'Polygon Amoy Testnet',
421614: 'Arbitrum Sepolia Testnet',
137: 'Polygon',
56: 'BSC',
42161: 'Arbitrum One'

View File

@@ -50,6 +50,13 @@ export default defineConfig({
rewrite: (path) => path,
ws: true,
},
'/compile-contracts': {
target: 'http://dapp-backend:8000',
changeOrigin: true,
secure: false,
credentials: true,
rewrite: (path) => path,
},
'/ws': {
target: 'ws://dapp-backend:8000',
ws: true,