跳到主要内容

Web3钱包连接管理

在现代Web3应用中,钱包连接是用户与区块链交互的基础。有效的钱包连接管理不仅能提供流畅的用户体验,还能增强应用的安全性和可靠性。本文将详细介绍Web3钱包连接的核心概念、实现方法和最佳实践。

1. 钱包连接基础

1.1 钱包连接协议概述

Web3应用与钱包的连接主要通过以下几种协议实现:

  • WalletConnect:跨平台协议,支持移动端钱包与网页应用的安全连接
  • MetaMask SDK:MetaMask官方提供的开发工具包
  • Coinbase Wallet SDK:Coinbase钱包的官方开发工具包
  • WalletLink:Coinbase开发的开源协议,支持多种钱包

每种协议都有其特定的使用场景和优势,开发者应根据项目需求选择合适的连接方案。

1.2 连接状态管理

钱包连接状态是Web3应用中的核心状态之一,需要妥善管理。

// 钱包连接状态管理器
const WalletConnectionManager = {
// 初始状态
state: {
isConnected: false,
address: null,
chainId: null,
provider: null,
walletType: null,
balance: null,
ensName: null,
connectionError: null
},

// 订阅状态变化的回调函数
subscribers: new Set(),

// 更新连接状态
updateState(newState) {
this.state = { ...this.state, ...newState };
this.notifySubscribers();
},

// 订阅状态变化
subscribe(callback) {
this.subscribers.add(callback);
// 立即调用一次,提供当前状态
callback(this.state);

// 返回取消订阅函数
return () => this.unsubscribe(callback);
},

// 取消订阅
unsubscribe(callback) {
this.subscribers.delete(callback);
},

// 通知所有订阅者
notifySubscribers() {
this.subscribers.forEach(callback => {
try {
callback(this.state);
} catch (error) {
console.error('Error in wallet connection subscriber:', error);
}
});
},

// 重置连接状态
resetState() {
this.updateState({
isConnected: false,
address: null,
chainId: null,
provider: null,
walletType: null,
balance: null,
ensName: null,
connectionError: null
});
},

// 获取当前状态
getState() {
return { ...this.state };
}
};

// 使用示例:在React组件中订阅钱包连接状态
function useWalletConnection() {
const [walletState, setWalletState] = useState(WalletConnectionManager.getState());

useEffect(() => {
// 订阅状态变化
const unsubscribe = WalletConnectionManager.subscribe(setWalletState);

// 组件卸载时取消订阅
return () => unsubscribe();
}, []);

return walletState;
}

2. 多钱包连接实现

2.1 支持多种钱包提供商

现代Web3应用应该支持多种钱包,以满足不同用户的需求。以下是一个支持多种钱包的连接管理器实现:

// 多钱包连接管理器
class MultiWalletConnector {
constructor(options = {}) {
this.supportedWallets = {
metamask: new MetaMaskConnector(),
walletconnect: new WalletConnectConnector(),
coinbasewallet: new CoinbaseWalletConnector(),
brave: new BraveWalletConnector(),
phantom: new PhantomWalletConnector()
};

this.activeWallet = null;
this.options = options;
}

// 初始化所有支持的钱包
async init() {
for (const [walletType, wallet] of Object.entries(this.supportedWallets)) {
try {
await wallet.init(this.options[walletType]);
} catch (error) {
console.warn(`Failed to initialize ${walletType}:`, error);
}
}
}

// 连接特定类型的钱包
async connect(walletType, options = {}) {
if (!this.supportedWallets[walletType]) {
throw new Error(`Unsupported wallet type: ${walletType}`);
}

// 先断开现有连接
if (this.activeWallet) {
await this.disconnect();
}

try {
const wallet = this.supportedWallets[walletType];
const connectionResult = await wallet.connect(options);

// 更新活跃钱包
this.activeWallet = walletType;

// 更新钱包连接状态
WalletConnectionManager.updateState({
isConnected: true,
address: connectionResult.address,
chainId: connectionResult.chainId,
provider: connectionResult.provider,
walletType: walletType,
connectionError: null
});

// 监听连接变化
this.setupListeners(wallet);

return connectionResult;
} catch (error) {
WalletConnectionManager.updateState({
connectionError: error.message
});
throw error;
}
}

// 断开当前钱包连接
async disconnect() {
if (this.activeWallet) {
const wallet = this.supportedWallets[this.activeWallet];

try {
await wallet.disconnect();
this.removeListeners(wallet);
} catch (error) {
console.warn('Error during wallet disconnection:', error);
} finally {
this.activeWallet = null;
WalletConnectionManager.resetState();
}
}
}

// 设置事件监听器
setupListeners(wallet) {
if (wallet.on) {
wallet.on('accountsChanged', this.handleAccountsChanged.bind(this));
wallet.on('chainChanged', this.handleChainChanged.bind(this));
wallet.on('disconnect', this.handleDisconnect.bind(this));
}
}

// 移除事件监听器
removeListeners(wallet) {
if (wallet.off) {
wallet.off('accountsChanged', this.handleAccountsChanged.bind(this));
wallet.off('chainChanged', this.handleChainChanged.bind(this));
wallet.off('disconnect', this.handleDisconnect.bind(this));
}
}

// 处理账户变化
handleAccountsChanged(accounts) {
if (accounts.length === 0) {
this.disconnect();
} else {
WalletConnectionManager.updateState({
address: accounts[0]
});
}
}

// 处理链变化
handleChainChanged(chainId) {
WalletConnectionManager.updateState({
chainId: parseInt(chainId, 16) // 从16进制转换为十进制
});
}

// 处理断开连接
handleDisconnect(error) {
console.warn('Wallet disconnected:', error);
this.disconnect();
}

// 获取当前活跃钱包
getActiveWallet() {
return this.activeWallet ? this.supportedWallets[this.activeWallet] : null;
}

// 获取支持的钱包列表
getSupportedWallets() {
return Object.keys(this.supportedWallets);
}

// 检查特定钱包是否可用
async isWalletAvailable(walletType) {
const wallet = this.supportedWallets[walletType];
return wallet && (await wallet.isAvailable());
}

// 获取钱包连接状态
getConnectionState() {
return WalletConnectionManager.getState();
}
}

// 具体钱包连接器实现示例 - MetaMask
class MetaMaskConnector {
async init(options = {}) {
this.options = options;
}

async isAvailable() {
return typeof window.ethereum !== 'undefined' && window.ethereum.isMetaMask;
}

async connect(options = {}) {
if (!await this.isAvailable()) {
throw new Error('MetaMask is not available');
}

try {
// 请求账户访问权限
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});

// 获取当前链ID
const chainId = await window.ethereum.request({
method: 'eth_chainId'
});

return {
address: accounts[0],
chainId: parseInt(chainId, 16),
provider: window.ethereum
};
} catch (error) {
throw new Error(`MetaMask connection failed: ${error.message}`);
}
}

async disconnect() {
// MetaMask不提供显式的断开连接方法
// 我们只能移除监听器
if (this.provider) {
this.removeAllListeners();
}
}

on(event, handler) {
if (typeof window.ethereum !== 'undefined') {
window.ethereum.on(event, handler);
}
}

off(event, handler) {
if (typeof window.ethereum !== 'undefined') {
window.ethereum.removeListener(event, handler);
}
}

removeAllListeners() {
if (typeof window.ethereum !== 'undefined') {
window.ethereum.removeAllListeners();
}
}
}

// WalletConnect连接器实现
class WalletConnectConnector {
constructor() {
this.wc = null;
this.connector = null;
this.chainId = 1; // 默认以太坊主网
}

async init(options = {}) {
this.options = options;
this.chainId = options.chainId || 1;

// 动态导入WalletConnect库
const WalletConnect = (await import('@walletconnect/client')).default;
const QRCodeModal = (await import('@walletconnect/qrcode-modal')).default;

this.WalletConnect = WalletConnect;
this.QRCodeModal = QRCodeModal;
}

async isAvailable() {
return true; // WalletConnect可以在任何支持二维码扫描的设备上使用
}

async connect(options = {}) {
const chainId = options.chainId || this.chainId;

// 创建WalletConnect实例
this.connector = new this.WalletConnect({
bridge: options.bridge || 'https://bridge.walletconnect.org',
qrcodeModal: this.QRCodeModal
});

// 检查是否已连接
if (!this.connector.connected) {
// 创建新连接
await this.connector.createSession({
chainId,
rpc: {
[chainId]: options.rpcUrl || `https://mainnet.infura.io/v3/${options.infuraId}`
}
});
}

return new Promise((resolve, reject) => {
// 监听连接事件
this.connector.on('connect', (error, payload) => {
if (error) {
reject(new Error(`WalletConnect connection failed: ${error.message}`));
} else {
const { accounts, chainId } = payload.params[0];

resolve({
address: accounts[0],
chainId,
provider: this.connector
});
}
});

// 设置连接超时
setTimeout(() => {
if (!this.connector.connected) {
reject(new Error('WalletConnect connection timeout'));
}
}, options.timeout || 120000); // 默认2分钟
});
}

async disconnect() {
if (this.connector) {
await this.connector.killSession();
this.connector = null;
}
}

on(event, handler) {
if (this.connector) {
this.connector.on(event, handler);
}
}

off(event, handler) {
if (this.connector) {
this.connector.off(event, handler);
}
}
}

// 使用示例:初始化和连接钱包
async function initializeWalletConnection() {
const walletConnector = new MultiWalletConnector({
metamask: {},
walletconnect: {
infuraId: 'your-infura-id',
chainId: 1
},
coinbasewallet: {
appName: 'Your Web3 App',
chainId: 1
}
});

await walletConnector.init();

// 检查哪些钱包可用
const availableWallets = [];
for (const walletType of walletConnector.getSupportedWallets()) {
if (await walletConnector.isWalletAvailable(walletType)) {
availableWallets.push(walletType);
}
}

console.log('可用的钱包:', availableWallets);

return walletConnector;
}

async function connectWalletExample(walletType) {
try {
const walletConnector = await initializeWalletConnection();
const result = await walletConnector.connect(walletType);

console.log('钱包连接成功:', result);

// 订阅连接状态变化
WalletConnectionManager.subscribe(state => {
console.log('钱包状态更新:', state);
});

return walletConnector;
} catch (error) {
console.error('钱包连接失败:', error);
throw error;
}
}

2.2 钱包连接UI组件

以下是一个React组件示例,用于显示钱包连接选项并处理连接流程:

// 钱包连接按钮组件
function WalletConnectButton({ onConnect, onDisconnect, disabled = false }) {
const [isConnecting, setIsConnecting] = useState(false);
const [showWalletOptions, setShowWalletOptions] = useState(false);
const walletState = useWalletConnection();

const handleConnect = async (walletType) => {
setIsConnecting(true);

try {
const connector = await connectWalletExample(walletType);
if (onConnect) {
onConnect(connector);
}
} catch (error) {
console.error('Wallet connection failed:', error);
alert(`连接钱包失败: ${error.message}`);
} finally {
setIsConnecting(false);
setShowWalletOptions(false);
}
};

const handleDisconnect = async () => {
if (onDisconnect) {
await onDisconnect();
}
};

// 格式化钱包地址显示(如:0x1234...5678)
const formatAddress = (address) => {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};

// 如果已连接,显示断开连接按钮
if (walletState.isConnected) {
return (
<div className="wallet-connected-container">
<div className="wallet-info">
<span className="wallet-address">
{formatAddress(walletState.address)}
</span>
<span className="wallet-type">
({walletState.walletType})
</span>
</div>
<button
className="disconnect-button"
onClick={handleDisconnect}
disabled={disabled}
>
断开连接
</button>
</div>
);
}

return (
<div className="wallet-connect-container">
<button
className="connect-button"
onClick={() => setShowWalletOptions(!showWalletOptions)}
disabled={disabled || isConnecting}
>
{isConnecting ? '连接中...' : '连接钱包'}
</button>

{showWalletOptions && (
<div className="wallet-options-dropdown">
<button
className="wallet-option"
onClick={() => handleConnect('metamask')}
disabled={isConnecting}
>
<img src="/metamask-logo.png" alt="MetaMask" className="wallet-logo" />
<span>MetaMask</span>
</button>
<button
className="wallet-option"
onClick={() => handleConnect('walletconnect')}
disabled={isConnecting}
>
<img src="/walletconnect-logo.png" alt="WalletConnect" className="wallet-logo" />
<span>WalletConnect</span>
</button>
<button
className="wallet-option"
onClick={() => handleConnect('coinbasewallet')}
disabled={isConnecting}
>
<img src="/coinbasewallet-logo.png" alt="Coinbase Wallet" className="wallet-logo" />
<span>Coinbase Wallet</span>
</button>
</div>
)}
</div>
);
}

// 钱包连接状态显示组件
function WalletConnectionStatus() {
const walletState = useWalletConnection();

if (!walletState) {
return null;
}

return (
<div className="wallet-connection-status">
<div className="status-indicator">
<span
className={`status-dot ${
walletState.isConnected ? 'connected' : 'disconnected'
}`}
/>
<span className="status-text">
{walletState.isConnected ? '已连接' : '未连接'}
</span>
</div>

{walletState.isConnected && (
<div className="connection-details">
<div className="detail-item">
<span className="detail-label">地址:</span>
<span className="detail-value">
{walletState.address}
</span>
</div>
<div className="detail-item">
<span className="detail-label">链ID:</span>
<span className="detail-value">
{walletState.chainId}
</span>
</div>
<div className="detail-item">
<span className="detail-label">钱包类型:</span>
<span className="detail-value">
{walletState.walletType}
</span>
</div>
</div>
)}

{walletState.connectionError && (
<div className="connection-error">
错误: {walletState.connectionError}
</div>
)}
</div>
);
}

3. 高级钱包连接功能

3.1 自动重连机制

实现钱包自动重连机制,提升用户体验:

// 钱包自动重连管理器
class WalletReconnectManager {
constructor(walletConnector) {
this.walletConnector = walletConnector;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 3000; // 3秒
this.isReconnecting = false;
this.lastConnectionInfo = null;
}

// 保存连接信息以用于重连
saveConnectionInfo(connectionInfo) {
try {
localStorage.setItem('walletConnectionInfo', JSON.stringify({
walletType: connectionInfo.walletType,
timestamp: Date.now()
}));
this.lastConnectionInfo = connectionInfo;
} catch (error) {
console.warn('Failed to save wallet connection info:', error);
}
}

// 清除保存的连接信息
clearConnectionInfo() {
try {
localStorage.removeItem('walletConnectionInfo');
this.lastConnectionInfo = null;
} catch (error) {
console.warn('Failed to clear wallet connection info:', error);
}
}

// 尝试自动重连
async attemptReconnect() {
if (this.isReconnecting || this.reconnectAttempts >= this.maxReconnectAttempts) {
return false;
}

try {
const savedInfo = this.getSavedConnectionInfo();

if (!savedInfo) {
return false;
}

this.isReconnecting = true;
this.reconnectAttempts++;

console.log(`Attempting wallet reconnection (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);

// 尝试重连到上次使用的钱包
await this.walletConnector.connect(savedInfo.walletType);

console.log('Wallet reconnection successful');
this.resetReconnectState();
return true;
} catch (error) {
console.warn('Wallet reconnection failed:', error);

// 安排下次重连尝试
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => this.attemptReconnect(), this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1));
} else {
console.error('Max wallet reconnection attempts reached');
this.resetReconnectState();
}

return false;
} finally {
this.isReconnecting = false;
}
}

// 获取保存的连接信息
getSavedConnectionInfo() {
try {
const savedInfo = localStorage.getItem('walletConnectionInfo');

if (savedInfo) {
const parsedInfo = JSON.parse(savedInfo);

// 检查连接信息是否过期(7天)
const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000;
if (Date.now() - parsedInfo.timestamp > sevenDaysInMs) {
this.clearConnectionInfo();
return null;
}

return parsedInfo;
}
} catch (error) {
console.warn('Failed to retrieve wallet connection info:', error);
}

return null;
}

// 重置重连状态
resetReconnectState() {
this.reconnectAttempts = 0;
this.isReconnecting = false;
}

// 设置最大重连尝试次数
setMaxReconnectAttempts(attempts) {
this.maxReconnectAttempts = attempts;
}

// 设置重连延迟时间(毫秒)
setReconnectDelay(delay) {
this.reconnectDelay = delay;
}
}

// 使用示例:设置自动重连
async function setupAutoReconnect() {
const walletConnector = await initializeWalletConnection();
const reconnectManager = new WalletReconnectManager(walletConnector);

// 设置最大重连尝试次数和延迟
reconnectManager.setMaxReconnectAttempts(3);
reconnectManager.setReconnectDelay(2000);

// 监听连接成功事件,保存连接信息
WalletConnectionManager.subscribe(state => {
if (state.isConnected && state.walletType) {
reconnectManager.saveConnectionInfo(state);
} else if (!state.isConnected) {
// 断开连接时,不清除保存的信息,以便下次自动重连
}
});

// 页面加载时尝试自动重连
window.addEventListener('load', () => {
// 延迟尝试重连,让页面有时间完全加载
setTimeout(() => {
reconnectManager.attemptReconnect();
}, 1000);
});

// 浏览器重新获得焦点时尝试重连
window.addEventListener('focus', () => {
// 检查当前是否未连接
const state = WalletConnectionManager.getState();
if (!state.isConnected) {
reconnectManager.attemptReconnect();
}
});

return {
walletConnector,
reconnectManager
};
}

3.2 网络切换管理

实现网络(链)切换功能,支持用户在不同区块链网络间切换:

// 网络切换管理器
class NetworkSwitchManager {
constructor(walletConnector) {
this.walletConnector = walletConnector;
this.supportedNetworks = {
1: { name: 'Ethereum Mainnet', symbol: 'ETH', rpcUrl: 'https://mainnet.infura.io/v3/' },
3: { name: 'Ropsten Testnet', symbol: 'ETH', rpcUrl: 'https://ropsten.infura.io/v3/' },
4: { name: 'Rinkeby Testnet', symbol: 'ETH', rpcUrl: 'https://rinkeby.infura.io/v3/' },
5: { name: 'Goerli Testnet', symbol: 'ETH', rpcUrl: 'https://goerli.infura.io/v3/' },
10: { name: 'Optimism', symbol: 'ETH', rpcUrl: 'https://mainnet.optimism.io' },
56: { name: 'Binance Smart Chain', symbol: 'BNB', rpcUrl: 'https://bsc-dataseed.binance.org/' },
137: { name: 'Polygon Mainnet', symbol: 'MATIC', rpcUrl: 'https://rpc-mainnet.maticvigil.com/' },
250: { name: 'Fantom Opera', symbol: 'FTM', rpcUrl: 'https://rpc.ftm.tools/' },
43114: { name: 'Avalanche C-Chain', symbol: 'AVAX', rpcUrl: 'https://api.avax.network/ext/bc/C/rpc' }
};
}

// 获取支持的网络列表
getSupportedNetworks() {
return Object.entries(this.supportedNetworks).map(([chainId, info]) => ({
chainId: parseInt(chainId),
...info
}));
}

// 获取网络信息
getNetworkInfo(chainId) {
return this.supportedNetworks[chainId];
}

// 检查网络是否支持
isNetworkSupported(chainId) {
return !!this.supportedNetworks[chainId];
}

// 切换网络
async switchNetwork(chainId, options = {}) {
const wallet = this.walletConnector.getActiveWallet();

if (!wallet) {
throw new Error('No active wallet connected');
}

if (!this.isNetworkSupported(chainId)) {
throw new Error(`Network with chainId ${chainId} is not supported`);
}

const networkInfo = this.getNetworkInfo(chainId);

try {
// 根据钱包类型使用不同的方法切换网络
const provider = WalletConnectionManager.getState().provider;

if (provider && provider.request) {
// 尝试使用EIP-3085方法切换网络
await provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x' + chainId.toString(16) }] // 转换为16进制
});
} else {
throw new Error('Wallet does not support network switching');
}

console.log(`Successfully switched to network: ${networkInfo.name}`);
return true;
} catch (error) {
// 处理用户拒绝切换网络的情况
if (error.code === 4001) {
throw new Error('User rejected network switch');
}

// 处理网络未添加的情况,尝试添加网络
if (error.code === 4902) {
return this.addNetwork(chainId, options);
}

throw new Error(`Failed to switch network: ${error.message}`);
}
}

// 添加网络到钱包
async addNetwork(chainId, options = {}) {
if (!this.isNetworkSupported(chainId)) {
throw new Error(`Network with chainId ${chainId} is not supported`);
}

const networkInfo = this.getNetworkInfo(chainId);
const provider = WalletConnectionManager.getState().provider;

if (!provider || !provider.request) {
throw new Error('Wallet does not support adding networks');
}

try {
// 使用EIP-3085方法添加网络
await provider.request({
method: 'wallet_addEthereumChain',
params: [
{
chainId: '0x' + chainId.toString(16), // 16进制链ID
chainName: networkInfo.name,
nativeCurrency: {
name: networkInfo.name.split(' ')[0],
symbol: networkInfo.symbol,
decimals: 18
},
rpcUrls: [
options.infuraId
? `${networkInfo.rpcUrl}${options.infuraId}`
: networkInfo.rpcUrl
],
blockExplorerUrls: options.blockExplorerUrls || []
}
]
});

console.log(`Successfully added network: ${networkInfo.name}`);
return true;
} catch (error) {
// 处理用户拒绝添加网络的情况
if (error.code === 4001) {
throw new Error('User rejected adding network');
}

throw new Error(`Failed to add network: ${error.message}`);
}
}

// 监听网络变化
startNetworkMonitoring(callback) {
const unsubscribe = WalletConnectionManager.subscribe(state => {
if (callback && state.chainId) {
callback(state.chainId, this.getNetworkInfo(state.chainId));
}
});

return unsubscribe;
}

// 验证当前网络是否是支持的网络
validateNetwork(requiredChainId) {
const currentChainId = WalletConnectionManager.getState().chainId;
return currentChainId === requiredChainId;
}

// 如果不是指定网络,则提示用户切换
async ensureNetwork(requiredChainId, options = {}) {
if (this.validateNetwork(requiredChainId)) {
return true;
}

if (options.autoSwitch !== false) {
try {
await this.switchNetwork(requiredChainId, options);
return true;
} catch (error) {
console.warn('Network switch failed:', error);

if (options.throwOnFailure) {
throw error;
}
}
}

return false;
}
}

// 使用示例:网络切换组件
function NetworkSwitcher({ requiredChainId = null, onNetworkChange }) {
const walletState = useWalletConnection();
const [networkManager, setNetworkManager] = useState(null);
const [isSwitching, setIsSwitching] = useState(false);

useEffect(() => {
// 初始化网络管理器
if (walletState && walletState.isConnected && !networkManager) {
const walletConnector = getActiveWalletConnector(); // 假设这个函数可以获取当前活跃的钱包连接器
const manager = new NetworkSwitchManager(walletConnector);
setNetworkManager(manager);

// 开始监听网络变化
const unsubscribe = manager.startNetworkMonitoring((chainId, networkInfo) => {
if (onNetworkChange) {
onNetworkChange(chainId, networkInfo);
}
});

return () => unsubscribe();
}
}, [walletState, networkManager, onNetworkChange]);

const handleNetworkSwitch = async (chainId) => {
if (!networkManager || isSwitching) {
return;
}

setIsSwitching(true);

try {
await networkManager.switchNetwork(chainId, {
infuraId: 'your-infura-id',
blockExplorerUrls: ['https://etherscan.io'] // 示例区块浏览器
});
} catch (error) {
console.error('Network switch failed:', error);
alert(`网络切换失败: ${error.message}`);
} finally {
setIsSwitching(false);
}
};

// 获取当前网络信息
const getCurrentNetworkInfo = () => {
if (!networkManager || !walletState.chainId) {
return null;
}

return networkManager.getNetworkInfo(walletState.chainId);
};

// 检查当前网络是否满足要求
const isRequiredNetwork = () => {
return requiredChainId === null || walletState.chainId === requiredChainId;
};

if (!walletState.isConnected) {
return null;
}

const currentNetworkInfo = getCurrentNetworkInfo();

return (
<div className="network-switcher">
{!isRequiredNetwork() && (
<div className="network-warning">
<span className="warning-icon">⚠️</span>
<span>需要切换到正确的网络</span>
</div>
)}

<div className="network-selector">
<span className="current-network">
{currentNetworkInfo ? currentNetworkInfo.name : `Chain ID: ${walletState.chainId}`}
</span>

<select
className="network-dropdown"
value={walletState.chainId || ''}
onChange={(e) => handleNetworkSwitch(parseInt(e.target.value))}
disabled={isSwitching || !networkManager}
>
{networkManager && networkManager.getSupportedNetworks().map(network => (
<option key={network.chainId} value={network.chainId}>
{network.name}
</option>
))}
</select>
</div>
</div>
);
}

3.3 账户信息获取与余额显示

实现用户账户信息获取和余额显示功能:

// 账户信息管理器
class AccountInfoManager {
constructor() {
this.web3 = null;
this.currentAddress = null;
}

// 初始化Web3实例
init(provider) {
if (provider) {
// 动态导入Web3库
import('web3').then(({ default: Web3 }) => {
this.web3 = new Web3(provider);
}).catch(error => {
console.error('Failed to initialize Web3:', error);
});
}
}

// 设置当前地址
setCurrentAddress(address) {
this.currentAddress = address;
}

// 获取账户余额
async getBalance(address = null) {
if (!this.web3) {
throw new Error('Web3 is not initialized');
}

const targetAddress = address || this.currentAddress;

if (!targetAddress) {
throw new Error('No address specified');
}

try {
const balanceWei = await this.web3.eth.getBalance(targetAddress);
const balanceEth = this.web3.utils.fromWei(balanceWei, 'ether');

return {
wei: balanceWei,
eth: balanceEth,
formatted: parseFloat(balanceEth).toFixed(4)
};
} catch (error) {
throw new Error(`Failed to get balance: ${error.message}`);
}
}

// 获取多资产余额
async getMultiAssetBalances(address = null, tokens = []) {
if (!this.web3) {
throw new Error('Web3 is not initialized');
}

const targetAddress = address || this.currentAddress;

if (!targetAddress) {
throw new Error('No address specified');
}

try {
const balances = {};

// 获取ETH余额
const ethBalance = await this.getBalance(targetAddress);
balances.ETH = ethBalance;

// 获取每个token的余额
for (const token of tokens) {
try {
const tokenBalance = await this.getTokenBalance(targetAddress, token.address, token.decimals);
balances[token.symbol] = tokenBalance;
} catch (error) {
console.warn(`Failed to get balance for ${token.symbol}:`, error);
balances[token.symbol] = { error: error.message };
}
}

return balances;
} catch (error) {
throw new Error(`Failed to get multi-asset balances: ${error.message}`);
}
}

// 获取ERC20代币余额
async getTokenBalance(address, tokenAddress, decimals = 18) {
if (!this.web3) {
throw new Error('Web3 is not initialized');
}

try {
// ERC20代币ABI(简化版,只包含balanceOf函数)
const tokenABI = [{
constant: true,
inputs: [{ name: '_owner', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: 'balance', type: 'uint256' }],
type: 'function'
}];

const tokenContract = new this.web3.eth.Contract(tokenABI, tokenAddress);
const balance = await tokenContract.methods.balanceOf(address).call();

// 转换为可读格式
const formattedBalance = this.web3.utils.fromWei(balance, 'ether');
const adjustedBalance = (parseFloat(formattedBalance) * Math.pow(10, 18 - decimals)).toFixed(4);

return {
raw: balance,
formatted: adjustedBalance,
decimals
};
} catch (error) {
throw new Error(`Failed to get token balance: ${error.message}`);
}
}

// 获取交易历史(简化版)
async getTransactionHistory(address = null, limit = 10) {
if (!this.web3) {
throw new Error('Web3 is not initialized');
}

const targetAddress = address || this.currentAddress;

if (!targetAddress) {
throw new Error('No address specified');
}

try {
// 注意:这个方法只能在全节点上使用,或者需要连接到第三方API
// 这里提供的是简化版实现,实际应用中应该使用如Etherscan API等服务

// 获取最新区块号
const latestBlock = await this.web3.eth.getBlockNumber();

// 从最新区块开始向前搜索
const transactions = [];
let currentBlock = latestBlock;

while (transactions.length < limit && currentBlock > 0) {
try {
const block = await this.web3.eth.getBlock(currentBlock, true); // true表示返回交易详情

if (block && block.transactions) {
// 筛选与目标地址相关的交易
const relevantTransactions = block.transactions.filter(tx =>
tx.from.toLowerCase() === targetAddress.toLowerCase() ||
tx.to?.toLowerCase() === targetAddress.toLowerCase()
);

// 添加到结果中
transactions.push(...relevantTransactions);

// 如果已达到限制,停止搜索
if (transactions.length >= limit) {
break;
}
}
} catch (error) {
console.warn(`Failed to get block ${currentBlock}:`, error);
}

currentBlock--;
}

// 排序并限制结果数量
return transactions
.sort((a, b) => b.blockNumber - a.blockNumber)
.slice(0, limit)
.map(tx => ({
hash: tx.hash,
from: tx.from,
to: tx.to,
value: this.web3.utils.fromWei(tx.value, 'ether'),
gasPrice: this.web3.utils.fromWei(tx.gasPrice, 'gwei'),
gas: tx.gas,
blockNumber: tx.blockNumber,
timestamp: new Date().toISOString() // 简化版,实际应该从区块中获取时间戳
}));
} catch (error) {
throw new Error(`Failed to get transaction history: ${error.message}`);
}
}

// 获取ENS名称(如果有)
async getENSName(address = null) {
if (!this.web3) {
throw new Error('Web3 is not initialized');
}

const targetAddress = address || this.currentAddress;

if (!targetAddress) {
throw new Error('No address specified');
}

try {
// 注意:这需要连接到支持ENS的节点或使用专门的ENS库
// 这里提供的是简化版实现

// 实际应用中,应该使用如@ensdomains/ensjs等专门的ENS库

// 简化版:始终返回null
return null;
} catch (error) {
console.warn(`Failed to get ENS name:`, error);
return null;
}
}

// 格式化地址显示
formatAddress(address, format = 'short') {
if (!address) {
return '';
}

switch (format) {
case 'short':
return `${address.slice(0, 6)}...${address.slice(-4)}`;
case 'medium':
return `${address.slice(0, 10)}...${address.slice(-8)}`;
case 'full':
return address;
default:
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
}
}

// 使用示例:账户信息显示组件
function AccountInfoDisplay() {
const walletState = useWalletConnection();
const [accountInfoManager, setAccountInfoManager] = useState(null);
const [balances, setBalances] = useState(null);
const [isLoading, setIsLoading] = useState(false);

useEffect(() => {
// 初始化账户信息管理器
if (walletState && walletState.isConnected && walletState.provider) {
const manager = new AccountInfoManager();
manager.init(walletState.provider);
manager.setCurrentAddress(walletState.address);
setAccountInfoManager(manager);
} else {
setBalances(null);
}
}, [walletState]);

// 获取账户余额
const fetchBalances = async () => {
if (!accountInfoManager) {
return;
}

setIsLoading(true);

try {
const tokenList = [
{ symbol: 'USDT', address: '0xdac17f958d2ee523a2206206994597c13d831ec7', decimals: 6 },
{ symbol: 'USDC', address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', decimals: 6 },
{ symbol: 'DAI', address: '0x6b175474e89094c44da98b954eedeac495271d0f', decimals: 18 }
];

const fetchedBalances = await accountInfoManager.getMultiAssetBalances(null, tokenList);
setBalances(fetchedBalances);
} catch (error) {
console.error('Failed to fetch balances:', error);
} finally {
setIsLoading(false);
}
};

// 组件挂载时获取余额
useEffect(() => {
if (accountInfoManager && !balances) {
fetchBalances();
}
}, [accountInfoManager, balances]);

if (!walletState.isConnected || !accountInfoManager) {
return (
<div className="account-info">
<p>请先连接钱包</p>
</div>
);
}

const formattedAddress = accountInfoManager.formatAddress(walletState.address);

return (
<div className="account-info">
<div className="account-header">
<div className="account-avatar">
{/* 可以使用如blockies等库生成地址头像 */}
<div className="address-avatar">{formattedAddress.slice(0, 2)}</div>
</div>
<div className="account-details">
<div className="account-address">
{formattedAddress}
<button
className="copy-button"
onClick={() => navigator.clipboard.writeText(walletState.address)}
title="复制地址"
>
📋
</button>
</div>
<div className="account-network">
{walletState.chainId === 1 ? 'Ethereum Mainnet' : `Chain ID: ${walletState.chainId}`}
</div>
</div>
</div>

<div className="balances-section">
<h4>资产余额</h4>
<button
className="refresh-button"
onClick={fetchBalances}
disabled={isLoading}
>
{isLoading ? '刷新中...' : '🔄 刷新'}
</button>

{isLoading ? (
<div className="loading-indicator">加载中...</div>
) : balances ? (
<div className="balances-list">
{Object.entries(balances).map(([symbol, balance]) => (
<div key={symbol} className="balance-item">
<span className="asset-symbol">{symbol}</span>
<span className="asset-balance">
{balance.error ? `Error: ${balance.error}` : balance.formatted}
</span>
</div>
))}
</div>
) : (
<div className="no-data">暂无余额数据</div>
)}
</div>
</div>
);
}

4. 钱包连接安全最佳实践

4.1 安全连接实现

实现安全的钱包连接流程是保护用户资产和数据安全的关键:

// 钱包连接安全管理器
class WalletConnectionSecurity {
constructor() {
this.allowedDomains = [];
this.maxConnectionAttempts = 5;
this.connectionAttempts = 0;
this.lastConnectionTime = null;
}

// 设置允许的域名列表
setAllowedDomains(domains) {
this.allowedDomains = domains;
}

// 验证域名是否允许连接钱包
isValidDomain() {
// 生产环境中,应该严格验证域名
if (process.env.NODE_ENV === 'production' && this.allowedDomains.length > 0) {
const currentDomain = window.location.hostname;
return this.allowedDomains.includes(currentDomain);
}

// 开发环境允许任何域名
return true;
}

// 检查连接尝试次数是否过多
checkConnectionAttempts() {
const now = Date.now();
const timeWindow = 60000; // 1分钟

// 重置时间窗口
if (!this.lastConnectionTime || now - this.lastConnectionTime > timeWindow) {
this.connectionAttempts = 0;
}

this.connectionAttempts++;
this.lastConnectionTime = now;

return this.connectionAttempts <= this.maxConnectionAttempts;
}

// 安全连接前检查
async preConnectionChecks() {
// 1. 检查域名是否有效
if (!this.isValidDomain()) {
throw new Error('Wallet connection is not allowed from this domain');
}

// 2. 检查连接尝试次数
if (!this.checkConnectionAttempts()) {
throw new Error('Too many connection attempts. Please try again later.');
}

// 3. 检查浏览器环境
if (!this.isSafeBrowserEnvironment()) {
throw new Error('Unsafe browser environment detected');
}

// 4. 检查是否存在钓鱼风险
if (await this.hasPhishingRisk()) {
throw new Error('Potential phishing risk detected');
}

return true;
}

// 检查浏览器环境是否安全
isSafeBrowserEnvironment() {
// 检查是否在iframe中
const isInIframe = window.self !== window.top;

// 检查是否启用了私密浏览模式
const isPrivateMode = this.detectPrivateMode();

// 在某些情况下,可能需要限制在iframe中的操作
if (isInIframe && process.env.NODE_ENV === 'production') {
console.warn('Wallet connection requested from within an iframe');
// 可以选择抛出错误或仅记录警告
}

return true; // 简化实现,实际应用中应根据需求进行更严格的检查
}

// 检测私密浏览模式
detectPrivateMode() {
// 这是一个简化的实现,实际检测私密模式可能需要更复杂的方法
try {
// 创建一个IndexedDB数据库,如果失败,可能是在私密模式下
const db = window.indexedDB.open('test');
db.onerror = () => {
return true;
};
} catch (e) {
return true;
}

return false;
}

// 检查是否存在钓鱼风险
async hasPhishingRisk() {
// 简化实现:实际应用中应该连接到钓鱼检测服务
// 例如,可以使用MetaMask的Phishing Detection API或其他第三方服务

return false; // 默认假设没有风险
}

// 记录连接事件(用于审计和监控)
logConnectionEvent(eventType, details = {}) {
const logEntry = {
timestamp: new Date().toISOString(),
eventType,
details: {
...details,
userAgent: navigator.userAgent,
referrer: document.referrer,
domain: window.location.hostname,
path: window.location.pathname
}
};

console.log('Wallet connection event:', logEntry);

// 实际应用中,可能需要将日志发送到服务器进行分析
}

// 验证钱包地址所有权(可选)
async verifyAddressOwnership(address, challenge = null) {
// 简化实现:要求用户签名一个挑战消息以验证所有权
// 这在高风险操作前特别有用

const provider = WalletConnectionManager.getState().provider;

if (!provider || !address) {
throw new Error('No wallet connected or address specified');
}

const message = challenge || `Please sign this message to verify ownership of ${address}\n\nTimestamp: ${Date.now()}`;

try {
// 使用eth_sign或personal_sign方法请求签名
const signature = await provider.request({
method: 'personal_sign',
params: [this.web3.utils.utf8ToHex(message), address]
});

// 验证签名
// 注意:实际应用中,应该在服务器端进行签名验证
// 这里仅提供客户端验证的简化示例

return {
success: true,
message,
signature
};
} catch (error) {
throw new Error(`Failed to verify address ownership: ${error.message}`);
}
}
}

// 使用示例:安全钱包连接流程
async function secureWalletConnection(walletType) {
const securityManager = new WalletConnectionSecurity();

try {
// 设置允许的域名(生产环境)
if (process.env.NODE_ENV === 'production') {
securityManager.setAllowedDomains(['yourapp.com', 'app.yourapp.com']);
}

// 连接前的安全检查
await securityManager.preConnectionChecks();

// 记录连接开始事件
securityManager.logConnectionEvent('connection_start', {
walletType
});

// 执行实际的钱包连接
const connector = await connectWalletExample(walletType);

// 记录连接成功事件
const walletState = WalletConnectionManager.getState();
securityManager.logConnectionEvent('connection_success', {
walletType,
address: walletState.address
});

// 可选:验证地址所有权
try {
await securityManager.verifyAddressOwnership(walletState.address);
securityManager.logConnectionEvent('ownership_verified', {
address: walletState.address
});
} catch (error) {
console.warn('Address ownership verification failed:', error);
securityManager.logConnectionEvent('ownership_verification_failed', {
address: walletState.address,
error: error.message
});
}

return connector;
} catch (error) {
// 记录连接失败事件
securityManager.logConnectionEvent('connection_failed', {
walletType,
error: error.message
});

throw error;
}
}

4.2 防止授权滥用

防止钱包授权被滥用是保护用户资产安全的重要措施:

// 授权管理器
class PermissionManager {
constructor() {
this.maxApprovalAmounts = {
'default': '1000000000000000000', // 默认1 ETH
'0xdac17f958d2ee523a2206206994597c13d831ec7': '1000000000', // USDT: 1,000,000.00
'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': '1000000000' // USDC: 1,000,000.00
};

this.lastApprovalTime = {};
this.approvalHistory = [];
}

// 设置最大授权金额
setMaxApprovalAmount(tokenAddress, amount) {
this.maxApprovalAmounts[tokenAddress.toLowerCase()] = amount;
}

// 获取推荐的最大授权金额
getRecommendedMaxApproval(tokenAddress, chainId = 1) {
tokenAddress = tokenAddress ? tokenAddress.toLowerCase() : 'default';

// 可以根据链ID调整推荐金额
const baseAmount = this.maxApprovalAmounts[tokenAddress] || this.maxApprovalAmounts.default;

return baseAmount;
}

// 检查授权金额是否安全
isApprovalAmountSafe(tokenAddress, amount, chainId = 1) {
const recommendedMax = this.getRecommendedMaxApproval(tokenAddress, chainId);

// 比较金额(注意:这里假设amount和recommendedMax都是字符串形式的整数)
// 实际应用中,应该使用大数处理库来避免精度问题
return BigInt(amount) <= BigInt(recommendedMax);
}

// 检查授权频率是否过高
isApprovalFrequencyTooHigh(tokenAddress, walletAddress) {
const key = `${walletAddress}:${tokenAddress}`;
const now = Date.now();
const timeWindow = 60000; // 1分钟
const maxAttempts = 3;

// 初始化记录(如果不存在)
if (!this.lastApprovalTime[key]) {
this.lastApprovalTime[key] = {
attempts: 0,
lastTime: now
};
return false;
}

const record = this.lastApprovalTime[key];

// 重置时间窗口
if (now - record.lastTime > timeWindow) {
record.attempts = 0;
}

record.attempts++;
record.lastTime = now;

return record.attempts > maxAttempts;
}

// 记录授权历史
recordApproval(approvalInfo) {
const record = {
id: `APPROVAL-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date().toISOString(),
walletAddress: approvalInfo.walletAddress,
tokenAddress: approvalInfo.tokenAddress,
spenderAddress: approvalInfo.spenderAddress,
amount: approvalInfo.amount,
amountFormatted: approvalInfo.amountFormatted,
tokenSymbol: approvalInfo.tokenSymbol,
isInfinite: approvalInfo.isInfinite || false,
status: approvalInfo.status || 'pending'
};

this.approvalHistory.push(record);

// 限制历史记录数量
if (this.approvalHistory.length > 100) {
this.approvalHistory.shift();
}

return record.id;
}

// 更新授权记录状态
updateApprovalStatus(recordId, status, details = {}) {
const record = this.approvalHistory.find(r => r.id === recordId);

if (record) {
record.status = status;
record.updatedAt = new Date().toISOString();
record.details = details;
}
}

// 获取授权历史
getApprovalHistory(walletAddress, limit = 20) {
return this.approvalHistory
.filter(record => record.walletAddress.toLowerCase() === walletAddress.toLowerCase())
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))
.slice(0, limit);
}

// 创建授权警告(如果需要)
createApprovalWarning(tokenAddress, amount, spenderAddress, chainId = 1) {
const warnings = [];

// 检查授权金额是否过大
if (!this.isApprovalAmountSafe(tokenAddress, amount, chainId)) {
warnings.push({
severity: 'high',
message: '授权金额过大,可能存在风险',
recommendation: `建议授权不超过推荐金额: ${this.getRecommendedMaxApproval(tokenAddress, chainId)}`
});
}

// 检查spender地址是否有风险(简化实现)
if (this.isHighRiskSpender(spenderAddress)) {
warnings.push({
severity: 'high',
message: '检测到高风险的授权接收方',
recommendation: '请确认你信任该合约地址'
});
}

// 检查是否是无限授权
if (this.isInfiniteApproval(amount)) {
warnings.push({
severity: 'medium',
message: '你正在进行无限授权',
recommendation: '建议使用最小必要授权金额'
});
}

return warnings;
}

// 检查是否是无限授权
isInfiniteApproval(amount) {
// 检查是否是常见的无限授权值
const infiniteValues = [
'115792089237316195423570985008687907853269984665640564039457584007913129639935', // 2^256-1
'999999999999999999999999999', // 常见的大额授权值
'0xffffffffffffffffffffffffffffffff' // 64位最大值
];

return infiniteValues.includes(amount.toString());
}

// 检查spender地址是否有风险(简化实现)
isHighRiskSpender(address) {
// 实际应用中,应该连接到威胁情报服务或维护一个黑名单
// 这里仅提供简化实现
const knownMaliciousAddresses = []; // 示例:空的黑名单

return knownMaliciousAddresses.includes(address.toLowerCase());
}

// 生成授权建议
generateApprovalRecommendation(tokenAddress, amount, spenderAddress, chainId = 1) {
const warnings = this.createApprovalWarning(tokenAddress, amount, spenderAddress, chainId);
const hasHighRisk = warnings.some(w => w.severity === 'high');

let recommendation = '安全授权';

if (hasHighRisk) {
recommendation = '不建议授权,存在高风险';
} else if (warnings.length > 0) {
recommendation = '谨慎授权,存在中等风险';
}

return {
recommendation,
warnings,
hasRisk: warnings.length > 0
};
}
}

// 使用示例:安全授权组件
function TokenApprovalComponent({ tokenInfo, spenderAddress, onApproval }) {
const [amount, setAmount] = useState('');
const [isInfinite, setIsInfinite] = useState(false);
const [isApproving, setIsApproving] = useState(false);
const [approvalWarnings, setApprovalWarnings] = useState([]);
const permissionManager = useMemo(() => new PermissionManager(), []);

const handleAmountChange = (e) => {
setAmount(e.target.value);
setIsInfinite(false);
};

const handleInfiniteToggle = () => {
setIsInfinite(!isInfinite);
};

const handleApprove = async () => {
if (isApproving || (!amount && !isInfinite)) {
return;
}

setIsApproving(true);

try {
const approvalAmount = isInfinite
? '115792089237316195423570985008687907853269984665640564039457584007913129639935' // 2^256-1
: amount;

// 检查授权建议
const recommendation = permissionManager.generateApprovalRecommendation(
tokenInfo.address,
approvalAmount,
spenderAddress,
tokenInfo.chainId
);

setApprovalWarnings(recommendation.warnings);

// 如果有高风险,要求用户确认
if (recommendation.hasRisk) {
const userConfirmed = window.confirm(
`检测到以下风险:\n${recommendation.warnings.map(w => `- ${w.message}`).join('\n')}\n\n是否继续?`
);

if (!userConfirmed) {
setIsApproving(false);
return;
}
}

// 记录授权历史
const recordId = permissionManager.recordApproval({
walletAddress: WalletConnectionManager.getState().address,
tokenAddress: tokenInfo.address,
spenderAddress: spenderAddress,
amount: approvalAmount,
amountFormatted: isInfinite ? '无限' : amount,
tokenSymbol: tokenInfo.symbol,
isInfinite: isInfinite
});

// 执行实际的授权操作
if (onApproval) {
await onApproval(approvalAmount, isInfinite);
}

// 更新授权记录状态
permissionManager.updateApprovalStatus(recordId, 'approved');

} catch (error) {
console.error('Token approval failed:', error);
alert(`授权失败: ${error.message}`);
} finally {
setIsApproving(false);
}
};

return (
<div className="token-approval-component">
<h3>授权 {tokenInfo.symbol}{spenderAddress}</h3>

<div className="approval-form">
<input
type="number"
placeholder="输入授权金额"
value={amount}
onChange={handleAmountChange}
disabled={isInfinite || isApproving}
/>

<label className="infinite-approval-checkbox">
<input
type="checkbox"
checked={isInfinite}
onChange={handleInfiniteToggle}
disabled={isApproving}
/>
无限授权
</label>

<button
onClick={handleApprove}
disabled={isApproving || (!amount && !isInfinite)}
className="approve-button"
>
{isApproving ? '授权中...' : '确认授权'}
</button>
</div>

{approvalWarnings.length > 0 && (
<div className="approval-warnings">
<h4>⚠️ 安全警告</h4>
<ul>
{approvalWarnings.map((warning, index) => (
<li key={index} className={`warning-${warning.severity}`}>
{warning.message}
</li>
))}
</ul>
</div>
)}
</div>
);
}

## 5. 钱包连接错误处理中心

### 5.1 错误处理分类

在钱包连接过程中,常见的错误类型包括:

- **连接错误**:钱包无法连接
- **授权错误**:用户拒绝授权或授权失败
- **网络错误**:区块链网络不可用或响应超时
- **链不兼容错误**:用户使用的链与应用要求的链不匹配
- **钱包不支持错误**:用户使用的钱包不支持某些功能

### 5.2 错误处理中心实现

```javascript
// 钱包错误处理中心
class WalletErrorHandler {
// 错误类型映射
static ERROR_TYPES = {
CONNECTION_ERROR: 'CONNECTION_ERROR',
AUTH_ERROR: 'AUTH_ERROR',
NETWORK_ERROR: 'NETWORK_ERROR',
CHAIN_INCOMPATIBLE_ERROR: 'CHAIN_INCOMPATIBLE_ERROR',
WALLET_NOT_SUPPORTED_ERROR: 'WALLET_NOT_SUPPORTED_ERROR',
UNKNOWN_ERROR: 'UNKNOWN_ERROR'
};

// 错误消息映射
static ERROR_MESSAGES = {
CONNECTION_ERROR: '钱包连接失败,请检查钱包是否已安装并正确配置',
AUTH_ERROR: '授权失败,请确保您已在钱包中确认操作',
NETWORK_ERROR: '网络连接超时,请检查您的网络连接并稍后再试',
CHAIN_INCOMPATIBLE_ERROR: '当前网络不兼容,请切换到正确的区块链网络',
WALLET_NOT_SUPPORTED_ERROR: '您的钱包不支持此功能,请尝试使用其他钱包',
UNKNOWN_ERROR: '发生未知错误,请稍后再试'
};

// 错误日志记录
static logError(error, context = {}) {
console.error('Wallet Error:', {
error,
context,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent
});
}

// 错误类型识别
static identifyErrorType(error) {
const errorMessage = (error.message || '').toLowerCase();

if (errorMessage.includes('connection') || errorMessage.includes('connect')) {
return this.ERROR_TYPES.CONNECTION_ERROR;
}

if (errorMessage.includes('auth') || errorMessage.includes('reject') || errorMessage.includes('cancel')) {
return this.ERROR_TYPES.AUTH_ERROR;
}

if (errorMessage.includes('network') || errorMessage.includes('timeout')) {
return this.ERROR_TYPES.NETWORK_ERROR;
}

if (errorMessage.includes('chain') || errorMessage.includes('network')) {
return this.ERROR_TYPES.CHAIN_INCOMPATIBLE_ERROR;
}

if (errorMessage.includes('support') || errorMessage.includes('unsupported')) {
return this.ERROR_TYPES.WALLET_NOT_SUPPORTED_ERROR;
}

return this.ERROR_TYPES.UNKNOWN_ERROR;
}

// 获取友好的错误消息
static getFriendlyErrorMessage(error) {
const errorType = this.identifyErrorType(error);
return this.ERROR_MESSAGES[errorType] || this.ERROR_MESSAGES.UNKNOWN_ERROR;
}

// 处理钱包连接错误
static handleConnectionError(error, context = {}) {
this.logError(error, { ...context, action: 'wallet_connect' });

// 尝试自动修复
if (this.canAutoFixError(error)) {
return this.autoFixError(error, context);
}

const friendlyMessage = this.getFriendlyErrorMessage(error);

// 显示用户友好的错误提示
this.displayError(friendlyMessage);

return {
success: false,
errorType: this.identifyErrorType(error),
message: friendlyMessage
};
}

// 判断错误是否可以自动修复
static canAutoFixError(error) {
const errorType = this.identifyErrorType(error);
// 目前只支持自动修复链不兼容错误
return errorType === this.ERROR_TYPES.CHAIN_INCOMPATIBLE_ERROR;
}

// 自动修复错误
static async autoFixError(error, context = {}) {
const errorType = this.identifyErrorType(error);

if (errorType === this.ERROR_TYPES.CHAIN_INCOMPATIBLE_ERROR && context.targetChainId) {
try {
// 尝试让用户切换到目标链
await WalletConnectionManager.switchChain(context.targetChainId);
return {
success: true,
message: '网络已成功切换'
};
} catch (switchError) {
// 如果用户拒绝切换,返回原始错误
return this.handleConnectionError(switchError, context);
}
}

return {
success: false,
message: '无法自动修复此错误'
};
}

// 显示错误提示
static displayError(message) {
// 可以替换为项目中使用的UI组件库的提示组件
if (typeof window !== 'undefined' && window.alert) {
window.alert(message);
}
}

// 提供错误恢复建议
static getRecoverySuggestions(error) {
const errorType = this.identifyErrorType(error);

const suggestions = {
[this.ERROR_TYPES.CONNECTION_ERROR]: [
'确保您的钱包扩展已正确安装',
'刷新页面后重试',
'尝试使用其他浏览器',
'检查钱包是否需要更新'
],
[this.ERROR_TYPES.AUTH_ERROR]: [
'确保您在钱包弹出窗口中点击了确认',
'检查钱包余额是否充足',
'重试操作'
],
[this.ERROR_TYPES.NETWORK_ERROR]: [
'检查您的网络连接',
'等待几分钟后重试',
'尝试切换到其他网络'
],
[this.ERROR_TYPES.CHAIN_INCOMPATIBLE_ERROR]: [
'在钱包中切换到正确的网络',
'查看应用支持的网络列表'
],
[this.ERROR_TYPES.WALLET_NOT_SUPPORTED_ERROR]: [
'尝试使用MetaMask等主流钱包',
'查看应用支持的钱包列表'
]
};

return suggestions[errorType] || ['联系客服获取帮助', '稍后再试'];
}
}

// 使用示例:在钱包连接过程中处理错误
async function connectWalletWithErrorHandling(walletType) {
try {
const result = await WalletConnectionManager.connectWallet(walletType);
return { success: true, data: result };
} catch (error) {
// 使用错误处理中心处理错误
const errorResult = WalletErrorHandler.handleConnectionError(error, {
walletType,
action: 'connect_wallet'
});

// 获取恢复建议
const recoverySuggestions = WalletErrorHandler.getRecoverySuggestions(error);

return {
...errorResult,
recoverySuggestions
};
}
}

## 6. 钱包连接性能优化

### 6.1 连接性能优化器

```javascript
// 钱包连接性能优化器
class WalletConnectionOptimizer {
// 连接缓存
static connectionCache = new Map();

// 连接超时设置
static connectionTimeout = 15000; // 15秒

// 最大重试次数
static maxRetries = 2;

// 优化连接尝试
static async optimizeConnection(walletType, options = {}) {
const cacheKey = `${walletType}_${options.chainId || 'default'}`;

// 检查缓存中是否有可用连接
if (this.connectionCache.has(cacheKey)) {
const cachedConnection = this.connectionCache.get(cacheKey);

// 验证缓存连接是否仍然有效
if (await this.validateCachedConnection(cachedConnection)) {
console.log('Using cached wallet connection');
return cachedConnection;
}

// 缓存连接无效,移除缓存
this.connectionCache.delete(cacheKey);
}

// 使用超时和重试机制优化连接尝试
const connectionResult = await this.attemptConnectionWithTimeoutAndRetry(
walletType,
options
);

// 缓存成功的连接
if (connectionResult.success) {
this.connectionCache.set(cacheKey, connectionResult);
// 设置缓存过期时间
this.setCacheExpiry(cacheKey);
}

return connectionResult;
}

// 验证缓存连接
static async validateCachedConnection(cachedConnection) {
try {
// 尝试获取账户信息来验证连接
const { provider } = cachedConnection;
if (provider && provider.request) {
const accounts = await provider.request({ method: 'eth_accounts' });
return accounts && accounts.length > 0;
}
return false;
} catch (error) {
console.error('Failed to validate cached connection:', error);
return false;
}
}

// 带超时和重试的连接尝试
static async attemptConnectionWithTimeoutAndRetry(walletType, options = {}) {
let lastError;

for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
if (attempt > 0) {
// 指数退避策略
const waitTime = Math.min(1000 * Math.pow(2, attempt - 1), 5000);
console.log(`Connection attempt ${attempt}/${this.maxRetries}, waiting ${waitTime}ms before retry`);
await new Promise(resolve => setTimeout(resolve, waitTime));
}

try {
// 使用Promise.race实现超时控制
const connectionPromise = WalletConnectionManager.connectWallet(walletType, options);
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Connection timeout')), this.connectionTimeout)
);

const result = await Promise.race([connectionPromise, timeoutPromise]);
return { success: true, ...result };
} catch (error) {
console.error(`Connection attempt ${attempt} failed:`, error);
lastError = error;
}
}

// 所有重试都失败
return {
success: false,
error: lastError,
message: `Failed to connect after ${this.maxRetries + 1} attempts`
};
}

// 设置缓存过期时间
static setCacheExpiry(cacheKey) {
// 缓存5分钟后过期
setTimeout(() => {
this.connectionCache.delete(cacheKey);
console.log(`Wallet connection cache expired for key: ${cacheKey}`);
}, 5 * 60 * 1000);
}

// 清除所有缓存
static clearCache() {
this.connectionCache.clear();
}

// 获取连接性能统计
static getPerformanceStats() {
// 这里可以实现性能统计逻辑
// 例如记录连接成功率、平均连接时间等
return {
cacheHits: this.connectionCache.size,
totalAttempts: this.totalAttempts || 0,
successfulAttempts: this.successfulAttempts || 0
};
}
}

// 使用示例:优化后的钱包连接
async function connectWalletOptimized(walletType, options = {}) {
const startTime = performance.now();

const result = await WalletConnectionOptimizer.optimizeConnection(walletType, options);

const endTime = performance.now();
console.log(`Wallet connection took ${(endTime - startTime).toFixed(2)}ms`);

return result;
}

### 6.2 钱包状态同步优化

```javascript
// 钱包状态同步优化器
class WalletStateSynchronizer {
// 状态同步队列
static syncQueue = [];

// 是否正在处理同步
static isProcessing = false;

// 节流延迟(毫秒)
static throttleDelay = 100;

// 最后一次同步时间
static lastSyncTime = 0;

// 批量处理同步请求
static enqueueSync(action, params = {}) {
return new Promise((resolve, reject) => {
this.syncQueue.push({ action, params, resolve, reject });
this.processQueue();
});
}

// 处理同步队列
static async processQueue() {
if (this.isProcessing) return;

const now = Date.now();
const timeSinceLastSync = now - this.lastSyncTime;

// 实现节流,避免频繁同步
if (timeSinceLastSync < this.throttleDelay) {
setTimeout(() => this.processQueue(), this.throttleDelay - timeSinceLastSync);
return;
}

this.isProcessing = true;
this.lastSyncTime = now;

try {
while (this.syncQueue.length > 0) {
const { action, params, resolve, reject } = this.syncQueue.shift();

try {
const result = await this.executeSyncAction(action, params);
resolve(result);
} catch (error) {
reject(error);
}
}
} finally {
this.isProcessing = false;

// 如果在处理过程中有新的请求入队,继续处理
if (this.syncQueue.length > 0) {
this.processQueue();
}
}
}

// 执行同步操作
static async executeSyncAction(action, params) {
const walletState = WalletConnectionManager.getState();

if (!walletState.isConnected || !walletState.provider) {
throw new Error('Wallet is not connected');
}

switch (action) {
case 'syncBalance':
return this.syncBalance(params);
case 'syncChainId':
return this.syncChainId();
case 'syncNetworkStatus':
return this.syncNetworkStatus();
case 'fullSync':
return this.performFullSync(params);
default:
throw new Error(`Unknown sync action: ${action}`);
}
}

// 同步余额
static async syncBalance({ address = null } = {}) {
const walletState = WalletConnectionManager.getState();
const targetAddress = address || walletState.address;

if (!targetAddress) {
throw new Error('No address provided for balance sync');
}

try {
const balance = await walletState.provider.request({
method: 'eth_getBalance',
params: [targetAddress, 'latest']
});

const balanceInEth = ethers.utils.formatEther(balance);

WalletConnectionManager.updateState({ balance: balanceInEth });

return { success: true, balance: balanceInEth };
} catch (error) {
console.error('Failed to sync balance:', error);
throw error;
}
}

// 同步链ID
static async syncChainId() {
const walletState = WalletConnectionManager.getState();

try {
const chainId = await walletState.provider.request({
method: 'eth_chainId'
});

const numericChainId = parseInt(chainId, 16);

WalletConnectionManager.updateState({ chainId: numericChainId });

return { success: true, chainId: numericChainId };
} catch (error) {
console.error('Failed to sync chain ID:', error);
throw error;
}
}

// 同步网络状态
static async syncNetworkStatus() {
try {
// 检查网络是否可用
const blockNumber = await WalletConnectionManager.getProvider().request({
method: 'eth_blockNumber'
});

return {
success: true,
isNetworkAvailable: true,
blockNumber: parseInt(blockNumber, 16)
};
} catch (error) {
console.error('Network is unavailable:', error);
return {
success: false,
isNetworkAvailable: false,
error: error.message
};
}
}

// 执行完全同步
static async performFullSync(options = {}) {
try {
// 并行执行多个同步操作以提高性能
const [balanceResult, chainIdResult, networkStatusResult] = await Promise.all([
this.syncBalance(options),
this.syncChainId(),
this.syncNetworkStatus()
]);

return {
success: true,
balance: balanceResult.balance,
chainId: chainIdResult.chainId,
networkStatus: networkStatusResult
};
} catch (error) {
console.error('Full sync failed:', error);
throw error;
}
}

// 设置节流延迟
static setThrottleDelay(delay) {
this.throttleDelay = delay;
}
}

// 使用示例:优化的钱包状态同步
function setupOptimizedWalletSync() {
// 监听钱包连接状态变化
WalletConnectionManager.subscribe((newState) => {
if (newState.isConnected && !newState.isConnecting) {
// 钱包连接成功后执行完全同步
WalletStateSynchronizer.enqueueSync('fullSync')
.then(syncResult => {
console.log('Wallet state fully synced:', syncResult);
})
.catch(error => {
console.error('Wallet sync failed:', error);
});
}
});

// 定期同步余额(例如每分钟)
setInterval(() => {
const walletState = WalletConnectionManager.getState();
if (walletState.isConnected) {
WalletStateSynchronizer.enqueueSync('syncBalance');
}
}, 60000);

// 监听网络变化事件
if (typeof window !== 'undefined' && window.ethereum) {
window.ethereum.on('chainChanged', () => {
WalletStateSynchronizer.enqueueSync('fullSync');
});

window.ethereum.on('accountsChanged', () => {
WalletStateSynchronizer.enqueueSync('fullSync');
});
}
}

## 7. 总结

有效的钱包连接管理是构建高质量Web3应用的关键。通过本文介绍的技术和最佳实践,开发者可以实现以下目标:

1. **提供流畅的用户体验**:通过多钱包支持、自动重连、状态同步等功能,让用户与区块链的交互更加便捷。

2. **增强应用安全性**:通过权限管理、安全连接实现、授权风险检测等机制,保护用户资产安全。

3. **提升应用性能**:通过连接优化、状态同步优化、错误处理等技术,提高应用的响应速度和稳定性。

4. **降低开发复杂度**:通过本文提供的可复用组件和工具类,简化钱包连接相关功能的开发。

在实际开发中,开发者应根据项目需求和用户群体特点,选择合适的钱包连接方案和安全策略,不断优化和完善钱包连接管理机制,为用户提供安全、可靠、流畅的Web3应用体验。