钱包连接与认证
核心知识点讲解
钱包连接的基本原理
钱包连接是Web3应用与用户交互的第一步:
- 钱包检测:检测用户是否安装了钱包
- 连接请求:请求用户授权连接钱包
- 地址获取:获取用户的钱包地址
- 网络检测:检测用户是否在正确的网络上
- 状态管理:管理钱包连接状态
签名认证的工作原理
签名认证是一种基于密码学的认证方法:
- 消息生成:生成一个随机消息或挑战
- 签名请求:请求用户签名消息
- 签名验证:验证签名是否来自声称的地址
- 会话管理:管理用户认证状态
钱包连接方法
常见的钱包连接方法:
- MetaMask直接连接:使用
window.ethereumAPI - WalletConnect:支持多种钱包的连接协议
- Coinbase Wallet SDK:Coinbase钱包的连接SDK
- 多钱包集成:同时支持多种钱包
认证流程
完整的认证流程包括:
- 钱包连接:连接用户的钱包
- 地址获取:获取用户的钱包地址
- 消息签名:请求用户签名消息
- 签名验证:验证签名的有效性
- 会话创建:创建用户会话
- 会话管理:管理会话状态和过期
实用案例分析
MetaMask钱包连接
// MetaMask钱包连接组件
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
function MetaMaskConnector() {
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('');
// 检查钱包是否安装
useEffect(() => {
if (window.ethereum) {
console.log('MetaMask detected');
} else {
setError('MetaMask not detected');
}
}, []);
// 连接钱包
const connectWallet = async () => {
try {
if (!window.ethereum) {
throw new Error('MetaMask not 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) {
try {
const network = await provider.getNetwork();
setNetwork(network);
} catch (err) {
console.error('Error getting network:', err);
}
}
};
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="metamask-connector">
<h2>MetaMask Wallet Connection</h2>
{!provider ? (
<button onClick={connectWallet} disabled={loading}>
{loading ? 'Connecting...' : 'Connect MetaMask'}
</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 MetaMaskConnector;钱包签名认证
// 钱包签名认证组件
import React, { useState } from 'react';
import { ethers } from 'ethers';
function WalletAuthentication({ signer, address }) {
const [message, setMessage] = useState('');
const [signature, setSignature] = useState('');
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
// 生成随机消息
const generateMessage = () => {
const randomMessage = `Sign this message to authenticate: ${Math.random().toString(36).substring(2)}`;
setMessage(randomMessage);
setSignature('');
setIsAuthenticated(false);
setError('');
};
// 签名消息
const signMessage = async () => {
if (!signer || !message) {
setError('Please connect wallet and generate message first');
return;
}
try {
setLoading(true);
setError('');
const signature = await signer.signMessage(message);
setSignature(signature);
// 验证签名
const recoveredAddress = ethers.verifyMessage(message, signature);
if (recoveredAddress.toLowerCase() === address.toLowerCase()) {
setIsAuthenticated(true);
console.log('Authentication successful');
} else {
setError('Signature verification failed');
console.error('Signature verification failed');
}
} catch (err) {
setError('Failed to sign message: ' + err.message);
console.error('Sign message error:', err);
} finally {
setLoading(false);
}
};
// 验证签名
const verifySignature = () => {
if (!message || !signature || !address) {
setError('Please sign a message first');
return;
}
try {
const recoveredAddress = ethers.verifyMessage(message, signature);
if (recoveredAddress.toLowerCase() === address.toLowerCase()) {
setIsAuthenticated(true);
setError('');
console.log('Signature verified successfully');
} else {
setIsAuthenticated(false);
setError('Signature verification failed');
console.error('Signature verification failed');
}
} catch (err) {
setError('Failed to verify signature: ' + err.message);
console.error('Verify signature error:', err);
}
};
return (
<div className="wallet-authentication">
<h2>Wallet Authentication</h2>
{!signer ? (
<p>Please connect wallet first</p>
) : (
<div>
<div className="message-section">
<button onClick={generateMessage}>Generate Message</button>
{message && (
<div>
<h3>Message:</h3>
<p>{message}</p>
</div>
)}
</div>
<div className="signature-section">
<button onClick={signMessage} disabled={!message || loading}>
{loading ? 'Signing...' : 'Sign Message'}
</button>
{signature && (
<div>
<h3>Signature:</h3>
<p>{signature}</p>
<button onClick={verifySignature}>Verify Signature</button>
</div>
)}
</div>
<div className="auth-status">
{isAuthenticated ? (
<p className="success">Authentication successful!</p>
) : (
<p className="pending">Not authenticated</p>
)}
</div>
</div>
)}
{error && (
<div className="error">
<p>{error}</p>
</div>
)}
</div>
);
}
export default WalletAuthentication;WalletConnect集成
// WalletConnect集成组件
import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import WalletConnectProvider from '@walletconnect/web3-provider';
function WalletConnectConnector() {
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 [connector, setConnector] = useState(null);
// 连接WalletConnect
const connectWalletConnect = async () => {
try {
setLoading(true);
setError('');
// 创建WalletConnect提供者
const walletConnectProvider = new WalletConnectProvider({
rpc: {
1: 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
11155111: 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY'
}
});
// 连接
await walletConnectProvider.enable();
// 创建ethers提供者
const ethersProvider = new ethers.BrowserProvider(walletConnectProvider);
const signer = await ethersProvider.getSigner();
// 获取地址
const address = await signer.getAddress();
// 获取网络信息
const network = await ethersProvider.getNetwork();
// 获取余额
const balance = await ethersProvider.getBalance(address);
const balanceInEther = ethers.formatEther(balance);
setProvider(ethersProvider);
setSigner(signer);
setAddress(address);
setBalance(balanceInEther);
setNetwork(network);
setConnector(walletConnectProvider);
} catch (err) {
setError('Failed to connect WalletConnect: ' + err.message);
console.error('WalletConnect connection error:', err);
} finally {
setLoading(false);
}
};
// 断开WalletConnect
const disconnectWalletConnect = async () => {
if (connector) {
await connector.disconnect();
}
setProvider(null);
setSigner(null);
setAddress('');
setBalance('0');
setNetwork(null);
setConnector(null);
setError('');
};
// 监听连接状态
useEffect(() => {
if (connector) {
connector.on('disconnect', () => {
disconnectWalletConnect();
});
connector.on('accountsChanged', (accounts) => {
if (accounts.length > 0) {
setAddress(accounts[0]);
} else {
disconnectWalletConnect();
}
});
connector.on('chainChanged', async (chainId) => {
if (provider) {
try {
const network = await provider.getNetwork();
setNetwork(network);
} catch (err) {
console.error('Error getting network:', err);
}
}
});
return () => {
if (connector) {
connector.off('disconnect');
connector.off('accountsChanged');
connector.off('chainChanged');
}
};
}
}, [connector, provider]);
return (
<div className="walletconnect-connector">
<h2>WalletConnect</h2>
{!provider ? (
<button onClick={connectWalletConnect} disabled={loading}>
{loading ? 'Connecting...' : 'Connect via WalletConnect'}
</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={disconnectWalletConnect}>Disconnect</button>
</div>
)}
{error && (
<div className="error">
<p>{error}</p>
</div>
)}
</div>
);
}
export default WalletConnectConnector;多钱包集成
// 多钱包集成组件
import React, { useState } from 'react';
import MetaMaskConnector from './MetaMaskConnector';
import WalletConnectConnector from './WalletConnectConnector';
function MultiWalletConnector() {
const [selectedWallet, setSelectedWallet] = useState('metaMask');
return (
<div className="multi-wallet-connector">
<h2>Connect Wallet</h2>
<div className="wallet-selector">
<button
className={selectedWallet === 'metaMask' ? 'active' : ''}
onClick={() => setSelectedWallet('metaMask')}
>
MetaMask
</button>
<button
className={selectedWallet === 'walletConnect' ? 'active' : ''}
onClick={() => setSelectedWallet('walletConnect')}
>
WalletConnect
</button>
</div>
<div className="wallet-connector">
{selectedWallet === 'metaMask' ? (
<MetaMaskConnector />
) : (
<WalletConnectConnector />
)}
</div>
</div>
);
}
export default MultiWalletConnector;会话管理
// 会话管理组件
import React, { useState, useEffect } from 'react';
function SessionManager({ isAuthenticated, address }) {
const [session, setSession] = useState(null);
const [sessionExpiry, setSessionExpiry] = useState(null);
// 创建会话
useEffect(() => {
if (isAuthenticated && address) {
const now = new Date();
const expiry = new Date(now.getTime() + 24 * 60 * 60 * 1000); // 24小时过期
const newSession = {
address,
createdAt: now,
expiresAt: expiry
};
// 存储会话到localStorage
localStorage.setItem('web3Session', JSON.stringify(newSession));
setSession(newSession);
setSessionExpiry(expiry);
}
}, [isAuthenticated, address]);
// 检查会话
useEffect(() => {
const storedSession = localStorage.getItem('web3Session');
if (storedSession) {
const session = JSON.parse(storedSession);
const now = new Date();
const expiresAt = new Date(session.expiresAt);
if (now < expiresAt) {
setSession(session);
setSessionExpiry(expiresAt);
} else {
// 会话过期
localStorage.removeItem('web3Session');
}
}
}, []);
// 清除会话
const clearSession = () => {
localStorage.removeItem('web3Session');
setSession(null);
setSessionExpiry(null);
};
return (
<div className="session-manager">
<h2>Session Management</h2>
{session ? (
<div>
<p><strong>Session active for address:</strong> {session.address}</p>
<p><strong>Expires at:</strong> {sessionExpiry?.toLocaleString()}</p>
<button onClick={clearSession}>Clear Session</button>
</div>
) : (
<p>No active session</p>
)}
</div>
);
}
export default SessionManager;实践练习
实现MetaMask钱包连接:
- 检测MetaMask是否安装
- 实现连接和断开功能
- 处理账户和网络变化
- 显示账户信息和余额
实现钱包签名认证:
- 生成随机消息
- 请求用户签名
- 验证签名
- 管理认证状态
集成WalletConnect:
- 安装WalletConnect库
- 实现WalletConnect连接
- 处理连接状态
- 与MetaMask连接集成
实现多钱包集成:
- 创建钱包选择界面
- 集成多种钱包连接方法
- 统一处理钱包状态
- 提供一致的用户体验
实现会话管理:
- 创建和存储会话
- 检查会话过期
- 清除会话
- 保持用户认证状态
总结
钱包连接与认证是Web3应用的重要组成部分,它为用户提供了安全、便捷的登录和认证方式。通过本集的学习,你应该能够:
- 连接钱包:使用MetaMask、WalletConnect等方式连接用户钱包
- 获取地址:获取用户的钱包地址和账户信息
- 签名认证:使用密码学签名进行用户认证
- 验证签名:验证签名的有效性和真实性
- 管理会话:创建和管理用户会话
在实际开发中,钱包连接与认证需要考虑用户体验、安全性和可靠性。通过合理的错误处理和状态管理,可以创建出安全、用户友好的Web3应用。
钱包连接与认证是Web3应用与用户交互的基础,它使得去中心化应用能够识别用户身份,为用户提供个性化的服务和体验。