Files
DLE/frontend/src/components/ContractInteraction.vue

562 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="contract-interaction">
<h2>Взаимодействие с контрактом</h2>
<div v-if="!isInitialized" class="loading-message">
Загрузка контракта...
</div>
<div v-if="!isConnected">
<button
@click="connectWallet"
class="connect-button"
>
Подключить кошелек
</button>
</div>
<div v-if="error" class="error-message">{{ error }}</div>
<div v-if="isConnected && isCorrectNetwork" class="wallet-info">
<p>Адрес кошелька: {{ address }}</p>
<div class="contract-controls">
<h3>Управление контрактом</h3>
<p v-if="currentPrice" class="price-info">
Текущая цена: {{ formatPrice(currentPrice) }} ETH
</p>
<!-- Панель управления для владельца -->
<div v-if="isOwner" class="owner-controls">
<h4>Панель владельца</h4>
<div class="input-group">
<input
v-model="newPrice"
type="number"
step="0.001"
placeholder="Новая цена (ETH)"
class="amount-input"
/>
<button
@click="handleSetPrice"
:disabled="!newPrice || isLoading"
class="admin-button"
>
Установить цену
</button>
</div>
<button
@click="handleWithdraw"
:disabled="isLoading"
class="admin-button withdraw-button"
>
Вывести средства
</button>
</div>
<!-- Панель покупки -->
<div class="purchase-panel">
<h4>Покупка</h4>
<div class="input-group">
<input
v-model="amount"
type="number"
placeholder="Введите количество"
class="amount-input"
/>
<button
@click="handlePurchase"
:disabled="!amount || isLoading"
class="purchase-button"
>
{{ isLoading ? 'Обработка...' : 'Купить' }}
</button>
</div>
<p v-if="amount && currentPrice" class="total-cost">
Общая стоимость: {{ formatPrice(calculateTotalCost()) }} ETH
</p>
</div>
<p v-if="success" class="success-message">{{ success }}</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed, watch } from 'vue'
import { BrowserProvider, Contract, JsonRpcProvider, formatEther, parseEther } from 'ethers'
// Инициализируем все ref переменные в начале
const amount = ref('')
const newPrice = ref('')
const isLoading = ref(false)
const error = ref('')
const success = ref('')
const currentPrice = ref(null)
const contractOwner = ref(null)
const isCorrectNetwork = ref(false)
const isConnected = ref(false)
const isInitialized = ref(false)
const address = ref(null)
const walletProvider = ref(null)
const isAuthenticated = ref(false)
// Константы
const SEPOLIA_CHAIN_ID = 11155111
const provider = new JsonRpcProvider(import.meta.env.VITE_APP_ETHEREUM_NETWORK_URL)
const contractAddress = '0xFF7602583E82C097Ae548Fc8B894F0a73089985E'
const contractABI = [
'function purchase(uint256 amount) payable',
'function price() view returns (uint256)',
'function owner() view returns (address)',
'function setPrice(uint256 newPrice) public',
'function withdraw() public',
'event Purchase(address buyer, uint256 amount)'
]
// Вычисляемые свойства
const isOwner = computed(() => {
return address.value && contractOwner.value &&
address.value.toLowerCase() === contractOwner.value.toLowerCase()
})
// Функции
function formatPrice(price) {
if (!price) return '0'
return formatEther(price)
}
// Функция инициализации контракта
async function initializeContract() {
try {
if (!provider) {
throw new Error('Provider не доступен')
}
const contract = new Contract(contractAddress, contractABI, provider)
await Promise.all([
contract.price().then(price => {
currentPrice.value = price
console.log('Начальная цена:', formatEther(price), 'ETH')
}),
contract.owner().then(owner => {
contractOwner.value = owner
console.log('Владелец контракта:', owner)
})
])
isInitialized.value = true
} catch (err) {
console.error('Ошибка при инициализации контракта:', err)
error.value = 'Ошибка при инициализации контракта: ' + err.message
}
}
// Функция подключения к MetaMask
async function connectWallet() {
try {
// Проверяем доступность MetaMask
if (!window.ethereum) {
throw new Error('MetaMask не установлен')
}
// Запрашиваем доступ к аккаунту
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
})
// Сохраняем адрес и провайдер
address.value = accounts[0]
walletProvider.value = window.ethereum
isConnected.value = true
// Подписываемся на изменение аккаунта
window.ethereum.on('accountsChanged', handleAccountsChanged)
window.ethereum.on('chainChanged', handleChainChanged)
await checkNetwork()
} catch (err) {
console.error('Ошибка при подключении кошелька:', err)
error.value = 'Ошибка при подключении кошелька: ' + err.message
}
}
// Обработчики событий MetaMask
function handleAccountsChanged(accounts) {
if (accounts.length === 0) {
// MetaMask отключен
isConnected.value = false
address.value = null
} else {
// Аккаунт изменен
address.value = accounts[0]
}
}
function handleChainChanged() {
// При смене сети перезагружаем страницу
window.location.reload()
}
// Обновляем watch
watch(isConnected, async (newValue) => {
try {
if (newValue) {
console.log('Кошелек подключен, адрес:', address.value)
if (!isInitialized.value) {
await initializeContract()
}
await checkNetwork()
} else {
console.log('Кошелек отключен')
currentPrice.value = null
contractOwner.value = null
isCorrectNetwork.value = false
error.value = ''
isInitialized.value = false
}
} catch (err) {
console.error('Ошибка при обработке подключения:', err)
error.value = 'Ошибка при обработке подключения: ' + err.message
}
}, { immediate: true })
// Обновляем функцию проверки сети
async function checkNetwork() {
try {
if (!walletProvider.value) {
isCorrectNetwork.value = false
error.value = 'Провайдер кошелька недоступен'
return
}
const ethersProvider = new BrowserProvider(walletProvider.value)
const network = await ethersProvider.getNetwork()
console.log('Текущая сеть:', network.chainId)
isCorrectNetwork.value = Number(network.chainId) === SEPOLIA_CHAIN_ID
if (!isCorrectNetwork.value) {
error.value = `Пожалуйста, переключитесь на сеть Sepolia (${SEPOLIA_CHAIN_ID}). Текущая сеть: ${network.chainId}`
} else {
error.value = ''
await Promise.all([fetchPrice(), fetchOwner()])
}
} catch (err) {
console.error('Ошибка при проверке сети:', err)
isCorrectNetwork.value = false
error.value = 'Ошибка при проверке сети: ' + err.message
}
}
// Обновляем функцию fetchPrice с обработкой ошибок
async function fetchPrice() {
try {
const contract = new Contract(contractAddress, contractABI, provider)
const price = await contract.price()
currentPrice.value = price
console.log('Текущая цена:', formatEther(price), 'ETH')
} catch (err) {
console.error('Ошибка при получении цены:', err)
error.value = `Не удалось получить текущую цену: ${err.message}`
currentPrice.value = null
}
}
// Получение адреса владельца
async function fetchOwner() {
try {
const contract = new Contract(contractAddress, contractABI, provider)
contractOwner.value = await contract.owner()
} catch (err) {
console.error('Ошибка при получении адреса владельца:', err)
}
}
// Обновляем onMounted
onMounted(async () => {
console.log('Компонент смонтирован')
try {
await initializeContract()
if (provider) {
const contract = new Contract(contractAddress, contractABI, provider)
contract.on('Purchase', (buyer, amount) => {
console.log(`Новая покупка: ${amount} единиц от ${buyer}`)
fetchPrice()
})
return () => {
contract.removeAllListeners('Purchase')
}
}
} catch (err) {
console.error('Ошибка при монтировании компонента:', err)
}
})
// Добавляем функцию для расчета общей стоимости
function calculateTotalCost() {
if (!currentPrice.value || !amount.value) return BigInt(0)
return currentPrice.value * BigInt(amount.value)
}
// Обновляем handlePurchase
async function handlePurchase() {
if (!amount.value) return
if (!isCorrectNetwork.value) {
error.value = 'Пожалуйста, переключитесь на сеть Sepolia'
return
}
error.value = ''
success.value = ''
try {
isLoading.value = true
const ethersProvider = new BrowserProvider(walletProvider.value)
const signer = await ethersProvider.getSigner()
const contract = new Contract(contractAddress, contractABI, signer)
const totalCost = calculateTotalCost()
console.log('Общая стоимость:', formatEther(totalCost), 'ETH')
const tx = await contract.purchase(amount.value, {
value: totalCost
})
console.log('Транзакция отправлена:', tx.hash)
await tx.wait()
console.log('Транзакция подтверждена')
amount.value = ''
success.value = 'Покупка успешно совершена!'
await fetchPrice()
} catch (err) {
console.error('Ошибка при покупке:', err)
if (err.message.includes('user rejected')) {
error.value = 'Транзакция отменена пользователем'
} else {
error.value = 'Произошла ошибка при совершении покупки: ' + err.message
}
} finally {
isLoading.value = false
}
}
// Добавляем новые функции
async function handleSetPrice() {
if (!newPrice.value) return
error.value = ''
success.value = ''
try {
isLoading.value = true
const ethersProvider = new BrowserProvider(walletProvider.value)
const signer = await ethersProvider.getSigner()
const contract = new Contract(contractAddress, contractABI, signer)
const priceInWei = parseEther(newPrice.value.toString())
const tx = await contract.setPrice(priceInWei)
await tx.wait()
newPrice.value = ''
success.value = 'Цена успешно обновлена!'
await fetchPrice()
} catch (err) {
console.error('Ошибка при установке цены:', err)
error.value = 'Ошибка при установке цены: ' + err.message
} finally {
isLoading.value = false
}
}
async function handleWithdraw() {
error.value = ''
success.value = ''
try {
isLoading.value = true
const ethersProvider = new BrowserProvider(walletProvider.value)
const signer = await ethersProvider.getSigner()
const contract = new Contract(contractAddress, contractABI, signer)
const tx = await contract.withdraw()
await tx.wait()
success.value = 'Средства успешно выведены!'
} catch (err) {
console.error('Ошибка при выводе средств:', err)
error.value = 'Ошибка при выводе средств: ' + err.message
} finally {
isLoading.value = false
}
}
</script>
<style scoped>
.contract-interaction {
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
.wallet-info {
margin-top: 20px;
padding: 15px;
background-color: #f5f5f5;
border-radius: 8px;
}
.contract-controls {
margin-top: 20px;
}
.input-group {
display: flex;
gap: 10px;
margin-top: 10px;
}
.amount-input {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
.purchase-button {
padding: 8px 16px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.purchase-button:hover:not(:disabled) {
background-color: #45a049;
}
.purchase-button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.error-message {
color: #dc3545;
padding: 10px;
margin: 10px 0;
background-color: #f8d7da;
border: 1px solid #f5c6cb;
border-radius: 4px;
}
.price-info {
margin: 10px 0;
font-weight: bold;
color: #2c3e50;
}
.owner-controls {
margin-top: 20px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.purchase-panel {
margin-top: 20px;
}
.admin-button {
padding: 8px 16px;
background-color: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
.admin-button:hover:not(:disabled) {
background-color: #0056b3;
}
.withdraw-button {
margin-top: 10px;
background-color: #6c757d;
}
.withdraw-button:hover:not(:disabled) {
background-color: #5a6268;
}
.total-cost {
margin-top: 10px;
font-size: 0.9em;
color: #6c757d;
}
.success-message {
color: #28a745;
margin-top: 10px;
font-size: 0.9em;
}
.loading-message {
color: #6c757d;
text-align: center;
padding: 20px;
background-color: #f8f9fa;
border-radius: 4px;
margin: 10px 0;
}
.auth-status {
margin: 10px 0;
padding: 10px;
background-color: #f8f9fa;
border-radius: 4px;
display: flex;
justify-content: space-between;
align-items: center;
}
.authenticated {
color: #28a745;
}
.signout-button {
padding: 5px 10px;
background-color: #dc3545;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.signout-button:hover {
background-color: #c82333;
}
.connect-button {
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
transition: background-color 0.3s;
}
.connect-button:hover {
background-color: #45a049;
}
</style>