Initial commit
This commit is contained in:
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DApp for Business</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<!-- Vite автоматически вставит сюда ваш JavaScript -->
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
24
frontend/package.json
Normal file
24
frontend/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "frontend",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reown/appkit": "1.6.8",
|
||||
"@reown/appkit-adapter-ethers": "1.6.8",
|
||||
"@reown/appkit-siwe": "^1.6.8",
|
||||
"ethers": "^6.13.4",
|
||||
"siwe": "^3.0.0",
|
||||
"viem": "^2.23.2",
|
||||
"vue": "^3.5.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.0",
|
||||
"vite": "^5.4.10"
|
||||
}
|
||||
}
|
||||
27
frontend/src/App.vue
Normal file
27
frontend/src/App.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<h1>Добро пожаловать в DApp-for-Business</h1>
|
||||
<appkit-button
|
||||
balance="show"
|
||||
size="md"
|
||||
label="Подключить кошелек"
|
||||
loadingLabel="Подключение..."
|
||||
/>
|
||||
<ContractInteraction />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ContractInteraction from './components/ContractInteraction.vue'
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
ContractInteraction
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Добавьте ваши стили здесь */
|
||||
</style>
|
||||
218
frontend/src/components/ContractInteraction.vue
Normal file
218
frontend/src/components/ContractInteraction.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="contract-interaction">
|
||||
<h2>Управление контрактом</h2>
|
||||
|
||||
<div v-if="!isConnected" class="warning">
|
||||
Пожалуйста, подключите кошелек для управления контрактом
|
||||
<button @click="handleConnect" class="connect-button">
|
||||
Подключить кошелек
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<div class="owner-info">
|
||||
<p>Текущий владелец:
|
||||
<span v-if="loading">Загрузка...</span>
|
||||
<span v-else-if="owner">{{ formatAddress(owner) }}</span>
|
||||
<span v-else>Не удалось загрузить</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="owner-controls">
|
||||
<input
|
||||
v-model="newOwner"
|
||||
placeholder="Адрес нового владельца (0x...)"
|
||||
:disabled="!isConnected"
|
||||
/>
|
||||
<button
|
||||
@click="setNewOwner"
|
||||
:disabled="!isConnected || !isValidAddress(newOwner)"
|
||||
>
|
||||
Установить нового владельца
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { ethers } from 'ethers';
|
||||
import { useAppKitAccount, useAppKitProvider, useAppKit } from '@reown/appkit/vue';
|
||||
|
||||
export default {
|
||||
name: 'ContractInteraction',
|
||||
setup() {
|
||||
const owner = ref('');
|
||||
const newOwner = ref('');
|
||||
const loading = ref(false);
|
||||
const { address, isConnected } = useAppKitAccount();
|
||||
const { walletProvider } = useAppKitProvider('eip155');
|
||||
const appKit = useAppKit();
|
||||
|
||||
const contractAddress = '0x6199Ba629C85Da887dBd8Ffd8d2C75Ea24EaDe2a';
|
||||
const contractABI = [
|
||||
'function owner() view returns (address)',
|
||||
'function setOwner(address newOwner)',
|
||||
];
|
||||
|
||||
const formatAddress = (addr) => {
|
||||
return addr.slice(0, 6) + '...' + addr.slice(-4);
|
||||
};
|
||||
|
||||
const isValidAddress = (addr) => {
|
||||
try {
|
||||
return ethers.isAddress(addr);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Добавим логирование для отладки
|
||||
watch(() => isConnected, (newValue) => {
|
||||
console.log('Состояние подключения изменилось:', newValue);
|
||||
console.log('Адрес кошелька:', address);
|
||||
}, { immediate: true });
|
||||
|
||||
const fetchOwner = async () => {
|
||||
if (!isConnected) return;
|
||||
loading.value = true;
|
||||
try {
|
||||
console.log('Получаем владельца контракта...');
|
||||
const ethersProvider = new ethers.BrowserProvider(walletProvider);
|
||||
const contract = new ethers.Contract(contractAddress, contractABI, ethersProvider);
|
||||
owner.value = await contract.owner();
|
||||
console.log('Владелец контракта:', owner.value);
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении владельца:', error);
|
||||
owner.value = '';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const setNewOwner = async () => {
|
||||
try {
|
||||
if (!isConnected) {
|
||||
console.log('Пожалуйста, подключите кошелек');
|
||||
return;
|
||||
}
|
||||
if (!isValidAddress(newOwner.value)) {
|
||||
console.log('Неверный адрес');
|
||||
return;
|
||||
}
|
||||
|
||||
const ethersProvider = new ethers.BrowserProvider(walletProvider);
|
||||
const signer = await ethersProvider.getSigner();
|
||||
const contract = new ethers.Contract(contractAddress, contractABI, signer);
|
||||
|
||||
const tx = await contract.setOwner(newOwner.value);
|
||||
await tx.wait();
|
||||
|
||||
// Обновляем информацию после успешной транзакции
|
||||
await fetchOwner();
|
||||
newOwner.value = ''; // Очищаем поле ввода
|
||||
} catch (error) {
|
||||
console.error('Ошибка при установке нового владельца:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Обработчик подключения кошелька
|
||||
const handleConnect = async () => {
|
||||
try {
|
||||
await appKit.open();
|
||||
} catch (error) {
|
||||
console.error('Ошибка при подключении:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// Обновляем watch
|
||||
watch(() => isConnected, (newValue, oldValue) => {
|
||||
console.log('Состояние подключения изменилось:', { newValue, oldValue });
|
||||
if (newValue) {
|
||||
fetchOwner();
|
||||
} else {
|
||||
owner.value = '';
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
onMounted(() => {
|
||||
// Проверяем состояние подключения при монтировании
|
||||
console.log('Компонент смонтирован, isConnected:', isConnected);
|
||||
fetchOwner();
|
||||
});
|
||||
|
||||
return {
|
||||
owner,
|
||||
newOwner,
|
||||
isConnected,
|
||||
loading,
|
||||
handleConnect,
|
||||
walletProvider,
|
||||
setNewOwner,
|
||||
formatAddress,
|
||||
isValidAddress
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.contract-interaction {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: #721c24;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.owner-info {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.owner-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
padding: 8px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background-color: #cccccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
button:hover:not(:disabled) {
|
||||
background-color: #45a049;
|
||||
}
|
||||
|
||||
.connect-button {
|
||||
margin-top: 10px;
|
||||
background-color: #007bff;
|
||||
}
|
||||
|
||||
.connect-button:hover:not(:disabled) {
|
||||
background-color: #0056b3;
|
||||
}
|
||||
</style>
|
||||
31
frontend/src/components/WalletButtonWrapper.vue
Normal file
31
frontend/src/components/WalletButtonWrapper.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div ref="walletButton"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import '@reown/appkit-wallet-button/react';
|
||||
|
||||
export default {
|
||||
name: 'WalletButtonWrapper',
|
||||
props: {
|
||||
wallet: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const walletButton = ref(null);
|
||||
|
||||
onMounted(() => {
|
||||
const button = document.createElement('appkit-wallet-button');
|
||||
button.setAttribute('wallet', props.wallet);
|
||||
walletButton.value.appendChild(button);
|
||||
});
|
||||
|
||||
return {
|
||||
walletButton
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
8
frontend/src/config/index.ts
Normal file
8
frontend/src/config/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { EthersAdapter } from '@reown/appkit-adapter-ethers'
|
||||
import { sepolia } from '@reown/appkit/networks'
|
||||
|
||||
export const projectId = '9a6515f7259ebccd149fd53341e01e6b'
|
||||
|
||||
export const networks = [sepolia]
|
||||
|
||||
export const ethersAdapter = new EthersAdapter()
|
||||
105
frontend/src/main.js
Normal file
105
frontend/src/main.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import { createAppKit } from '@reown/appkit/vue';
|
||||
import { EthersAdapter } from '@reown/appkit-adapter-ethers';
|
||||
import { sepolia } from '@reown/appkit/networks';
|
||||
import { createSIWEConfig, formatMessage } from '@reown/appkit-siwe';
|
||||
|
||||
// Определяем базовый URL для API
|
||||
const BASE_URL = 'http://localhost:3000';
|
||||
|
||||
// 1. Get projectId
|
||||
const projectId = '9a6515f7259ebccd149fd53341e01e6b';
|
||||
|
||||
// 2. Create SIWE config
|
||||
const siweConfig = createSIWEConfig({
|
||||
getMessageParams: async () => ({
|
||||
domain: window.location.host,
|
||||
uri: window.location.origin,
|
||||
chains: [11155111], // Sepolia chainId
|
||||
statement: 'Подпишите это сообщение для входа в DApp for Business. Это безопасно и не требует оплаты.',
|
||||
}),
|
||||
createMessage: ({ address, ...args }) => formatMessage(args, address),
|
||||
getNonce: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/nonce`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'text/plain'
|
||||
}
|
||||
});
|
||||
if (!res.ok) throw new Error('Failed to get nonce');
|
||||
return await res.text();
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения nonce:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
getSession: async () => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/session`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
headers: {
|
||||
'Accept': 'application/json'
|
||||
}
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
return await res.json();
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения сессии:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
verifyMessage: async ({ message, signature }) => {
|
||||
try {
|
||||
const res = await fetch(`${BASE_URL}/verify`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ message, signature }),
|
||||
credentials: 'include'
|
||||
});
|
||||
return res.ok;
|
||||
} catch (error) {
|
||||
console.error('Ошибка верификации:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
signOut: async () => {
|
||||
try {
|
||||
await fetch(`${BASE_URL}/signout`, {
|
||||
method: 'GET',
|
||||
credentials: 'include'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка выхода:', error);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. Create AppKit instance
|
||||
createAppKit({
|
||||
adapters: [new EthersAdapter()],
|
||||
networks: [sepolia],
|
||||
projectId,
|
||||
metadata: {
|
||||
name: 'DApp for Business',
|
||||
description: 'Smart Contract Management DApp',
|
||||
url: window.location.origin,
|
||||
icons: ['https://avatars.githubusercontent.com/u/37784886']
|
||||
},
|
||||
defaultNetwork: sepolia,
|
||||
features: {
|
||||
analytics: true,
|
||||
connectMethodsOrder: ['wallet', 'email', 'social'],
|
||||
autoConnect: false
|
||||
},
|
||||
siweConfig
|
||||
});
|
||||
|
||||
const app = createApp(App);
|
||||
app.mount('#app');
|
||||
16
frontend/vite.config.js
Normal file
16
frontend/vite.config.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue({
|
||||
template: {
|
||||
compilerOptions: {
|
||||
isCustomElement: (tag) =>
|
||||
tag.startsWith('appkit-') ||
|
||||
tag.startsWith('w3m-')
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
});
|
||||
1990
frontend/yarn.lock
Normal file
1990
frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user