跳到主要内容

WalletConnect协议详解

WalletConnect是Web3生态系统中连接DApp与移动钱包的重要协议,它通过QR码扫描或深度链接实现安全的跨设备通信。本文将深入解析WalletConnect协议的工作原理、实现方法和最佳实践,帮助开发者构建更安全、更可靠的多设备钱包集成功能。

1. WalletConnect概述

1.1 什么是WalletConnect

WalletConnect是一个开源协议,允许用户在桌面浏览器中的DApp与移动设备上的钱包应用之间建立安全连接。它解决了Web3应用在移动设备上的用户体验问题,使用户可以在任何设备上安全地进行区块链交易。

1.2 WalletConnect的主要特点

  • 跨平台兼容:支持桌面和移动设备,以及各种钱包应用
  • 安全通信:使用端到端加密保护所有交易和签名请求
  • 开放标准:开源协议,任何钱包或DApp都可以实现
  • 无需安装插件:不需要在浏览器中安装额外的插件或扩展
  • 支持多链:兼容各种区块链网络

1.3 WalletConnect的应用场景

  • 桌面DApp与移动钱包的连接:用户在桌面浏览器中浏览DApp,使用手机钱包进行签名和交易
  • 多设备签名:在一个设备上发起交易,在另一个更安全的设备上进行签名
  • 硬件钱包集成:通过移动设备桥接桌面DApp与硬件钱包
  • 企业级安全解决方案:对于需要额外安全层的企业应用

2. WalletConnect架构

2.1 通信架构

WalletConnect采用基于Relay Server的通信架构,主要包括三个核心组件:

  1. DApp客户端:集成WalletConnect的Web应用
  2. 钱包客户端:支持WalletConnect的钱包应用
  3. Relay Server:中继服务器,负责在DApp和钱包之间传递加密消息

WalletConnect架构示意图

2.2 连接流程

WalletConnect的连接流程主要包括以下几个步骤:

  1. DApp发起连接请求:生成唯一的连接URI,包含协议版本、桥梁服务器URL、会话密钥和会话ID
  2. 显示连接二维码:DApp将连接URI编码为QR码显示给用户
  3. 钱包扫描二维码:用户使用钱包应用扫描QR码,获取连接URI
  4. 钱包验证连接:钱包解析URI并验证连接信息
  5. 建立加密通信:DApp和钱包通过Relay Server建立加密通信通道
  6. 用户授权连接:用户在钱包中确认连接请求
  7. 连接建立成功:DApp收到钱包的确认,连接正式建立

2.3 数据加密机制

WalletConnect使用现代加密技术保护通信安全:

  • 椭圆曲线加密:用于密钥生成和签名验证
  • AES-256-GCM加密:用于加密所有通过Relay Server传输的消息
  • 临时密钥交换:每次会话生成新的密钥对,防止重放攻击
  • 消息完整性验证:确保消息在传输过程中没有被篡改

3. WalletConnect核心概念

3.1 URI结构

WalletConnect URI采用标准化格式,包含建立连接所需的所有信息:

wc:[session-id]@[version]?bridge=[bridge-url]&key=[symmetric-key]

其中:

  • session-id:随机生成的256位会话ID
  • version:WalletConnect协议版本
  • bridge-url:中继服务器URL
  • symmetric-key:用于加密通信的对称密钥

3.2 会话管理

会话是WalletConnect的核心概念,表示DApp和钱包之间的连接状态。每个会话包含以下信息:

  • 会话ID:唯一标识会话的字符串
  • 链ID:当前连接的区块链网络ID
  • 账户地址:用户授权的账户地址
  • 会话密钥:用于加密通信的对称密钥
  • 过期时间:会话的过期时间
  • 已授权方法:钱包授权DApp调用的RPC方法列表

3.3 JSON-RPC方法

WalletConnect使用JSON-RPC 2.0协议进行通信,支持以下主要方法:

  • eth_accounts:获取用户账户地址
  • eth_chainId:获取当前链ID
  • eth_sign:对消息进行签名
  • eth_signTypedData:对结构化数据进行签名
  • eth_sendTransaction:发送交易
  • wallet_switchEthereumChain:切换区块链网络
  • wallet_addEthereumChain:添加新的区块链网络

4. WalletConnect实现

4.1 在DApp中集成WalletConnect

下面是使用WalletConnect v2在DApp中实现钱包连接的示例代码:

// WalletConnect DApp连接管理器
class WalletConnectManager {
constructor() {
this.walletConnectClient = null;
this.session = null;
this.connector = null;
this.connectedWallets = new Map();
}

// 初始化WalletConnect客户端
async initWalletConnect() {
try {
// 动态导入WalletConnect客户端库
const { WalletConnectClient } = await import('@walletconnect/client');
const { EthereumProvider } = await import('@walletconnect/ethereum-provider');

// 创建以太坊Provider
this.provider = await EthereumProvider.init({
projectId: 'YOUR_PROJECT_ID', // 从WalletConnect云获取
chains: [1, 5, 137], // 支持的链ID列表 (Ethereum, Goerli, Polygon)
showQrModal: true, // 显示内置的QR码模态框
methods: [
'eth_accounts',
'eth_chainId',
'eth_sign',
'eth_signTypedData',
'eth_sendTransaction',
'wallet_switchEthereumChain',
'wallet_addEthereumChain'
], // 请求的方法权限
events: ['accountsChanged', 'chainChanged'] // 监听的事件
});

// 监听连接事件
this.setupEventListeners();

console.log('WalletConnect provider initialized');
return this.provider;
} catch (error) {
console.error('Failed to initialize WalletConnect:', error);
throw error;
}
}

// 设置事件监听器
setupEventListeners() {
if (!this.provider) return;

// 连接成功事件
this.provider.on('connect', (connectInfo) => {
console.log('WalletConnect connected:', connectInfo);
this.handleConnect(connectInfo);
});

// 断开连接事件
this.provider.on('disconnect', (disconnectInfo) => {
console.log('WalletConnect disconnected:', disconnectInfo);
this.handleDisconnect(disconnectInfo);
});

// 账户变更事件
this.provider.on('accountsChanged', (accounts) => {
console.log('WalletConnect accounts changed:', accounts);
this.handleAccountsChanged(accounts);
});

// 链变更事件
this.provider.on('chainChanged', (chainId) => {
console.log('WalletConnect chain changed:', chainId);
this.handleChainChanged(chainId);
});

// 会话更新事件
this.provider.on('session_update', (session) => {
console.log('WalletConnect session updated:', session);
this.session = session;
});
}

// 处理连接成功
async handleConnect(connectInfo) {
try {
// 获取账户和链信息
const accounts = await this.provider.request({ method: 'eth_accounts' });
const chainId = await this.provider.request({ method: 'eth_chainId' });

this.session = {
accounts,
chainId: parseInt(chainId, 16),
...connectInfo
};

// 保存连接的钱包信息
if (accounts && accounts.length > 0) {
this.connectedWallets.set(accounts[0], {
address: accounts[0],
chainId: parseInt(chainId, 16),
connectedAt: Date.now()
});
}

// 触发自定义连接成功事件
this.emit('wallet_connected', this.session);
} catch (error) {
console.error('Error handling WalletConnect connection:', error);
}
}

// 处理断开连接
handleDisconnect(disconnectInfo) {
// 清除会话信息
this.session = null;

// 清除所有连接的钱包
this.connectedWallets.clear();

// 触发自定义断开连接事件
this.emit('wallet_disconnected', disconnectInfo);
}

// 处理账户变更
handleAccountsChanged(accounts) {
if (!accounts || accounts.length === 0) {
// 没有账户,视为断开连接
this.handleDisconnect({ reason: 'No accounts available' });
return;
}

// 更新会话账户信息
if (this.session) {
this.session.accounts = accounts;
}

// 触发自定义账户变更事件
this.emit('accounts_changed', accounts);
}

// 处理链变更
handleChainChanged(chainId) {
const numericChainId = parseInt(chainId, 16);

// 更新会话链信息
if (this.session) {
this.session.chainId = numericChainId;
}

// 更新所有连接的钱包的链ID
this.connectedWallets.forEach((walletInfo, address) => {
this.connectedWallets.set(address, {
...walletInfo,
chainId: numericChainId
});
});

// 触发自定义链变更事件
this.emit('chain_changed', numericChainId);
}

// 连接钱包
async connectWallet() {
try {
if (!this.provider) {
await this.initWalletConnect();
}

// 检查是否已经连接
if (this.provider.connected) {
console.log('Already connected to WalletConnect');
return this.session;
}

// 触发连接流程
await this.provider.connect();

return this.session;
} catch (error) {
console.error('Failed to connect WalletConnect:', error);
throw error;
}
}

// 断开连接
async disconnectWallet() {
try {
if (this.provider && this.provider.connected) {
await this.provider.disconnect();
}
return true;
} catch (error) {
console.error('Failed to disconnect WalletConnect:', error);
throw error;
}
}

// 发送交易
async sendTransaction(transaction) {
try {
if (!this.provider || !this.provider.connected) {
throw new Error('WalletConnect not connected');
}

// 确保交易包含必要的字段
const tx = {
from: this.session.accounts[0],
...transaction
};

// 发送交易请求
const txHash = await this.provider.request({
method: 'eth_sendTransaction',
params: [tx]
});

return txHash;
} catch (error) {
console.error('Failed to send transaction via WalletConnect:', error);
throw error;
}
}

// 签名消息
async signMessage(message) {
try {
if (!this.provider || !this.provider.connected) {
throw new Error('WalletConnect not connected');
}

// 发送签名请求
const signature = await this.provider.request({
method: 'eth_sign',
params: [this.session.accounts[0], ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message))]
});

return signature;
} catch (error) {
console.error('Failed to sign message via WalletConnect:', error);
throw error;
}
}

// 签名TypedData (EIP-712)
async signTypedData(typedData) {
try {
if (!this.provider || !this.provider.connected) {
throw new Error('WalletConnect not connected');
}

// 发送签名请求
const signature = await this.provider.request({
method: 'eth_signTypedData',
params: [this.session.accounts[0], JSON.stringify(typedData)]
});

return signature;
} catch (error) {
console.error('Failed to sign typed data via WalletConnect:', error);
throw error;
}
}

// 切换链
async switchChain(chainId) {
try {
if (!this.provider || !this.provider.connected) {
throw new Error('WalletConnect not connected');
}

// 发送切换链请求
await this.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: ethers.utils.hexlify(chainId) }]
});

return true;
} catch (error) {
// 如果链不存在,尝试添加链
if (error.code === 4902) {
return this.addChain(chainId);
}

console.error('Failed to switch chain via WalletConnect:', error);
throw error;
}
}

// 添加链
async addChain(chainId) {
try {
if (!this.provider || !this.provider.connected) {
throw new Error('WalletConnect not connected');
}

// 获取链的配置信息
const chainConfig = this.getChainConfig(chainId);
if (!chainConfig) {
throw new Error(`Chain configuration not found for chainId: ${chainId}`);
}

// 发送添加链请求
await this.provider.request({
method: 'wallet_addEthereumChain',
params: [chainConfig]
});

return true;
} catch (error) {
console.error('Failed to add chain via WalletConnect:', error);
throw error;
}
}

// 获取链配置
getChainConfig(chainId) {
const chains = {
1: {
chainId: '0x1',
chainName: 'Ethereum Mainnet',
rpcUrls: ['https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID'],
blockExplorerUrls: ['https://etherscan.io'],
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }
},
5: {
chainId: '0x5',
chainName: 'Goerli Testnet',
rpcUrls: ['https://goerli.infura.io/v3/YOUR_INFURA_PROJECT_ID'],
blockExplorerUrls: ['https://goerli.etherscan.io'],
nativeCurrency: { name: 'Goerli Ether', symbol: 'ETH', decimals: 18 }
},
137: {
chainId: '0x89',
chainName: 'Polygon Mainnet',
rpcUrls: ['https://polygon-rpc.com'],
blockExplorerUrls: ['https://polygonscan.com'],
nativeCurrency: { name: 'Polygon Matic', symbol: 'MATIC', decimals: 18 }
}
// 可以添加更多链配置
};

return chains[chainId];
}

// 事件发射器(简化实现)
emit(event, data) {
// 实际实现应使用完整的事件发射器库
const eventHandlers = this.eventHandlers || {};
if (eventHandlers[event]) {
eventHandlers[event].forEach(handler => {
try {
handler(data);
} catch (error) {
console.error(`Error in event handler for ${event}:`, error);
}
});
}
}

// 监听事件
on(event, handler) {
this.eventHandlers = this.eventHandlers || {};
if (!this.eventHandlers[event]) {
this.eventHandlers[event] = [];
}
this.eventHandlers[event].push(handler);

// 返回取消监听函数
return () => {
this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
};
}

// 获取连接状态
getConnectionState() {
return {
isConnected: this.provider && this.provider.connected,
session: this.session,
connectedWallets: Array.from(this.connectedWallets.values())
};
}
}

// 使用示例
const walletConnectManager = new WalletConnectManager();

// 连接钱包
async function connectWithWalletConnect() {
try {
const session = await walletConnectManager.connectWallet();
console.log('WalletConnect连接成功:', session);

// 监听账户变化
walletConnectManager.on('accounts_changed', (accounts) => {
console.log('账户已变更:', accounts);
// 更新UI以显示新账户
});

// 监听链变化
walletConnectManager.on('chain_changed', (chainId) => {
console.log('链已变更:', chainId);
// 更新UI以显示新链
});

return session;
} catch (error) {
console.error('WalletConnect连接失败:', error);
throw error;
}
}

// 发送交易示例
async function exampleSendTransaction() {
try {
const txHash = await walletConnectManager.sendTransaction({
to: '0xRecipientAddress',
value: ethers.utils.parseEther('0.01'), // 0.01 ETH
gasLimit: '21000',
gasPrice: ethers.utils.parseUnits('50', 'gwei')
});

console.log('交易已发送,哈希:', txHash);
return txHash;
} catch (error) {
console.error('交易发送失败:', error);
throw error;
}
}

4.2 自定义QR码界面

有时,您可能需要自定义WalletConnect的QR码界面,而不是使用默认的模态框:

// 自定义WalletConnect QR码显示组件
class CustomWalletConnectQRCode {
constructor(options = {}) {
this.containerId = options.containerId || 'walletconnect-qr-container';
this.size = options.size || 250;
this.onConnectionStatus = options.onConnectionStatus || (() => {});
this.walletConnectManager = null;
}

// 初始化并显示QR码
async initAndShowQRCode() {
try {
// 动态导入QR码生成库
const QRCode = (await import('qrcode')).default;

// 初始化WalletConnect,但不使用内置的QR码模态框
this.walletConnectManager = new WalletConnectManager();

// 监听连接状态
this.walletConnectManager.on('wallet_connected', (session) => {
this.hideQRCode();
this.onConnectionStatus({ connected: true, session });
});

this.walletConnectManager.on('wallet_disconnected', (reason) => {
this.onConnectionStatus({ connected: false, reason });
});

// 初始化WalletConnect provider但不自动连接
const provider = await this.walletConnectManager.initWalletConnect();

// 创建连接URI
const uri = await provider.connect({ showQrModal: false });

// 生成并显示QR码
await this.generateQRCode(uri);

console.log('Custom WalletConnect QR code displayed');

return uri;
} catch (error) {
console.error('Failed to initialize custom WalletConnect QR code:', error);
throw error;
}
}

// 生成QR码
async generateQRCode(uri) {
try {
const QRCode = (await import('qrcode')).default;
const container = document.getElementById(this.containerId);

if (!container) {
throw new Error(`Container with id ${this.containerId} not found`);
}

// 清空容器
container.innerHTML = '';

// 创建canvas元素
const canvas = document.createElement('canvas');
canvas.width = this.size;
canvas.height = this.size;
container.appendChild(canvas);

// 生成QR码
await QRCode.toCanvas(canvas, uri, {
width: this.size,
margin: 2,
color: {
dark: '#000000',
light: '#ffffff'
}
});

// 添加连接说明
const instructions = document.createElement('div');
instructions.className = 'walletconnect-instructions';
instructions.textContent = '请使用钱包应用扫描QR码进行连接';
container.appendChild(instructions);

// 添加连接URI文本(用于手动输入)
const uriText = document.createElement('div');
uriText.className = 'walletconnect-uri';
uriText.textContent = uri;
uriText.style.display = 'none'; // 默认隐藏,可通过CSS显示
container.appendChild(uriText);

} catch (error) {
console.error('Failed to generate QR code:', error);
throw error;
}
}

// 隐藏QR码
hideQRCode() {
const container = document.getElementById(this.containerId);
if (container) {
container.innerHTML = '<div class="walletconnect-connected">已连接到钱包!</div>';
}
}

// 取消连接请求
async cancelConnection() {
if (this.walletConnectManager) {
try {
await this.walletConnectManager.disconnectWallet();
} catch (error) {
console.error('Failed to cancel WalletConnect connection:', error);
}
}

this.hideQRCode();
}

// 获取WalletConnect管理器实例
getWalletConnectManager() {
return this.walletConnectManager;
}
}

// 使用示例
const customQRCode = new CustomWalletConnectQRCode({
containerId: 'my-custom-qr-container',
size: 300,
onConnectionStatus: (status) => {
if (status.connected) {
console.log('钱包已连接,会话信息:', status.session);
// 执行连接成功后的操作
} else {
console.log('钱包连接断开,原因:', status.reason);
// 执行断开连接后的操作
}
}
});

// 显示自定义QR码
async function showCustomWalletConnectQR() {
try {
await customQRCode.initAndShowQRCode();
} catch (error) {
console.error('显示自定义QR码失败:', error);
}
}

// 取消连接
function cancelWalletConnect() {
customQRCode.cancelConnection();
}

5. WalletConnect安全最佳实践

5.1 安全连接实现

// WalletConnect安全连接管理器
class SecureWalletConnectManager {
constructor(options = {}) {
this.walletConnectManager = new WalletConnectManager();
this.allowedDomains = options.allowedDomains || [];
this.allowedChainIds = options.allowedChainIds || [1]; // 默认只允许主网
this.sessionTimeout = options.sessionTimeout || 30 * 60 * 1000; // 30分钟
this.sessionTimer = null;
}

// 初始化安全连接
async initSecureConnection() {
try {
// 验证域名
this.validateDomain();

// 初始化WalletConnect管理器
await this.walletConnectManager.initWalletConnect();

// 设置安全相关的事件监听
this.setupSecurityListeners();

// 设置会话超时
this.setupSessionTimeout();

console.log('Secure WalletConnect initialized');
return this.walletConnectManager;
} catch (error) {
console.error('Failed to initialize secure WalletConnect:', error);
throw error;
}
}

// 验证域名(防止钓鱼攻击)
validateDomain() {
if (this.allowedDomains.length === 0) {
console.warn('No allowed domains configured for WalletConnect security');
return;
}

const currentDomain = window.location.hostname;
const isAllowed = this.allowedDomains.some(domain => {
// 支持精确匹配和通配符前缀
if (domain.startsWith('*.')) {
const baseDomain = domain.slice(2);
return currentDomain.endsWith(baseDomain);
}
return currentDomain === domain;
});

if (!isAllowed) {
throw new Error(`Domain ${currentDomain} is not allowed to use WalletConnect`);
}
}

// 设置安全相关的事件监听
setupSecurityListeners() {
// 监听链变更,验证是否为允许的链
this.walletConnectManager.on('chain_changed', (chainId) => {
if (!this.allowedChainIds.includes(chainId)) {
console.warn(`Chain ${chainId} is not allowed, disconnecting...`);
this.walletConnectManager.disconnectWallet();
alert(`不允许连接到链ID ${chainId},请使用支持的网络。`);
}
});

// 监听断开连接事件,清除超时计时器
this.walletConnectManager.on('wallet_disconnected', () => {
this.clearSessionTimeout();
});
}

// 设置会话超时
setupSessionTimeout() {
// 清除已存在的计时器
this.clearSessionTimeout();

// 创建新的超时计时器
this.sessionTimer = setTimeout(() => {
console.log('WalletConnect session timed out');
this.walletConnectManager.disconnectWallet();
alert('钱包连接已超时,请重新连接。');
}, this.sessionTimeout);
}

// 清除会话超时计时器
clearSessionTimeout() {
if (this.sessionTimer) {
clearTimeout(this.sessionTimer);
this.sessionTimer = null;
}
}

// 安全地连接钱包
async secureConnect() {
try {
// 检查是否已初始化
if (!this.walletConnectManager.provider) {
await this.initSecureConnection();
}

// 连接钱包
const session = await this.walletConnectManager.connectWallet();

// 验证连接的链是否在允许列表中
if (!this.allowedChainIds.includes(session.chainId)) {
await this.walletConnectManager.disconnectWallet();
throw new Error(`Connected chain ${session.chainId} is not allowed`);
}

// 重新设置会话超时
this.setupSessionTimeout();

return session;
} catch (error) {
console.error('Secure WalletConnect connection failed:', error);
throw error;
}
}

// 安全地发送交易
async secureSendTransaction(transaction) {
try {
// 验证连接状态
const state = this.walletConnectManager.getConnectionState();
if (!state.isConnected) {
throw new Error('Wallet is not connected');
}

// 验证交易参数
this.validateTransaction(transaction);

// 验证链ID
if (!this.allowedChainIds.includes(state.session.chainId)) {
throw new Error(`Current chain ${state.session.chainId} is not allowed for transactions`);
}

// 重新设置会话超时
this.setupSessionTimeout();

// 发送交易
return await this.walletConnectManager.sendTransaction(transaction);
} catch (error) {
console.error('Secure transaction failed:', error);
throw error;
}
}

// 验证交易参数
validateTransaction(transaction) {
// 检查必要的交易参数
if (!transaction.to) {
throw new Error('Transaction must have a recipient address');
}

// 验证地址格式
if (!this.isValidAddress(transaction.to)) {
throw new Error('Invalid recipient address');
}

// 可以添加更多验证逻辑,如金额限制等
}

// 验证以太坊地址
isValidAddress(address) {
try {
// 检查地址格式是否符合以太坊标准
return ethers.utils.isAddress(address);
} catch (error) {
return false;
}
}

// 安全断开连接
async secureDisconnect() {
try {
// 清除会话超时计时器
this.clearSessionTimeout();

// 断开连接
return await this.walletConnectManager.disconnectWallet();
} catch (error) {
console.error('Secure disconnection failed:', error);
throw error;
}
}

// 销毁管理器
destroy() {
this.clearSessionTimeout();
// 执行其他清理操作
}
}

// 使用示例
const secureWalletConnect = new SecureWalletConnectManager({
allowedDomains: ['my-dapp.com', '*.my-dapp.com'],
allowedChainIds: [1, 137], // 允许主网和Polygon
sessionTimeout: 60 * 60 * 1000 // 60分钟
});

// 安全连接钱包
async function secureConnectWallet() {
try {
const session = await secureWalletConnect.secureConnect();
console.log('安全连接成功:', session);
return session;
} catch (error) {
console.error('安全连接失败:', error);
alert(`连接失败: ${error.message}`);
return null;
}
}

// 安全发送交易
async function secureTransaction(transactionData) {
try {
const txHash = await secureWalletConnect.secureSendTransaction(transactionData);
console.log('安全交易已发送:', txHash);
return txHash;
} catch (error) {
console.error('安全交易失败:', error);
alert(`交易失败: ${error.message}`);
return null;
}
}

5.2 常见安全问题与解决方案

安全问题描述解决方案
会话劫持攻击者截获WalletConnect会话使用HTTPS、实现会话超时、定期验证会话有效性
钓鱼攻击恶意网站模仿真实DApp诱导用户连接验证域名、显示安全提示、使用硬件钱包
权限滥用授权过多方法给DApp实现最小权限原则、定期撤销授权
链ID混淆攻击者诱导用户连接到恶意链验证链ID、实施链白名单、显示当前链信息
签名重放签名被用于其他目的使用nonce、包含域分隔符、验证签名上下文

5.3 安全审计与监控

// WalletConnect安全审计与监控工具
class WalletConnectSecurityMonitor {
constructor(walletConnectManager) {
this.walletConnectManager = walletConnectManager;
this.securityEvents = [];
this.eventListeners = [];
this.isMonitoring = false;
}

// 开始安全监控
startMonitoring() {
if (this.isMonitoring) {
console.warn('WalletConnect security monitoring already started');
return;
}

// 设置各种安全相关事件的监听
this.setupSecurityEventListeners();

// 记录初始状态
this.logSecurityEvent('monitoring_started', {
timestamp: Date.now(),
initialState: this.walletConnectManager.getConnectionState()
});

this.isMonitoring = true;
console.log('WalletConnect security monitoring started');
}

// 设置安全事件监听
setupSecurityEventListeners() {
// 监听连接事件
const connectListener = this.walletConnectManager.on('wallet_connected', (session) => {
this.logSecurityEvent('wallet_connected', {
session: {
accounts: session.accounts,
chainId: session.chainId,
// 不记录敏感信息
},
timestamp: Date.now()
});

// 验证会话信息
this.validateSession(session);
});

// 监听断开连接事件
const disconnectListener = this.walletConnectManager.on('wallet_disconnected', (reason) => {
this.logSecurityEvent('wallet_disconnected', {
reason,
timestamp: Date.now()
});
});

// 监听账户变更事件
const accountsListener = this.walletConnectManager.on('accounts_changed', (accounts) => {
this.logSecurityEvent('accounts_changed', {
accounts,
timestamp: Date.now()
});
});

// 监听链变更事件
const chainListener = this.walletConnectManager.on('chain_changed', (chainId) => {
this.logSecurityEvent('chain_changed', {
chainId,
timestamp: Date.now()
});

// 验证链变更
this.validateChainChange(chainId);
});

// 保存监听器引用以便后续清理
this.eventListeners = [connectListener, disconnectListener, accountsListener, chainListener];
}

// 验证会话信息
validateSession(session) {
try {
// 检查会话是否包含预期的字段
if (!session || !session.accounts || !session.chainId) {
this.logSecurityEvent('invalid_session', {
reason: 'Missing required session fields',
session,
severity: 'high'
});
return false;
}

// 检查账户地址格式
const allValidAddresses = session.accounts.every(address => {
try {
return ethers.utils.isAddress(address);
} catch (error) {
return false;
}
});

if (!allValidAddresses) {
this.logSecurityEvent('invalid_session', {
reason: 'Invalid account address format',
session,
severity: 'high'
});
return false;
}

// 检查链ID是否在预期范围内
if (session.chainId <= 0) {
this.logSecurityEvent('invalid_session', {
reason: 'Invalid chain ID',
session,
severity: 'high'
});
return false;
}

return true;
} catch (error) {
console.error('Error validating session:', error);
this.logSecurityEvent('session_validation_error', {
error: error.message,
severity: 'medium'
});
return false;
}
}

// 验证链变更
validateChainChange(chainId) {
try {
// 这里可以实现特定的链变更验证逻辑
// 例如检查是否允许切换到该链

// 记录链变更信息
console.log(`Chain changed to ${chainId}`);

return true;
} catch (error) {
console.error('Error validating chain change:', error);
this.logSecurityEvent('chain_change_validation_error', {
error: error.message,
chainId,
severity: 'medium'
});
return false;
}
}

// 记录安全事件
logSecurityEvent(eventType, details) {
const securityEvent = {
eventType,
timestamp: details.timestamp || Date.now(),
severity: details.severity || 'low',
details: this.sanitizeDetails(details)
};

// 添加到事件列表
this.securityEvents.push(securityEvent);

// 保留最近100个事件
if (this.securityEvents.length > 100) {
this.securityEvents.shift();
}

// 控制台日志
console.log(`[SECURITY] ${eventType}:`, securityEvent);

// 如果是高严重性事件,可以触发警报
if (securityEvent.severity === 'high') {
this.triggerSecurityAlert(securityEvent);
}
}

// 清理敏感信息
sanitizeDetails(details) {
// 创建副本以避免修改原始对象
const sanitized = { ...details };

// 移除或混淆敏感信息
if (sanitized.session && sanitized.session.accounts) {
sanitized.session.accounts = sanitized.session.accounts.map(address =>
this.obfuscateAddress(address)
);
}

if (sanitized.accounts) {
sanitized.accounts = sanitized.accounts.map(address =>
this.obfuscateAddress(address)
);
}

return sanitized;
}

// 混淆以太坊地址(显示前4位和后4位)
obfuscateAddress(address) {
if (!address || address.length < 10) return '***';
return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`;
}

// 触发安全警报
triggerSecurityAlert(event) {
// 实际实现可能包括:
// 1. 显示用户警报
// 2. 发送日志到服务器
// 3. 自动断开连接(对于严重威胁)

console.error('🚨 SECURITY ALERT:', event);

// 根据事件类型执行不同的响应
if (event.eventType === 'invalid_session') {
// 对于无效会话,可以尝试断开连接
setTimeout(() => {
try {
this.walletConnectManager.disconnectWallet();
} catch (error) {
console.error('Failed to disconnect wallet after security alert:', error);
}
}, 1000);
}
}

// 获取安全事件日志
getSecurityLog() {
return [...this.securityEvents];
}

// 生成安全报告
generateSecurityReport() {
const eventsByType = this.securityEvents.reduce((acc, event) => {
if (!acc[event.eventType]) {
acc[event.eventType] = [];
}
acc[event.eventType].push(event);
return acc;
}, {});

const highSeverityEvents = this.securityEvents.filter(event => event.severity === 'high');
const mediumSeverityEvents = this.securityEvents.filter(event => event.severity === 'medium');

return {
timestamp: Date.now(),
totalEvents: this.securityEvents.length,
highSeverityEvents: highSeverityEvents.length,
mediumSeverityEvents: mediumSeverityEvents.length,
eventsByType,
summary: this.generateSecuritySummary(highSeverityEvents.length, mediumSeverityEvents.length)
};
}

// 生成安全摘要
generateSecuritySummary(highCount, mediumCount) {
if (highCount > 0) {
return `检测到 ${highCount} 个高严重性安全事件,需要立即关注。`;
} else if (mediumCount > 0) {
return `检测到 ${mediumCount} 个中等严重性安全事件,建议进行检查。`;
} else {
return '未检测到严重的安全问题。';
}
}

// 停止安全监控
stopMonitoring() {
if (!this.isMonitoring) {
console.warn('WalletConnect security monitoring not started');
return;
}

// 移除所有事件监听器
this.eventListeners.forEach(removeListener => removeListener());
this.eventListeners = [];

// 记录停止监控事件
this.logSecurityEvent('monitoring_stopped', {
timestamp: Date.now()
});

this.isMonitoring = false;
console.log('WalletConnect security monitoring stopped');
}
}

// 使用示例
function setupWalletConnectSecurity() {
// 假设已经初始化了walletConnectManager
const securityMonitor = new WalletConnectSecurityMonitor(walletConnectManager);

// 开始监控
securityMonitor.startMonitoring();

// 定期生成安全报告(例如每小时)
setInterval(() => {
const report = securityMonitor.generateSecurityReport();
console.log('WalletConnect Security Report:', report);

// 可以将报告发送到服务器进行分析
// sendSecurityReportToServer(report);
}, 60 * 60 * 1000);

// 在应用关闭前停止监控
window.addEventListener('beforeunload', () => {
securityMonitor.stopMonitoring();
});

return securityMonitor;
}

6. WalletConnect V2 新特性

WalletConnect V2相比V1版本带来了多项重要改进:

6.1 核心改进

  • 支持多链连接:一次连接支持多条链,无需重新连接
  • 账户抽象支持:更好地支持以太坊改进提案EIP-4337(账户抽象)
  • 改进的隐私保护:增强用户隐私和数据保护
  • 更好的移动体验:优化移动端用户体验和深度链接支持
  • 去中心化网络:使用去中心化的Relay服务器网络,提高可靠性

6.2 V2迁移指南

如果您正在从WalletConnect V1迁移到V2,以下是一些关键步骤:

  1. 更新依赖包:安装@walletconnect/client和@walletconnect/ethereum-provider替代旧的walletconnect包
  2. 获取Project ID:从WalletConnect云注册获取Project ID
  3. 更新连接逻辑:使用新的API初始化和管理连接
  4. 处理多链支持:利用V2的多链特性简化多网络支持
  5. 更新事件处理:适应V2的新事件系统
// WalletConnect V1到V2的迁移示例
class WalletConnectMigrationHelper {
constructor() {
this.isV2Available = false;
}

// 检测是否可以使用V2
async checkV2Compatibility() {
try {
// 尝试动态导入V2包
await import('@walletconnect/client');
await import('@walletconnect/ethereum-provider');
this.isV2Available = true;
return true;
} catch (error) {
console.warn('WalletConnect V2 is not available, falling back to V1');
return false;
}
}

// 创建适合当前环境的WalletConnect实例
async createWalletConnectInstance(options = {}) {
const isV2Compatible = await this.checkV2Compatibility();

if (isV2Compatible) {
// 使用V2
return this.createV2Instance(options);
} else {
// 回退到V1
return this.createV1Instance(options);
}
}

// 创建V2实例
async createV2Instance(options) {
const { WalletConnectClient } = await import('@walletconnect/client');
const { EthereumProvider } = await import('@walletconnect/ethereum-provider');

const provider = await EthereumProvider.init({
projectId: options.projectId || 'YOUR_PROJECT_ID',
chains: options.chains || [1],
showQrModal: options.showQrModal !== undefined ? options.showQrModal : true,
methods: options.methods || [
'eth_accounts',
'eth_chainId',
'eth_sign',
'eth_signTypedData',
'eth_sendTransaction'
],
events: options.events || ['accountsChanged', 'chainChanged']
});

return {
version: 2,
provider,
isV2: true
};
}

// 创建V1实例(回退方案)
async createV1Instance(options) {
try {
const WalletConnect = (await import('walletconnect')).default;

const connector = new WalletConnect({
bridge: options.bridge || 'https://bridge.walletconnect.org',
qrcode: options.qrcode !== undefined ? options.qrcode : true
});

return {
version: 1,
connector,
isV2: false
};
} catch (error) {
console.error('Failed to create WalletConnect V1 instance:', error);
throw new Error('No compatible WalletConnect version available');
}
}

// 获取统一的API包装器
async getUnifiedWalletConnectAPI(options = {}) {
const instance = await this.createWalletConnectInstance(options);

// 根据版本提供统一的API接口
if (instance.isV2) {
return this.createV2APIWrapper(instance.provider);
} else {
return this.createV1APIWrapper(instance.connector);
}
}

// 创建V2 API包装器
createV2APIWrapper(provider) {
return {
connect: async () => {
await provider.connect();
const accounts = await provider.request({ method: 'eth_accounts' });
const chainId = await provider.request({ method: 'eth_chainId' });

return {
accounts,
chainId: parseInt(chainId, 16)
};
},
disconnect: async () => {
await provider.disconnect();
},
sendTransaction: async (tx) => {
return await provider.request({
method: 'eth_sendTransaction',
params: [tx]
});
},
signMessage: async (message, address) => {
return await provider.request({
method: 'eth_sign',
params: [address, ethers.utils.hexlify(ethers.utils.toUtf8Bytes(message))]
});
},
on: (event, handler) => {
provider.on(event, handler);
return () => provider.off(event, handler);
},
isConnected: () => provider.connected
};
}

// 创建V1 API包装器
createV1APIWrapper(connector) {
return {
connect: async () => {
if (!connector.connected) {
await connector.createSession();
}
return {
accounts: connector.accounts,
chainId: connector.chainId
};
},
disconnect: async () => {
if (connector.connected) {
await connector.killSession();
}
},
sendTransaction: async (tx) => {
return await connector.sendTransaction(tx);
},
signMessage: async (message, address) => {
return await connector.signMessage([message, address]);
},
on: (event, handler) => {
connector.on(event, handler);
return () => connector.off(event, handler);
},
isConnected: () => connector.connected
};
}
}

// 使用示例
const migrationHelper = new WalletConnectMigrationHelper();

async function getWalletConnectAPI() {
try {
const walletConnectAPI = await migrationHelper.getUnifiedWalletConnectAPI({
projectId: 'YOUR_PROJECT_ID', // V2项目ID
chains: [1, 5, 137], // 支持的链
bridge: 'https://bridge.walletconnect.org' // V1桥接器(备用)
});

console.log('WalletConnect API initialized successfully');
return walletConnectAPI;
} catch (error) {
console.error('Failed to initialize WalletConnect API:', error);
throw error;
}
}

7. 总结

WalletConnect协议为Web3应用提供了安全、便捷的跨设备钱包连接解决方案,通过了解其工作原理和实现方法,开发者可以为用户提供更流畅、更安全的多设备Web3体验。

在实现WalletConnect集成时,应始终将安全性放在首位,遵循本文介绍的最佳实践,包括:

  1. 使用最新版本的WalletConnect:优先使用V2版本,享受更好的安全性和功能
  2. 实施严格的安全验证:验证域名、链ID、交易参数等关键信息
  3. 保护用户隐私:避免收集和存储不必要的用户数据
  4. 提供清晰的用户反馈:在连接过程中的每个阶段都提供明确的反馈
  5. 实现会话管理:设置合理的会话超时,定期验证会话有效性
  6. 监控安全事件:建立安全监控机制,及时发现和响应潜在威胁

通过遵循这些原则和实践,开发者可以构建出既安全又用户友好的WalletConnect集成,为Web3生态系统的发展做出贡献。