前端与智能合约交互
核心知识点讲解
前端与智能合约交互的基本流程
前端应用与智能合约交互的基本流程包括:
- 连接钱包:获取用户的以太坊钱包地址
- 创建Provider:连接到以太坊网络
- 创建合约实例:使用合约ABI和地址创建合约实例
- 调用合约方法:调用合约的读取和写入方法
- 监听事件:监听合约触发的事件
- 处理交易:处理交易的提交、确认和状态更新
前端框架集成
常见的前端框架与Web3集成:
- React:使用useState、useEffect等钩子管理状态和副作用
- Vue:使用data、computed、watch等选项管理状态
- Angular:使用组件、服务等管理状态和逻辑
钱包连接
前端应用需要连接用户的钱包:
- MetaMask:最流行的以太坊钱包
- WalletConnect:支持多种钱包的连接协议
- Coinbase Wallet:Coinbase提供的钱包
- 其他钱包:如Trust Wallet、Ledger等
交易处理
前端应用需要处理交易的各个阶段:
- 交易签名:用户确认交易
- 交易提交:将交易提交到网络
- 交易确认:等待交易被打包确认
- 交易状态:更新UI显示交易状态
实用案例分析
React与智能合约交互
// React组件与智能合约交互
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
// 合约ABI
const contractABI = [
{
"inputs": [{"internalType": "uint256", "name": "_value", "type": "uint256"}],
"name": "setValue",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getValue",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "ValueChanged",
"type": "event"
}
];
// 合约地址
const contractAddress = '0x1234567890123456789012345678901234567890';
function SmartContractInteraction() {
const [provider, setProvider] = useState(null);
const [signer, setSigner] = useState(null);
const [contract, setContract] = useState(null);
const [value, setValue] = useState('0');
const [newValue, setNewValue] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [transactionHash, setTransactionHash] = useState('');
// 连接钱包
const connectWallet = async () => {
try {
if (!window.ethereum) {
throw new Error('MetaMask not detected');
}
// 请求用户授权
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
console.log('Connected accounts:', accounts);
// 创建Provider和Signer
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
// 创建合约实例
const contract = new ethers.Contract(contractAddress, contractABI, signer);
setProvider(provider);
setSigner(signer);
setContract(contract);
setError('');
} catch (err) {
setError('Failed to connect wallet: ' + err.message);
console.error('Wallet connection error:', err);
}
};
// 加载合约值
const loadValue = async () => {
if (!contract) {
setError('Please connect wallet first');
return;
}
try {
setLoading(true);
const contractValue = await contract.getValue();
setValue(contractValue.toString());
setError('');
} catch (err) {
setError('Failed to load value: ' + err.message);
console.error('Load value error:', err);
} finally {
setLoading(false);
}
};
// 设置合约值
const updateValue = async () => {
if (!contract) {
setError('Please connect wallet first');
return;
}
if (!newValue || isNaN(newValue)) {
setError('Please enter a valid number');
return;
}
try {
setLoading(true);
const tx = await contract.setValue(parseInt(newValue));
setTransactionHash(tx.hash);
console.log('Transaction submitted:', tx.hash);
// 等待交易确认
await tx.wait();
console.log('Transaction confirmed');
// 重新加载值
await loadValue();
setNewValue('');
setError('');
} catch (err) {
setError('Failed to update value: ' + err.message);
console.error('Update value error:', err);
} finally {
setLoading(false);
}
};
// 监听合约事件
useEffect(() => {
if (contract) {
// 监听ValueChanged事件
const listener = contract.on('ValueChanged', (newValue) => {
console.log('Event received:', newValue.toString());
setValue(newValue.toString());
});
// 清理函数
return () => {
if (listener) {
listener.removeAllListeners();
}
};
}
}, [contract]);
// 初始加载
useEffect(() => {
if (contract) {
loadValue();
}
}, [contract]);
return (
<div className="smart-contract-interaction">
<h1>智能合约交互</h1>
{!provider ? (
<button onClick={connectWallet} disabled={loading}>
{loading ? 'Connecting...' : 'Connect Wallet'}
</button>
) : (
<div>
<p>Connected to wallet: {signer && signer.address}</p>
<div className="value-section">
<h2>Current Value: {value}</h2>
<button onClick={loadValue} disabled={loading}>
{loading ? 'Loading...' : 'Load Value'}
</button>
</div>
<div className="update-section">
<h2>Update Value</h2>
<input
type="number"
value={newValue}
onChange={(e) => setNewValue(e.target.value)}
placeholder="Enter new value"
/>
<button onClick={updateValue} disabled={loading}>
{loading ? 'Updating...' : 'Update Value'}
</button>
</div>
{transactionHash && (
<div className="transaction-section">
<h3>Transaction Hash:</h3>
<p>{transactionHash}</p>
</div>
)}
</div>
)}
{error && (
<div className="error">
<p>{error}</p>
</div>
)}
</div>
);
}
export default SmartContractInteraction;Vue与智能合约交互
<template>
<div class="smart-contract-interaction">
<h1>智能合约交互</h1>
<button v-if="!provider" @click="connectWallet" :disabled="loading">
{{ loading ? 'Connecting...' : 'Connect Wallet' }}
</button>
<div v-else>
<p>Connected to wallet: {{ signerAddress }}</p>
<div class="value-section">
<h2>Current Value: {{ value }}</h2>
<button @click="loadValue" :disabled="loading">
{{ loading ? 'Loading...' : 'Load Value' }}
</button>
</div>
<div class="update-section">
<h2>Update Value</h2>
<input
type="number"
v-model="newValue"
placeholder="Enter new value"
/>
<button @click="updateValue" :disabled="loading">
{{ loading ? 'Updating...' : 'Update Value' }}
</button>
</div>
<div v-if="transactionHash" class="transaction-section">
<h3>Transaction Hash:</h3>
<p>{{ transactionHash }}</p>
</div>
</div>
<div v-if="error" class="error">
<p>{{ error }}</p>
</div>
</div>
</template>
<script>
import { ethers } from 'ethers';
export default {
name: 'SmartContractInteraction',
data() {
return {
provider: null,
signer: null,
contract: null,
signerAddress: '',
value: '0',
newValue: '',
loading: false,
error: '',
transactionHash: ''
};
},
mounted() {
// 组件挂载时检查是否已有钱包连接
this.checkWalletConnection();
},
methods: {
async checkWalletConnection() {
if (window.ethereum) {
try {
const accounts = await window.ethereum.request({ method: 'eth_accounts' });
if (accounts.length > 0) {
await this.setupProvider(accounts[0]);
}
} catch (err) {
console.error('Error checking wallet connection:', err);
}
}
},
async connectWallet() {
try {
if (!window.ethereum) {
throw new Error('MetaMask not detected');
}
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
await this.setupProvider(accounts[0]);
this.error = '';
} catch (err) {
this.error = 'Failed to connect wallet: ' + err.message;
console.error('Wallet connection error:', err);
}
},
async setupProvider(address) {
try {
this.loading = true;
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
// 合约ABI
const contractABI = [
{
"inputs": [{"internalType": "uint256", "name": "_value", "type": "uint256"}],
"name": "setValue",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getValue",
"outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [{"indexed": false, "internalType": "uint256", "name": "value", "type": "uint256"}],
"name": "ValueChanged",
"type": "event"
}
];
// 合约地址
const contractAddress = '0x1234567890123456789012345678901234567890';
const contract = new ethers.Contract(contractAddress, contractABI, signer);
this.provider = provider;
this.signer = signer;
this.contract = contract;
this.signerAddress = address;
// 监听事件
this.setupEventListeners();
// 加载初始值
await this.loadValue();
} catch (err) {
this.error = 'Failed to setup provider: ' + err.message;
console.error('Setup provider error:', err);
} finally {
this.loading = false;
}
},
async loadValue() {
if (!this.contract) {
this.error = 'Please connect wallet first';
return;
}
try {
this.loading = true;
const contractValue = await this.contract.getValue();
this.value = contractValue.toString();
this.error = '';
} catch (err) {
this.error = 'Failed to load value: ' + err.message;
console.error('Load value error:', err);
} finally {
this.loading = false;
}
},
async updateValue() {
if (!this.contract) {
this.error = 'Please connect wallet first';
return;
}
if (!this.newValue || isNaN(this.newValue)) {
this.error = 'Please enter a valid number';
return;
}
try {
this.loading = true;
const tx = await this.contract.setValue(parseInt(this.newValue));
this.transactionHash = tx.hash;
console.log('Transaction submitted:', tx.hash);
await tx.wait();
console.log('Transaction confirmed');
await this.loadValue();
this.newValue = '';
this.error = '';
} catch (err) {
this.error = 'Failed to update value: ' + err.message;
console.error('Update value error:', err);
} finally {
this.loading = false;
}
},
setupEventListeners() {
if (this.contract) {
this.contract.on('ValueChanged', (newValue) => {
console.log('Event received:', newValue.toString());
this.value = newValue.toString();
});
}
}
}
};
</script>
<style scoped>
.smart-contract-interaction {
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
.value-section,
.update-section,
.transaction-section {
margin: 20px 0;
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
}
.error {
color: red;
margin: 10px 0;
padding: 10px;
background-color: #ffe6e6;
border-radius: 5px;
}
button {
margin: 5px;
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
input {
padding: 10px;
margin: 5px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>钱包连接管理
// 钱包连接管理组件
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
function WalletConnector() {
const [provider, setProvider] = useState(null);
const [signer, setSigner] = useState(null);
const [address, setAddress] = useState('');
const [balance, setBalance] = useState('0');
const [network, setNetwork] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
// 连接钱包
const connectWallet = async () => {
try {
if (!window.ethereum) {
throw new Error('No wallet detected');
}
setLoading(true);
setError('');
// 请求账户访问
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
const address = accounts[0];
// 创建Provider和Signer
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
// 获取网络信息
const network = await provider.getNetwork();
// 获取余额
const balance = await provider.getBalance(address);
const balanceInEther = ethers.formatEther(balance);
setProvider(provider);
setSigner(signer);
setAddress(address);
setBalance(balanceInEther);
setNetwork(network);
} catch (err) {
setError('Failed to connect wallet: ' + err.message);
console.error('Wallet connection error:', err);
} finally {
setLoading(false);
}
};
// 断开钱包
const disconnectWallet = () => {
setProvider(null);
setSigner(null);
setAddress('');
setBalance('0');
setNetwork(null);
setError('');
};
// 监听账户变化
useEffect(() => {
if (window.ethereum) {
const handleAccountsChanged = (accounts) => {
if (accounts.length === 0) {
disconnectWallet();
} else {
setAddress(accounts[0]);
}
};
const handleNetworkChanged = async (networkId) => {
if (provider) {
const network = await provider.getNetwork();
setNetwork(network);
}
};
window.ethereum.on('accountsChanged', handleAccountsChanged);
window.ethereum.on('networkChanged', handleNetworkChanged);
return () => {
window.ethereum.off('accountsChanged', handleAccountsChanged);
window.ethereum.off('networkChanged', handleNetworkChanged);
};
}
}, [provider]);
return (
<div className="wallet-connector">
<h2>Wallet Connection</h2>
{!provider ? (
<button onClick={connectWallet} disabled={loading}>
{loading ? 'Connecting...' : 'Connect Wallet'}
</button>
) : (
<div>
<p><strong>Address:</strong> {address}</p>
<p><strong>Balance:</strong> {balance} ETH</p>
<p><strong>Network:</strong> {network?.name} (Chain ID: {network?.chainId})</p>
<button onClick={disconnectWallet}>Disconnect</button>
</div>
)}
{error && (
<div className="error">
<p>{error}</p>
</div>
)}
</div>
);
}
export default WalletConnector;交易状态管理
// 交易状态管理组件
import React, { useState } from 'react';
import { ethers } from 'ethers';
function TransactionManager({ contract }) {
const [transactionState, setTransactionState] = useState({
status: 'idle', // idle, pending, confirmed, failed
hash: '',
error: ''
});
const executeTransaction = async (transactionFunction, ...args) => {
if (!contract) {
setTransactionState({
status: 'failed',
hash: '',
error: 'Contract not initialized'
});
return null;
}
try {
setTransactionState({
status: 'pending',
hash: '',
error: ''
});
const tx = await transactionFunction(...args);
setTransactionState(prev => ({
...prev,
hash: tx.hash
}));
const receipt = await tx.wait();
setTransactionState({
status: 'confirmed',
hash: tx.hash,
error: ''
});
return receipt;
} catch (error) {
setTransactionState({
status: 'failed',
hash: '',
error: error.message
});
return null;
}
};
const resetState = () => {
setTransactionState({
status: 'idle',
hash: '',
error: ''
});
};
return (
<div className="transaction-manager">
<h3>Transaction Status</h3>
{transactionState.status === 'idle' && (
<p>Ready to execute transactions</p>
)}
{transactionState.status === 'pending' && (
<div className="pending">
<p>Transaction pending...</p>
{transactionState.hash && (
<p>Transaction hash: {transactionState.hash}</p>
)}
</div>
)}
{transactionState.status === 'confirmed' && (
<div className="confirmed">
<p>Transaction confirmed!</p>
<p>Transaction hash: {transactionState.hash}</p>
<button onClick={resetState}>Reset</button>
</div>
)}
{transactionState.status === 'failed' && (
<div className="failed">
<p>Transaction failed</p>
<p>Error: {transactionState.error}</p>
<button onClick={resetState}>Reset</button>
</div>
)}
</div>
);
}
export default TransactionManager;实践练习
创建React应用与智能合约交互:
- 初始化React项目
- 安装Ethers.js
- 实现钱包连接功能
- 与智能合约交互
- 处理交易状态
创建Vue应用与智能合约交互:
- 初始化Vue项目
- 安装Ethers.js
- 实现钱包连接功能
- 与智能合约交互
- 处理交易状态
实现钱包连接管理:
- 检测钱包是否安装
- 处理账户切换
- 处理网络切换
- 显示账户信息和余额
实现交易状态管理:
- 显示交易提交状态
- 显示交易确认状态
- 处理交易失败
- 提供交易哈希查询
实现事件监听:
- 监听智能合约事件
- 实时更新UI
- 处理事件数据
总结
前端与智能合约交互是Web3开发的核心部分,它使去中心化应用能够与区块链上的智能合约进行通信。通过本集的学习,你应该能够:
- 连接钱包:使用Ethers.js连接用户的以太坊钱包
- 创建合约实例:使用合约ABI和地址创建合约实例
- 调用合约方法:调用合约的读取和写入方法
- 处理交易:处理交易的提交、确认和状态更新
- 监听事件:监听合约触发的事件并更新UI
在实际开发中,前端应用与智能合约交互需要考虑用户体验、安全性和错误处理。通过合理的状态管理和错误处理,可以创建出用户友好的Web3应用。
前端框架(如React和Vue)提供了良好的状态管理和UI渲染能力,与Ethers.js结合使用,可以构建出功能完整、用户体验良好的Web3应用。