什么是Web3钱包?主流的Web3钱包有哪些?
What is a Web3 wallet? What are the mainstream Web3 wallets?
*考察点:钱包基本概念与分类。*
共 30 道题目
What is a Web3 wallet? What are the mainstream Web3 wallets?
What is a Web3 wallet? What are the mainstream Web3 wallets?
考察点:钱包基本概念与分类。
答案:
Web3钱包是用户在去中心化网络中管理数字资产和身份的工具。它不仅存储加密货币,还能与智能合约交互、签署交易,是用户进入Web3世界的入口。Web3钱包通过私钥控制资产,用户拥有完全的资产控制权,体现了"Not your keys, not your crypto"的核心理念。
主要类型:
主流Web3钱包:
核心功能:
What is the basic working principle of MetaMask?
What is the basic working principle of MetaMask?
考察点:MetaMask核心机制理解。
答案:
MetaMask是一个浏览器插件钱包,作为用户与以太坊区块链之间的桥梁。它在浏览器中注入ethereum对象,使网页能够与区块链交互。MetaMask管理用户的私钥,代替用户签名交易,并通过RPC节点将交易广播到区块链网络。
工作原理:
核心组件:
安全机制:
How to detect if a user has MetaMask installed?
How to detect if a user has MetaMask installed?
考察点:钱包检测基础。
答案:
检测MetaMask安装的核心是检查浏览器中是否存在ethereum对象。MetaMask会在页面加载时注入window.ethereum,我们可以通过检测这个对象来判断钱包是否已安装。
基础检测方法:
// 简单检测
function isMetaMaskInstalled() {
return typeof window.ethereum !== 'undefined';
}
// 更准确的检测
function detectMetaMask() {
if (typeof window.ethereum !== 'undefined') {
// 检查是否确实是MetaMask
if (window.ethereum.isMetaMask) {
return true;
}
}
return false;
}
完整检测实现:
async function checkMetaMask() {
// 检查ethereum对象是否存在
if (typeof window.ethereum === 'undefined') {
return {
installed: false,
message: 'MetaMask未安装,请先安装MetaMask插件'
};
}
// 检查是否为MetaMask
if (!window.ethereum.isMetaMask) {
return {
installed: false,
message: '检测到其他钱包,但未安装MetaMask'
};
}
return {
installed: true,
message: 'MetaMask已安装并可用'
};
}
// checkMetaMask() => {installed: true, message: 'MetaMask已安装并可用'}
注意事项:
How to request users to connect their wallet? What should be noted during the connection process?
How to request users to connect their wallet? What should be noted during the connection process?
考察点:钱包连接流程。
答案:
钱包连接是通过调用ethereum.request()方法请求用户授权访问账户。这个过程会弹出MetaMask授权窗口,用户确认后返回账户地址数组。连接建立了网站与钱包的信任关系,是后续所有交互的基础。
基本连接方法:
async function connectWallet() {
try {
// 请求连接钱包
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
if (accounts.length > 0) {
console.log('连接成功,当前账户:', accounts[0]);
return accounts[0];
}
} catch (error) {
console.error('连接失败:', error);
handleConnectionError(error);
}
}
// connectWallet() => '0x742d35Cc6327C0532...'
完整连接实现:
async function connectToMetaMask() {
// 检查MetaMask是否安装
if (!window.ethereum) {
throw new Error('MetaMask未安装');
}
try {
// 请求账户访问权限
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
// 获取网络信息
const chainId = await window.ethereum.request({
method: 'eth_chainId'
});
return {
success: true,
account: accounts[0],
chainId: chainId,
message: '钱包连接成功'
};
} catch (error) {
return handleConnectionError(error);
}
}
function handleConnectionError(error) {
if (error.code === 4001) {
return { success: false, message: '用户拒绝连接请求' };
} else if (error.code === -32002) {
return { success: false, message: '连接请求已存在,请检查MetaMask' };
} else {
return { success: false, message: `连接失败: ${error.message}` };
}
}
连接注意事项:
What are account permissions? How to get the user's Ethereum address?
What are account permissions? How to get the user’s Ethereum address?
考察点:权限管理基础。
答案:
账户权限是指网站访问用户钱包账户信息的授权。用户必须明确授权,网站才能获取账户地址、发送交易等。这是Web3安全机制的核心,确保用户对自己的资产和隐私有完全控制权。权限一旦授予,网站可以读取账户地址和余额,但不能自动发送交易。
获取账户地址的方法:
// 方法1:检查已授权账户
async function getConnectedAccounts() {
try {
const accounts = await window.ethereum.request({
method: 'eth_accounts'
});
return accounts; // 返回已授权的账户数组
} catch (error) {
console.error('获取账户失败:', error);
return [];
}
}
// 方法2:请求账户访问(会弹窗)
async function requestAccountAccess() {
try {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
return accounts[0]; // 返回主账户地址
} catch (error) {
throw new Error('用户拒绝授权或发生错误');
}
}
完整账户管理实现:
class WalletManager {
constructor() {
this.currentAccount = null;
this.isConnected = false;
}
// 检查连接状态并获取账户
async checkConnection() {
try {
const accounts = await window.ethereum.request({
method: 'eth_accounts'
});
if (accounts.length > 0) {
this.currentAccount = accounts[0];
this.isConnected = true;
return this.currentAccount;
} else {
this.isConnected = false;
return null;
}
} catch (error) {
console.error('检查连接状态失败:', error);
return null;
}
}
// 获取当前账户地址
getCurrentAccount() {
return this.currentAccount;
}
}
// const wallet = new WalletManager();
// await wallet.checkConnection() => '0x742d35Cc6327C0532...'
权限相关概念:
最佳实践:
How to listen for account switching events in the wallet?
How to listen for account switching events in the wallet?
考察点:账户状态监听。
答案:
账户切换事件监听通过ethereum.on(‘accountsChanged’, callback)方法实现。当用户在MetaMask中切换账户、锁定钱包或撤销权限时,都会触发此事件。监听这个事件对于保持应用状态与钱包状态同步非常重要。
基础监听实现:
// 监听账户变化
function setupAccountListener() {
if (window.ethereum) {
window.ethereum.on('accountsChanged', handleAccountsChanged);
}
}
function handleAccountsChanged(accounts) {
if (accounts.length === 0) {
// 用户断开连接或锁定了钱包
console.log('钱包已断开连接');
handleDisconnection();
} else {
// 用户切换了账户
console.log('账户已切换:', accounts[0]);
updateCurrentAccount(accounts[0]);
}
}
function handleDisconnection() {
// 清理应用状态
currentAccount = null;
isConnected = false;
// 更新UI显示
updateUI();
}
// setupAccountListener()
完整账户监听管理:
class AccountManager {
constructor() {
this.currentAccount = null;
this.listeners = [];
this.setupListener();
}
setupListener() {
if (window.ethereum) {
window.ethereum.on('accountsChanged', (accounts) => {
this.handleAccountChange(accounts);
});
}
}
handleAccountChange(accounts) {
const previousAccount = this.currentAccount;
if (accounts.length === 0) {
// 钱包断开或锁定
this.currentAccount = null;
this.notifyListeners('disconnected', { previousAccount });
} else if (accounts[0] !== previousAccount) {
// 账户切换
this.currentAccount = accounts[0];
this.notifyListeners('accountChanged', {
newAccount: accounts[0],
previousAccount: previousAccount
});
}
}
// 添加事件监听器
addListener(callback) {
this.listeners.push(callback);
}
// 移除事件监听器
removeListener(callback) {
this.listeners = this.listeners.filter(listener => listener !== callback);
}
// 通知所有监听器
notifyListeners(event, data) {
this.listeners.forEach(callback => {
try {
callback(event, data);
} catch (error) {
console.error('监听器回调错误:', error);
}
});
}
// 清理监听器
cleanup() {
if (window.ethereum && window.ethereum.removeListener) {
window.ethereum.removeListener('accountsChanged', this.handleAccountChange);
}
}
}
// 使用示例
const accountManager = new AccountManager();
accountManager.addListener((event, data) => {
if (event === 'accountChanged') {
console.log(`账户从 ${data.previousAccount} 切换到 ${data.newAccount}`);
} else if (event === 'disconnected') {
console.log('钱包已断开连接');
}
});
监听注意事项:
What is network switching? How to request users to switch to a specific network?
What is network switching? How to request users to switch to a specific network?
考察点:网络管理基础。
答案:
网络切换是指在不同的区块链网络之间切换,如从以太坊主网切换到Polygon或BSC。不同网络有不同的Chain ID、RPC端点和原生代币。DApp通常需要在特定网络上运行,因此需要引导用户切换到正确的网络。
请求网络切换:
// 请求切换到指定网络
async function switchNetwork(chainId) {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: chainId }]
});
console.log('网络切换成功');
} catch (error) {
if (error.code === 4902) {
// 网络未添加,需要先添加网络
console.log('网络未添加,尝试添加网络');
throw new Error('NETWORK_NOT_ADDED');
} else {
console.error('网络切换失败:', error);
throw error;
}
}
}
// 切换到Polygon网络
// switchNetwork('0x89') => 切换成功
添加新网络:
// 添加新网络配置
async function addNetwork(networkConfig) {
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [networkConfig]
});
console.log('网络添加成功');
} catch (error) {
console.error('网络添加失败:', error);
throw error;
}
}
// Polygon网络配置
const polygonNetwork = {
chainId: '0x89', // 137 in decimal
chainName: 'Polygon Mainnet',
nativeCurrency: {
name: 'MATIC',
symbol: 'MATIC',
decimals: 18
},
rpcUrls: ['https://polygon-rpc.com'],
blockExplorerUrls: ['https://polygonscan.com']
};
完整网络管理实现:
class NetworkManager {
constructor() {
this.networks = {
'0x1': {
chainId: '0x1',
chainName: 'Ethereum Mainnet',
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrls: ['https://mainnet.infura.io/v3/YOUR_KEY'],
blockExplorerUrls: ['https://etherscan.io']
},
'0x89': {
chainId: '0x89',
chainName: 'Polygon Mainnet',
nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 },
rpcUrls: ['https://polygon-rpc.com'],
blockExplorerUrls: ['https://polygonscan.com']
}
};
}
async switchToNetwork(chainId) {
try {
// 尝试切换网络
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId }]
});
return { success: true, message: '网络切换成功' };
} catch (error) {
if (error.code === 4902) {
// 网络未添加,尝试添加
return await this.addAndSwitchNetwork(chainId);
} else if (error.code === 4001) {
return { success: false, message: '用户拒绝切换网络' };
} else {
return { success: false, message: `切换失败: ${error.message}` };
}
}
}
async addAndSwitchNetwork(chainId) {
const networkConfig = this.networks[chainId];
if (!networkConfig) {
return { success: false, message: '不支持的网络' };
}
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [networkConfig]
});
return { success: true, message: '网络添加并切换成功' };
} catch (error) {
return { success: false, message: `添加网络失败: ${error.message}` };
}
}
async getCurrentNetwork() {
try {
const chainId = await window.ethereum.request({
method: 'eth_chainId'
});
return chainId;
} catch (error) {
console.error('获取当前网络失败:', error);
return null;
}
}
}
// 使用示例
const networkManager = new NetworkManager();
await networkManager.switchToNetwork('0x89'); // 切换到Polygon
常见网络配置:
How to listen for network switching events in the wallet?
How to listen for network switching events in the wallet?
考察点:网络状态监听。
答案:
网络切换事件通过ethereum.on(‘chainChanged’, callback)方法监听。当用户在MetaMask中切换网络时,会触发此事件并返回新的Chain ID。监听网络变化对于确保DApp在正确网络上运行至关重要。
基础网络监听:
// 监听网络变化
function setupChainListener() {
if (window.ethereum) {
window.ethereum.on('chainChanged', handleChainChanged);
}
}
function handleChainChanged(chainId) {
console.log('网络已切换:', chainId);
// 转换为十进制显示
const networkId = parseInt(chainId, 16);
console.log('网络ID (十进制):', networkId);
// 更新应用状态
updateNetworkState(chainId);
// 刷新页面(推荐方式)
window.location.reload();
}
// setupChainListener()
完整网络监听管理:
class ChainManager {
constructor() {
this.currentChainId = null;
this.supportedChains = ['0x1', '0x89', '0x38']; // ETH, Polygon, BSC
this.listeners = [];
this.setupListener();
}
setupListener() {
if (window.ethereum) {
window.ethereum.on('chainChanged', (chainId) => {
this.handleChainChange(chainId);
});
}
}
async handleChainChange(newChainId) {
const previousChainId = this.currentChainId;
this.currentChainId = newChainId;
const isSupported = this.supportedChains.includes(newChainId);
const networkInfo = this.getNetworkInfo(newChainId);
// 通知监听器
this.notifyListeners({
previousChainId,
newChainId,
isSupported,
networkInfo
});
// 处理不支持的网络
if (!isSupported) {
this.handleUnsupportedNetwork(newChainId);
}
}
getNetworkInfo(chainId) {
const networks = {
'0x1': { name: 'Ethereum Mainnet', symbol: 'ETH' },
'0x89': { name: 'Polygon', symbol: 'MATIC' },
'0x38': { name: 'BSC', symbol: 'BNB' },
'0xa4b1': { name: 'Arbitrum One', symbol: 'ETH' },
'0xa': { name: 'Optimism', symbol: 'ETH' }
};
return networks[chainId] || {
name: 'Unknown Network',
symbol: 'Unknown'
};
}
handleUnsupportedNetwork(chainId) {
console.warn(`不支持的网络: ${chainId}`);
// 显示警告消息
this.showNetworkWarning(chainId);
}
showNetworkWarning(chainId) {
const networkInfo = this.getNetworkInfo(chainId);
alert(`当前网络 ${networkInfo.name} 不受支持,请切换到支持的网络`);
}
// 添加监听器
addListener(callback) {
this.listeners.push(callback);
}
// 移除监听器
removeListener(callback) {
this.listeners = this.listeners.filter(listener => listener !== callback);
}
// 通知所有监听器
notifyListeners(data) {
this.listeners.forEach(callback => {
try {
callback(data);
} catch (error) {
console.error('网络监听器回调错误:', error);
}
});
}
// 获取当前网络
async getCurrentChain() {
if (!this.currentChainId) {
try {
this.currentChainId = await window.ethereum.request({
method: 'eth_chainId'
});
} catch (error) {
console.error('获取当前网络失败:', error);
}
}
return this.currentChainId;
}
// 检查网络是否受支持
isNetworkSupported(chainId = this.currentChainId) {
return this.supportedChains.includes(chainId);
}
// 清理监听器
cleanup() {
if (window.ethereum && window.ethereum.removeListener) {
window.ethereum.removeListener('chainChanged', this.handleChainChange);
}
}
}
// 使用示例
const chainManager = new ChainManager();
chainManager.addListener((data) => {
console.log(`网络从 ${data.previousChainId} 切换到 ${data.newChainId}`);
console.log(`新网络: ${data.networkInfo.name}`);
if (!data.isSupported) {
console.warn('当前网络不受支持');
}
});
监听最佳实践:
What is Personal Sign? How does it differ from regular signatures?
What is Personal Sign? How does it differ from regular signatures?
考察点:签名类型理解。
答案:
个人签名(Personal Sign)是一种用于签名任意文本消息的方法,遵循EIP-191标准。它在消息前添加特定前缀"\x19Ethereum Signed Message:\n",防止签名被恶意用于区块链交易。个人签名主要用于身份验证、登录授权等场景,不涉及资金转移。
个人签名特点:
个人签名实现:
// 请求个人签名
async function personalSign(message, account) {
try {
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, account]
});
return signature;
} catch (error) {
console.error('签名失败:', error);
throw error;
}
}
// 使用示例
const message = "欢迎登录我们的DApp!";
const account = "0x742d35Cc6327C0532...";
// personalSign(message, account) => "0x1234abcd..."
与普通签名的区别:
| 特性 | Personal Sign | eth_sign | Transaction Sign |
|---|---|---|---|
| 用途 | 文本消息签名 | 原始数据签名 | 交易签名 |
| 安全前缀 | 自动添加 | 无前缀 | 无需前缀 |
| 数据格式 | UTF-8文本 | 32字节哈希 | 交易对象 |
| 安全性 | 高(有前缀保护) | 低(易被滥用) | 高(受钱包保护) |
| 应用场景 | 登录、授权 | 开发调试 | 链上交易 |
完整签名验证示例:
class MessageSigner {
constructor() {
this.account = null;
}
// 个人签名
async signMessage(message) {
if (!this.account) {
throw new Error('请先连接钱包');
}
try {
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, this.account]
});
return {
message: message,
signature: signature,
address: this.account,
timestamp: Date.now()
};
} catch (error) {
if (error.code === 4001) {
throw new Error('用户拒绝签名');
}
throw error;
}
}
// 验证签名(需要ethers.js库)
async verifySignature(message, signature, expectedAddress) {
try {
// 恢复签名者地址
const recoveredAddress = ethers.utils.verifyMessage(message, signature);
// 比较地址(忽略大小写)
return recoveredAddress.toLowerCase() === expectedAddress.toLowerCase();
} catch (error) {
console.error('验证签名失败:', error);
return false;
}
}
setAccount(account) {
this.account = account;
}
}
// 使用示例
const signer = new MessageSigner();
signer.setAccount('0x742d35Cc6327C0532...');
const signData = await signer.signMessage('登录验证消息');
const isValid = await signer.verifySignature(
signData.message,
signData.signature,
signData.address
);
console.log('签名验证结果:', isValid);
应用场景:
注意事项:
How to request users to sign a message?
How to request users to sign a message?
考察点:消息签名基础。
答案:
消息签名是通过调用ethereum.request()方法,使用personal_sign或eth_signTypedData等方法实现的。用户确认后,钱包会使用私钥对消息进行签名并返回签名结果。这个过程不消耗Gas费用,主要用于身份验证和授权。
基础消息签名:
// 简单文本签名
async function signMessage(message, account) {
try {
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, account]
});
console.log('签名成功:', signature);
return signature;
} catch (error) {
console.error('签名失败:', error);
throw error;
}
}
// 使用示例
const message = "请签名以验证您的身份";
const userAccount = "0x742d35Cc6327C0532...";
// signMessage(message, userAccount) => "0x1234abcd..."
完整签名管理系统:
class SignatureManager {
constructor() {
this.currentAccount = null;
this.signatureHistory = [];
}
async connectWallet() {
try {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
this.currentAccount = accounts[0];
return this.currentAccount;
} catch (error) {
throw new Error('连接钱包失败');
}
}
// 文本消息签名
async signTextMessage(message) {
if (!this.currentAccount) {
throw new Error('请先连接钱包');
}
try {
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, this.currentAccount]
});
// 记录签名历史
const signRecord = {
type: 'personal_sign',
message: message,
signature: signature,
account: this.currentAccount,
timestamp: Date.now()
};
this.signatureHistory.push(signRecord);
return signRecord;
} catch (error) {
return this.handleSignError(error);
}
}
// 类型化数据签名(EIP-712)
async signTypedData(typedData) {
if (!this.currentAccount) {
throw new Error('请先连接钱包');
}
try {
const signature = await window.ethereum.request({
method: 'eth_signTypedData_v4',
params: [this.currentAccount, JSON.stringify(typedData)]
});
const signRecord = {
type: 'typed_data',
data: typedData,
signature: signature,
account: this.currentAccount,
timestamp: Date.now()
};
this.signatureHistory.push(signRecord);
return signRecord;
} catch (error) {
return this.handleSignError(error);
}
}
// 批量签名(多条消息)
async signMultipleMessages(messages) {
if (!Array.isArray(messages)) {
throw new Error('消息必须是数组格式');
}
const results = [];
for (let i = 0; i < messages.length; i++) {
try {
const result = await this.signTextMessage(messages[i]);
results.push(result);
} catch (error) {
console.error(`签名第${i+1}条消息失败:`, error);
results.push({ error: error.message, message: messages[i] });
}
}
return results;
}
// 处理签名错误
handleSignError(error) {
let errorMessage = '签名失败';
if (error.code === 4001) {
errorMessage = '用户拒绝签名';
} else if (error.code === -32603) {
errorMessage = '签名处理错误';
} else if (error.message) {
errorMessage = error.message;
}
return {
success: false,
error: errorMessage,
code: error.code
};
}
// 获取签名历史
getSignatureHistory() {
return this.signatureHistory;
}
// 清除签名历史
clearSignatureHistory() {
this.signatureHistory = [];
}
// 验证签名格式
isValidSignature(signature) {
// 以太坊签名格式:0x + 130个十六进制字符
const signatureRegex = /^0x[a-fA-F0-9]{130}$/;
return signatureRegex.test(signature);
}
}
// 使用示例
const sigManager = new SignatureManager();
// 连接钱包并签名
await sigManager.connectWallet();
const signResult = await sigManager.signTextMessage('欢迎使用我们的DApp!');
if (signResult.success !== false) {
console.log('签名成功:', signResult.signature);
} else {
console.log('签名失败:', signResult.error);
}
EIP-712类型化数据签名示例:
// EIP-712结构化数据
const typedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' }
],
LoginMessage: [
{ name: 'user', type: 'address' },
{ name: 'timestamp', type: 'uint256' },
{ name: 'nonce', type: 'string' }
]
},
primaryType: 'LoginMessage',
domain: {
name: 'MyDApp',
version: '1.0',
chainId: 1
},
message: {
user: '0x742d35Cc6327C0532...',
timestamp: Date.now(),
nonce: 'random-nonce-123'
}
};
// 签名类型化数据
const typedSignResult = await sigManager.signTypedData(typedData);
签名应用场景:
How to handle wallet disconnection? How to gracefully manage connection states?
How to handle wallet disconnection? How to gracefully manage connection states?
考察点:连接状态管理。
答案:
钱包断开连接需要及时检测并更新应用状态,清理相关数据,为用户提供重新连接的选项。优雅的连接状态管理包括状态监听、数据清理、UI更新和错误处理等多个方面。
连接状态检测:
// 检测连接状态变化
function setupConnectionMonitor() {
// 监听账户变化(断开时accounts为空)
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
handleDisconnection();
} else {
handleReconnection(accounts[0]);
}
});
// 监听连接状态变化
window.ethereum.on('connect', (connectInfo) => {
console.log('钱包已连接:', connectInfo);
handleConnection(connectInfo);
});
window.ethereum.on('disconnect', (error) => {
console.log('钱包已断开:', error);
handleDisconnection();
});
}
完整连接状态管理:
class WalletConnectionManager {
constructor() {
this.isConnected = false;
this.currentAccount = null;
this.chainId = null;
this.listeners = [];
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 3;
this.setupEventListeners();
}
setupEventListeners() {
if (!window.ethereum) return;
// 账户变化监听
window.ethereum.on('accountsChanged', (accounts) => {
this.handleAccountsChanged(accounts);
});
// 网络变化监听
window.ethereum.on('chainChanged', (chainId) => {
this.handleChainChanged(chainId);
});
// 连接状态监听
window.ethereum.on('connect', (connectInfo) => {
this.handleConnect(connectInfo);
});
window.ethereum.on('disconnect', (error) => {
this.handleDisconnect(error);
});
}
// 处理账户变化
handleAccountsChanged(accounts) {
if (accounts.length === 0) {
// 钱包断开或锁定
this.handleDisconnection('WALLET_LOCKED');
} else if (accounts[0] !== this.currentAccount) {
// 账户切换
this.currentAccount = accounts[0];
this.notifyListeners('accountChanged', {
account: accounts[0]
});
}
}
// 处理网络变化
handleChainChanged(chainId) {
this.chainId = chainId;
this.notifyListeners('chainChanged', { chainId });
// 网络变化可能影响连接状态,重新验证
this.validateConnection();
}
// 处理连接
handleConnect(connectInfo) {
this.isConnected = true;
this.chainId = connectInfo.chainId;
this.reconnectAttempts = 0;
this.notifyListeners('connected', connectInfo);
}
// 处理断开
handleDisconnect(error) {
this.handleDisconnection('PROVIDER_DISCONNECT', error);
}
// 统一断开处理
handleDisconnection(reason = 'UNKNOWN', error = null) {
const wasConnected = this.isConnected;
// 清理连接状态
this.isConnected = false;
this.currentAccount = null;
// 清理应用数据
this.clearApplicationData();
// 通知监听器
this.notifyListeners('disconnected', {
reason,
error,
wasConnected
});
// 尝试自动重连(某些情况下)
if (reason === 'PROVIDER_DISCONNECT' && this.shouldAutoReconnect()) {
this.attemptReconnect();
}
}
// 处理重新连接
handleReconnection(account) {
this.currentAccount = account;
this.isConnected = true;
this.reconnectAttempts = 0;
this.notifyListeners('reconnected', {
account: account
});
}
// 清理应用数据
clearApplicationData() {
// 清理缓存的用户数据
localStorage.removeItem('walletConnection');
localStorage.removeItem('userPreferences');
// 清理会话数据
sessionStorage.removeItem('transactionCache');
// 重置应用状态
this.resetApplicationState();
}
// 重置应用状态
resetApplicationState() {
// 这里可以调用应用的状态重置方法
// 例如:Redux store重置、组件状态清理等
console.log('重置应用状态');
}
// 判断是否应该自动重连
shouldAutoReconnect() {
return this.reconnectAttempts < this.maxReconnectAttempts;
}
// 尝试重连
async attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
return false;
}
this.reconnectAttempts++;
try {
await new Promise(resolve => setTimeout(resolve, 2000)); // 等待2秒
const accounts = await window.ethereum.request({
method: 'eth_accounts'
});
if (accounts.length > 0) {
this.handleReconnection(accounts[0]);
return true;
}
} catch (error) {
console.error(`重连尝试 ${this.reconnectAttempts} 失败:`, error);
}
return false;
}
// 手动连接
async connect() {
try {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
if (accounts.length > 0) {
this.currentAccount = accounts[0];
this.isConnected = true;
// 获取当前网络
this.chainId = await window.ethereum.request({
method: 'eth_chainId'
});
this.notifyListeners('connected', {
account: this.currentAccount,
chainId: this.chainId
});
return this.currentAccount;
}
} catch (error) {
this.notifyListeners('connectionError', { error });
throw error;
}
}
// 手动断开
disconnect() {
this.handleDisconnection('USER_DISCONNECT');
}
// 验证连接状态
async validateConnection() {
try {
const accounts = await window.ethereum.request({
method: 'eth_accounts'
});
const isValid = accounts.length > 0 &&
accounts[0] === this.currentAccount;
if (!isValid && this.isConnected) {
this.handleDisconnection('VALIDATION_FAILED');
}
return isValid;
} catch (error) {
console.error('连接验证失败:', error);
return false;
}
}
// 添加事件监听器
addListener(callback) {
this.listeners.push(callback);
}
// 移除事件监听器
removeListener(callback) {
this.listeners = this.listeners.filter(l => l !== callback);
}
// 通知监听器
notifyListeners(event, data) {
this.listeners.forEach(callback => {
try {
callback(event, data);
} catch (error) {
console.error('监听器回调错误:', error);
}
});
}
// 获取连接状态
getConnectionState() {
return {
isConnected: this.isConnected,
currentAccount: this.currentAccount,
chainId: this.chainId,
reconnectAttempts: this.reconnectAttempts
};
}
}
// 使用示例
const connectionManager = new WalletConnectionManager();
connectionManager.addListener((event, data) => {
switch (event) {
case 'connected':
console.log('钱包已连接:', data.account);
break;
case 'disconnected':
console.log('钱包已断开:', data.reason);
break;
case 'reconnected':
console.log('钱包已重连:', data.account);
break;
}
});
用户界面处理:
// UI状态管理
function updateConnectionUI(isConnected, account = null) {
const connectButton = document.getElementById('connectButton');
const accountDisplay = document.getElementById('accountDisplay');
const appContent = document.getElementById('appContent');
if (isConnected && account) {
connectButton.textContent = '断开连接';
accountDisplay.textContent = `已连接: ${account.substring(0, 6)}...${account.substring(38)}`;
appContent.style.display = 'block';
} else {
connectButton.textContent = '连接钱包';
accountDisplay.textContent = '未连接';
appContent.style.display = 'none';
}
}
What is WalletConnect? What problems does it solve?
What is WalletConnect? What problems does it solve?
考察点:WalletConnect协议理解。
答案:
WalletConnect是一个开放协议,通过扫码或深链接的方式连接桌面DApp与移动端钱包。它解决了移动端钱包与桌面浏览器DApp之间的连接问题,使用户可以在桌面使用DApp的同时,通过手机钱包安全地管理私钥和签署交易。
WalletConnect工作原理:
解决的核心问题:
WalletConnect集成实现:
import WalletConnect from '@walletconnect/client';
import QRCodeModal from '@walletconnect/qrcode-modal';
class WalletConnectManager {
constructor() {
this.connector = null;
this.connected = false;
this.accounts = [];
this.chainId = null;
}
// 初始化WalletConnect
async initWalletConnect() {
try {
// 创建连接器
this.connector = new WalletConnect({
bridge: 'https://bridge.walletconnect.org',
qrcodeModal: QRCodeModal
});
// 设置事件监听
this.setupEventListeners();
// 检查是否已连接
if (this.connector.connected) {
this.onConnect();
}
return this.connector;
} catch (error) {
console.error('WalletConnect初始化失败:', error);
throw error;
}
}
// 设置事件监听器
setupEventListeners() {
// 连接成功
this.connector.on('connect', (error, payload) => {
if (error) {
console.error('连接错误:', error);
return;
}
this.onConnect(payload);
});
// 会话更新
this.connector.on('session_update', (error, payload) => {
if (error) {
console.error('会话更新错误:', error);
return;
}
this.onSessionUpdate(payload);
});
// 断开连接
this.connector.on('disconnect', (error, payload) => {
if (error) {
console.error('断开连接错误:', error);
}
this.onDisconnect();
});
}
// 创建连接会话
async connect() {
try {
if (!this.connector) {
await this.initWalletConnect();
}
// 如果已连接,直接返回
if (this.connector.connected) {
return this.getConnectionInfo();
}
// 创建新会话
await this.connector.createSession();
return new Promise((resolve, reject) => {
// 监听连接结果
this.connector.on('connect', (error, payload) => {
if (error) {
reject(error);
} else {
resolve(this.getConnectionInfo());
}
});
// 设置超时
setTimeout(() => {
if (!this.connected) {
reject(new Error('连接超时'));
}
}, 30000);
});
} catch (error) {
console.error('WalletConnect连接失败:', error);
throw error;
}
}
// 处理连接成功
onConnect(payload) {
const { accounts, chainId } = payload?.params[0] || this.connector;
this.connected = true;
this.accounts = accounts;
this.chainId = chainId;
console.log('WalletConnect连接成功:', {
accounts: this.accounts,
chainId: this.chainId
});
}
// 处理会话更新
onSessionUpdate(payload) {
const { accounts, chainId } = payload.params[0];
this.accounts = accounts;
this.chainId = chainId;
console.log('WalletConnect会话更新:', {
accounts: this.accounts,
chainId: this.chainId
});
}
// 处理断开连接
onDisconnect() {
this.connected = false;
this.accounts = [];
this.chainId = null;
console.log('WalletConnect已断开连接');
}
// 发送交易
async sendTransaction(transaction) {
if (!this.connected) {
throw new Error('WalletConnect未连接');
}
try {
const result = await this.connector.sendTransaction(transaction);
return result;
} catch (error) {
console.error('发送交易失败:', error);
throw error;
}
}
// 签名消息
async signMessage(message) {
if (!this.connected) {
throw new Error('WalletConnect未连接');
}
try {
const msgParams = {
data: message,
from: this.accounts[0]
};
const result = await this.connector.signPersonalMessage(msgParams);
return result;
} catch (error) {
console.error('签名失败:', error);
throw error;
}
}
// 断开连接
async disconnect() {
if (this.connector && this.connected) {
await this.connector.killSession();
}
this.onDisconnect();
}
// 获取连接信息
getConnectionInfo() {
return {
connected: this.connected,
accounts: this.accounts,
chainId: this.chainId,
connector: this.connector
};
}
}
// 使用示例
const walletConnect = new WalletConnectManager();
// 连接钱包
async function connectWalletConnect() {
try {
const connectionInfo = await walletConnect.connect();
console.log('连接成功:', connectionInfo);
} catch (error) {
console.error('连接失败:', error);
}
}
WalletConnect v2.0 新特性:
// WalletConnect v2.0 (Sign API)
import { SignClient } from '@walletconnect/sign-client';
class WalletConnectV2Manager {
constructor() {
this.signClient = null;
this.session = null;
}
async init() {
this.signClient = await SignClient.init({
projectId: 'YOUR_PROJECT_ID', // 从WalletConnect Cloud获取
metadata: {
name: 'My DApp',
description: 'My DApp Description',
url: 'https://mydapp.com',
icons: ['https://mydapp.com/icon.png']
}
});
}
async connect() {
const { uri, approval } = await this.signClient.connect({
requiredNamespaces: {
eip155: {
methods: ['eth_sendTransaction', 'personal_sign'],
chains: ['eip155:1'],
events: ['accountsChanged', 'chainChanged']
}
}
});
if (uri) {
QRCodeModal.open(uri, () => {
console.log('用户关闭了二维码弹窗');
});
}
this.session = await approval();
QRCodeModal.close();
return this.session;
}
}
WalletConnect优势:
How do mobile DApps interact with wallet applications?
How do mobile DApps interact with wallet applications?
考察点:移动端钱包集成。
答案:
移动端DApp与钱包应用的交互主要通过深链接(Deep Link)、应用内浏览器和WalletConnect协议实现。由于移动端浏览器限制,无法像桌面端那样直接注入ethereum对象,需要采用特殊的交互方式来实现钱包连接和交易签名。
主要交互方式:
1. 深链接(Deep Link):
// 构建深链接URL
function buildDeepLink(walletScheme, dappUrl, action) {
const encodedUrl = encodeURIComponent(dappUrl);
return `${walletScheme}://wc?uri=${encodedUrl}&action=${action}`;
}
// 跳转到钱包应用
function openWalletApp(walletName, params) {
const walletSchemes = {
metamask: 'metamask',
trust: 'trust',
coinbase: 'cbwallet',
imtoken: 'imtokenv2'
};
const scheme = walletSchemes[walletName];
if (!scheme) {
throw new Error('不支持的钱包');
}
const deepLink = buildDeepLink(scheme, window.location.href, 'connect');
window.location.href = deepLink;
}
// 检测移动端环境
function isMobile() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
navigator.userAgent
);
}
2. WalletConnect集成:
class MobileWalletConnector {
constructor() {
this.connector = null;
this.isMobile = this.detectMobile();
}
detectMobile() {
return window.innerWidth <= 768 || /Mobi|Android/i.test(navigator.userAgent);
}
async connectWallet() {
if (this.isMobile) {
return await this.connectMobile();
} else {
return await this.connectDesktop();
}
}
async connectMobile() {
// 移动端优先使用WalletConnect
const { WalletConnect } = await import('@walletconnect/client');
this.connector = new WalletConnect({
bridge: 'https://bridge.walletconnect.org',
qrcodeModal: {
open: (uri) => {
// 移动端直接打开钱包应用
this.openMobileWallet(uri);
},
close: () => {
console.log('关闭连接');
}
}
});
if (this.connector.connected) {
return this.connector;
}
await this.connector.createSession();
return this.connector;
}
openMobileWallet(uri) {
// 尝试打开不同的钱包应用
const walletLinks = [
`metamask://wc?uri=${encodeURIComponent(uri)}`,
`trust://wc?uri=${encodeURIComponent(uri)}`,
`rainbow://wc?uri=${encodeURIComponent(uri)}`,
`imtokenv2://wc?uri=${encodeURIComponent(uri)}`
];
// 依次尝试打开钱包
walletLinks.forEach((link, index) => {
setTimeout(() => {
window.location.href = link;
}, index * 100);
});
}
}
3. 应用内浏览器检测:
// 检测钱包内置浏览器
function detectWalletBrowser() {
const userAgent = navigator.userAgent.toLowerCase();
if (userAgent.includes('metamask')) {
return 'metamask';
} else if (userAgent.includes('trust')) {
return 'trust';
} else if (userAgent.includes('coinbase')) {
return 'coinbase';
} else if (userAgent.includes('imtoken')) {
return 'imtoken';
}
return null;
}
// 在钱包浏览器中直接使用注入的对象
async function connectInWalletBrowser() {
const walletType = detectWalletBrowser();
if (walletType && window.ethereum) {
// 钱包内置浏览器,直接使用ethereum对象
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
return accounts[0];
} else {
// 普通浏览器,使用WalletConnect
throw new Error('请在钱包应用中打开或使用WalletConnect');
}
}
How to handle situations where users reject wallet requests?
How to handle situations where users reject wallet requests?
考察点:异常处理基础。
答案:
用户拒绝钱包请求是常见情况,需要优雅处理并提供合适的用户反馈。不同类型的拒绝有不同的错误码和处理方式,应该根据具体情况给出相应的提示和后续操作建议。
错误类型和处理:
class WalletErrorHandler {
constructor() {
this.errorTypes = {
4001: 'USER_REJECTED',
4100: 'UNAUTHORIZED',
4200: 'UNSUPPORTED_METHOD',
4900: 'DISCONNECTED',
-32002: 'PENDING_REQUEST',
-32603: 'INTERNAL_ERROR'
};
}
handleError(error, requestType) {
const errorCode = error.code;
const errorType = this.errorTypes[errorCode] || 'UNKNOWN_ERROR';
console.error(`钱包请求失败 [${requestType}]:`, error);
switch (errorType) {
case 'USER_REJECTED':
return this.handleUserRejection(requestType, error);
case 'UNAUTHORIZED':
return this.handleUnauthorized(requestType);
case 'PENDING_REQUEST':
return this.handlePendingRequest(requestType);
case 'DISCONNECTED':
return this.handleDisconnected();
default:
return this.handleGenericError(error, requestType);
}
}
// 处理用户拒绝
handleUserRejection(requestType, error) {
const messages = {
'connect': '连接被拒绝,请重新尝试连接钱包',
'transaction': '交易被取消,您可以稍后重新发起交易',
'sign': '签名被拒绝,无法完成身份验证',
'network': '网络切换被拒绝,某些功能可能无法使用',
'addToken': '添加代币被拒绝,您可以手动添加代币'
};
const message = messages[requestType] || '操作被用户拒绝';
return {
success: false,
error: 'USER_REJECTED',
message: message,
canRetry: true,
userAction: 'reject'
};
}
// 处理未授权
handleUnauthorized(requestType) {
return {
success: false,
error: 'UNAUTHORIZED',
message: '请先连接钱包后再进行操作',
canRetry: true,
suggestedAction: 'connect'
};
}
// 处理待处理请求
handlePendingRequest(requestType) {
return {
success: false,
error: 'PENDING_REQUEST',
message: '已有请求正在处理中,请检查钱包或稍后重试',
canRetry: true,
retryDelay: 3000
};
}
// 处理断开连接
handleDisconnected() {
return {
success: false,
error: 'DISCONNECTED',
message: '钱包连接已断开,请重新连接',
canRetry: true,
suggestedAction: 'reconnect'
};
}
// 处理通用错误
handleGenericError(error, requestType) {
return {
success: false,
error: 'UNKNOWN_ERROR',
message: `操作失败: ${error.message || '未知错误'}`,
canRetry: true,
originalError: error
};
}
}
// 使用示例
const errorHandler = new WalletErrorHandler();
async function safeWalletRequest(requestFunction, requestType) {
try {
const result = await requestFunction();
return { success: true, data: result };
} catch (error) {
return errorHandler.handleError(error, requestType);
}
}
// 安全的连接请求
async function connectWalletSafely() {
return safeWalletRequest(async () => {
return await window.ethereum.request({
method: 'eth_requestAccounts'
});
}, 'connect');
}
// 使用示例
const connectResult = await connectWalletSafely();
if (connectResult.success) {
console.log('连接成功:', connectResult.data);
} else {
console.log('连接失败:', connectResult.message);
if (connectResult.canRetry) {
// 显示重试按钮
showRetryButton(connectResult);
}
}
用户界面处理:
// UI反馈处理
class WalletUIHandler {
showError(errorResult) {
const errorContainer = document.getElementById('error-message');
const retryButton = document.getElementById('retry-button');
// 显示错误消息
errorContainer.textContent = errorResult.message;
errorContainer.className = `error-message ${errorResult.error.toLowerCase()}`;
// 显示重试按钮
if (errorResult.canRetry) {
retryButton.style.display = 'block';
retryButton.onclick = () => this.handleRetry(errorResult);
}
// 自动隐藏消息
setTimeout(() => {
errorContainer.textContent = '';
errorContainer.className = '';
}, 5000);
}
handleRetry(errorResult) {
if (errorResult.retryDelay) {
setTimeout(() => {
this.performRetry(errorResult);
}, errorResult.retryDelay);
} else {
this.performRetry(errorResult);
}
}
performRetry(errorResult) {
switch (errorResult.suggestedAction) {
case 'connect':
connectWalletSafely();
break;
case 'reconnect':
reconnectWallet();
break;
default:
// 重新执行原始操作
console.log('重试操作');
}
}
}
What are the basic security considerations in wallet integration development?
What are the basic security considerations in wallet integration development?
考察点:安全意识基础。
答案:
钱包集成安全是Web3开发的核心,涉及私钥保护、交易验证、网络安全等多个方面。开发者需要从多个维度确保用户资产和数据安全,防范各种潜在攻击。
核心安全原则:
1. 私钥和助记词安全:
// 永远不要在代码中处理私钥
class SecurityManager {
constructor() {
this.securityRules = {
// 禁止的操作
forbidden: [
'requestPrivateKey',
'storePrivateKey',
'transmitPrivateKey'
]
};
}
// 检查是否安全的钱包操作
isSecureWalletOperation(operation) {
// 只允许通过钱包接口进行操作
const allowedMethods = [
'eth_requestAccounts',
'eth_sendTransaction',
'personal_sign',
'eth_signTypedData_v4'
];
return allowedMethods.includes(operation);
}
// 验证钱包连接的安全性
validateWalletSecurity() {
const checks = {
hasEthereum: typeof window.ethereum !== 'undefined',
isSecureContext: window.isSecureContext, // HTTPS
noPrivateKeyAccess: !this.hasPrivateKeyAccess(),
trustedOrigin: this.isTrustedOrigin()
};
return Object.values(checks).every(check => check === true);
}
hasPrivateKeyAccess() {
// 检查是否有私钥访问(这是危险的)
return window.ethereum?.privateKey !== undefined;
}
isTrustedOrigin() {
// 验证应用运行在受信任的域名
const trustedDomains = ['localhost', 'yourdapp.com'];
const currentDomain = window.location.hostname;
return trustedDomains.includes(currentDomain);
}
}
2. 交易验证和确认:
// 交易安全验证
class TransactionSecurity {
// 验证交易参数
validateTransaction(transaction) {
const errors = [];
// 检查必需字段
if (!transaction.to) {
errors.push('缺少接收地址');
}
// 验证地址格式
if (transaction.to && !this.isValidAddress(transaction.to)) {
errors.push('接收地址格式无效');
}
// 检查金额合理性
if (transaction.value) {
const value = parseFloat(transaction.value);
if (value < 0) {
errors.push('交易金额不能为负数');
}
if (value > 1000) { // 大额交易警告
errors.push('大额交易,请仔细确认');
}
}
// 验证Gas设置
if (transaction.gasLimit && parseInt(transaction.gasLimit) < 21000) {
errors.push('Gas限制过低,交易可能失败');
}
return {
isValid: errors.length === 0,
errors: errors
};
}
isValidAddress(address) {
// 以太坊地址验证
return /^0x[a-fA-F0-9]{40}$/.test(address);
}
// 显示交易确认界面
showTransactionConfirmation(transaction) {
const validation = this.validateTransaction(transaction);
if (!validation.isValid) {
throw new Error(`交易验证失败: ${validation.errors.join(', ')}`);
}
// 显示交易详情供用户确认
return this.displayTransactionDetails(transaction);
}
displayTransactionDetails(transaction) {
return {
to: transaction.to,
value: `${transaction.value} ETH`,
gasLimit: transaction.gasLimit,
gasPrice: transaction.gasPrice,
estimatedFee: this.calculateFee(transaction)
};
}
calculateFee(transaction) {
const gasPrice = parseInt(transaction.gasPrice || '20000000000'); // 20 Gwei
const gasLimit = parseInt(transaction.gasLimit || '21000');
return (gasPrice * gasLimit) / 1e18; // 转换为ETH
}
}
3. 网络和合约安全:
// 网络安全管理
class NetworkSecurity {
constructor() {
this.trustedNetworks = {
'0x1': 'Ethereum Mainnet',
'0x89': 'Polygon Mainnet',
'0x38': 'BSC Mainnet'
};
this.knownScamContracts = new Set([
// 已知的恶意合约地址
'0xScamContract1...',
'0xScamContract2...'
]);
}
// 验证网络安全性
async validateNetwork(chainId) {
// 检查是否为受信任网络
if (!this.trustedNetworks[chainId]) {
return {
safe: false,
warning: '未知网络,请谨慎操作'
};
}
// 获取当前网络状态
const networkStatus = await this.getNetworkStatus(chainId);
return {
safe: networkStatus.healthy,
networkName: this.trustedNetworks[chainId],
status: networkStatus
};
}
// 检查合约安全性
async validateContract(contractAddress) {
// 检查黑名单
if (this.knownScamContracts.has(contractAddress.toLowerCase())) {
return {
safe: false,
risk: 'HIGH',
reason: '已知恶意合约'
};
}
// 验证合约代码
const contractCode = await this.getContractCode(contractAddress);
if (contractCode === '0x') {
return {
safe: false,
risk: 'MEDIUM',
reason: '目标地址不是合约'
};
}
return {
safe: true,
risk: 'LOW',
verified: await this.isVerifiedContract(contractAddress)
};
}
async getContractCode(address) {
try {
return await window.ethereum.request({
method: 'eth_getCode',
params: [address, 'latest']
});
} catch (error) {
console.error('获取合约代码失败:', error);
return '0x';
}
}
async getNetworkStatus(chainId) {
// 模拟网络健康检查
return {
healthy: true,
latency: '< 100ms',
lastBlock: Date.now()
};
}
async isVerifiedContract(address) {
// 检查合约是否在Etherscan等平台验证
// 这里需要调用相应的API
return true; // 简化示例
}
}
4. 用户数据保护:
// 数据保护措施
class DataProtection {
constructor() {
this.sensitiveFields = [
'privateKey', 'mnemonic', 'seed',
'password', 'pin', 'secret'
];
}
// 清理敏感数据
sanitizeData(data) {
const cleaned = { ...data };
this.sensitiveFields.forEach(field => {
if (cleaned[field]) {
delete cleaned[field];
}
});
return cleaned;
}
// 安全存储
secureStore(key, data, temporary = false) {
const sanitizedData = this.sanitizeData(data);
const storage = temporary ? sessionStorage : localStorage;
try {
storage.setItem(key, JSON.stringify(sanitizedData));
} catch (error) {
console.error('存储失败:', error);
}
}
// 清理存储
clearSensitiveStorage() {
// 清理可能包含敏感信息的存储
const keysToRemove = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (this.isSensitiveKey(key)) {
keysToRemove.push(key);
}
}
keysToRemove.forEach(key => {
localStorage.removeItem(key);
});
}
isSensitiveKey(key) {
return this.sensitiveFields.some(field =>
key.toLowerCase().includes(field)
);
}
}
安全最佳实践清单:
How to implement multi-wallet support? Design a universal wallet adapter.
How to implement multi-wallet support? Design a universal wallet adapter.
考察点:架构设计能力。
答案:
多钱包支持需要设计一个统一的适配器架构,将不同钱包的API差异抽象成通用接口。通过适配器模式和策略模式,可以实现对MetaMask、WalletConnect、Coinbase Wallet等多种钱包的统一管理,为上层应用提供一致的调用方式。
核心架构设计:
// 钱包接口定义
interface IWalletAdapter {
name: string;
connect(): Promise<string[]>;
disconnect(): Promise<void>;
getAccounts(): Promise<string[]>;
signMessage(message: string): Promise<string>;
sendTransaction(transaction: any): Promise<string>;
switchNetwork(chainId: string): Promise<boolean>;
addNetwork(network: any): Promise<boolean>;
isConnected(): boolean;
}
// 基础适配器抽象类
abstract class BaseWalletAdapter {
protected name: string;
protected connected: boolean = false;
protected accounts: string[] = [];
protected chainId: string | null = null;
protected listeners: Map<string, Function[]> = new Map();
constructor(name: string) {
this.name = name;
}
// 抽象方法,需要子类实现
abstract connect(): Promise<string[]>;
abstract disconnect(): Promise<void>;
abstract signMessage(message: string): Promise<string>;
abstract sendTransaction(transaction: any): Promise<string>;
// 通用事件管理
on(event: string, listener: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(listener);
}
off(event: string, listener: Function) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
const index = eventListeners.indexOf(listener);
if (index > -1) {
eventListeners.splice(index, 1);
}
}
}
protected emit(event: string, data: any) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.forEach(listener => listener(data));
}
}
isConnected(): boolean {
return this.connected;
}
getAccounts(): Promise<string[]> {
return Promise.resolve(this.accounts);
}
getCurrentChain(): string | null {
return this.chainId;
}
}
MetaMask适配器实现:
class MetaMaskAdapter extends BaseWalletAdapter {
private provider: any;
constructor() {
super('MetaMask');
this.provider = window.ethereum;
this.setupEventListeners();
}
static isAvailable(): boolean {
return typeof window.ethereum !== 'undefined' &&
window.ethereum.isMetaMask;
}
private setupEventListeners() {
if (!this.provider) return;
this.provider.on('accountsChanged', (accounts: string[]) => {
this.accounts = accounts;
this.connected = accounts.length > 0;
this.emit('accountsChanged', accounts);
});
this.provider.on('chainChanged', (chainId: string) => {
this.chainId = chainId;
this.emit('chainChanged', chainId);
});
this.provider.on('disconnect', () => {
this.connected = false;
this.accounts = [];
this.emit('disconnect', {});
});
}
async connect(): Promise<string[]> {
if (!this.provider) {
throw new Error('MetaMask not installed');
}
try {
const accounts = await this.provider.request({
method: 'eth_requestAccounts'
});
this.accounts = accounts;
this.connected = accounts.length > 0;
// 获取当前网络
this.chainId = await this.provider.request({
method: 'eth_chainId'
});
this.emit('connect', { accounts, chainId: this.chainId });
return accounts;
} catch (error) {
throw new Error(`MetaMask connection failed: ${error.message}`);
}
}
async disconnect(): Promise<void> {
this.connected = false;
this.accounts = [];
this.chainId = null;
this.emit('disconnect', {});
}
async signMessage(message: string): Promise<string> {
if (!this.connected || this.accounts.length === 0) {
throw new Error('Wallet not connected');
}
return await this.provider.request({
method: 'personal_sign',
params: [message, this.accounts[0]]
});
}
async sendTransaction(transaction: any): Promise<string> {
if (!this.connected) {
throw new Error('Wallet not connected');
}
return await this.provider.request({
method: 'eth_sendTransaction',
params: [transaction]
});
}
async switchNetwork(chainId: string): Promise<boolean> {
try {
await this.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId }]
});
return true;
} catch (error) {
if (error.code === 4902) {
// Network not added
return false;
}
throw error;
}
}
async addNetwork(network: any): Promise<boolean> {
try {
await this.provider.request({
method: 'wallet_addEthereumChain',
params: [network]
});
return true;
} catch (error) {
console.error('Failed to add network:', error);
return false;
}
}
}
WalletConnect适配器实现:
class WalletConnectAdapter extends BaseWalletAdapter {
private connector: any;
constructor() {
super('WalletConnect');
}
static isAvailable(): boolean {
// WalletConnect is always available as it's a protocol
return true;
}
async connect(): Promise<string[]> {
try {
// 动态导入WalletConnect
const { WalletConnect } = await import('@walletconnect/client');
const { QRCodeModal } = await import('@walletconnect/qrcode-modal');
this.connector = new WalletConnect({
bridge: 'https://bridge.walletconnect.org',
qrcodeModal: QRCodeModal
});
this.setupWalletConnectListeners();
if (this.connector.connected) {
this.accounts = this.connector.accounts;
this.chainId = `0x${this.connector.chainId.toString(16)}`;
this.connected = true;
return this.accounts;
}
// Create new session
await this.connector.createSession();
return new Promise((resolve, reject) => {
this.connector.on('connect', (error: any, payload: any) => {
if (error) {
reject(error);
return;
}
const { accounts, chainId } = payload.params[0];
this.accounts = accounts;
this.chainId = `0x${chainId.toString(16)}`;
this.connected = true;
this.emit('connect', { accounts, chainId: this.chainId });
resolve(accounts);
});
});
} catch (error) {
throw new Error(`WalletConnect connection failed: ${error.message}`);
}
}
private setupWalletConnectListeners() {
this.connector.on('session_update', (error: any, payload: any) => {
if (error) return;
const { accounts, chainId } = payload.params[0];
this.accounts = accounts;
this.chainId = `0x${chainId.toString(16)}`;
this.emit('accountsChanged', accounts);
this.emit('chainChanged', this.chainId);
});
this.connector.on('disconnect', () => {
this.connected = false;
this.accounts = [];
this.chainId = null;
this.emit('disconnect', {});
});
}
async disconnect(): Promise<void> {
if (this.connector) {
await this.connector.killSession();
}
this.connected = false;
this.accounts = [];
this.chainId = null;
this.emit('disconnect', {});
}
async signMessage(message: string): Promise<string> {
if (!this.connected) {
throw new Error('Wallet not connected');
}
const msgParams = {
data: message,
from: this.accounts[0]
};
return await this.connector.signPersonalMessage(msgParams);
}
async sendTransaction(transaction: any): Promise<string> {
if (!this.connected) {
throw new Error('Wallet not connected');
}
return await this.connector.sendTransaction(transaction);
}
async switchNetwork(chainId: string): Promise<boolean> {
// WalletConnect doesn't support network switching directly
// This would need to be handled by the connected wallet
console.warn('Network switching not supported in WalletConnect');
return false;
}
async addNetwork(network: any): Promise<boolean> {
console.warn('Adding network not supported in WalletConnect');
return false;
}
}
通用钱包管理器:
class MultiWalletManager {
private adapters: Map<string, BaseWalletAdapter> = new Map();
private currentAdapter: BaseWalletAdapter | null = null;
private listeners: Map<string, Function[]> = new Map();
constructor() {
this.registerAdapters();
}
private registerAdapters() {
// 注册可用的钱包适配器
if (MetaMaskAdapter.isAvailable()) {
this.adapters.set('metamask', new MetaMaskAdapter());
}
// WalletConnect is always available
this.adapters.set('walletconnect', new WalletConnectAdapter());
// Register other wallet adapters...
this.setupAdapterListeners();
}
private setupAdapterListeners() {
this.adapters.forEach((adapter) => {
adapter.on('connect', (data) => {
this.currentAdapter = adapter;
this.emit('walletConnected', {
wallet: adapter.name,
...data
});
});
adapter.on('disconnect', (data) => {
if (this.currentAdapter === adapter) {
this.currentAdapter = null;
}
this.emit('walletDisconnected', {
wallet: adapter.name,
...data
});
});
adapter.on('accountsChanged', (accounts) => {
this.emit('accountsChanged', {
wallet: adapter.name,
accounts
});
});
adapter.on('chainChanged', (chainId) => {
this.emit('chainChanged', {
wallet: adapter.name,
chainId
});
});
});
}
// 获取可用钱包列表
getAvailableWallets(): string[] {
return Array.from(this.adapters.keys());
}
// 连接指定钱包
async connect(walletName: string): Promise<string[]> {
const adapter = this.adapters.get(walletName);
if (!adapter) {
throw new Error(`Wallet ${walletName} not available`);
}
try {
const accounts = await adapter.connect();
this.currentAdapter = adapter;
return accounts;
} catch (error) {
throw new Error(`Failed to connect to ${walletName}: ${error.message}`);
}
}
// 断开当前钱包
async disconnect(): Promise<void> {
if (this.currentAdapter) {
await this.currentAdapter.disconnect();
this.currentAdapter = null;
}
}
// 获取当前连接的钱包
getCurrentWallet(): string | null {
return this.currentAdapter?.name || null;
}
// 获取账户
async getAccounts(): Promise<string[]> {
if (!this.currentAdapter) {
throw new Error('No wallet connected');
}
return await this.currentAdapter.getAccounts();
}
// 签名消息
async signMessage(message: string): Promise<string> {
if (!this.currentAdapter) {
throw new Error('No wallet connected');
}
return await this.currentAdapter.signMessage(message);
}
// 发送交易
async sendTransaction(transaction: any): Promise<string> {
if (!this.currentAdapter) {
throw new Error('No wallet connected');
}
return await this.currentAdapter.sendTransaction(transaction);
}
// 切换网络
async switchNetwork(chainId: string): Promise<boolean> {
if (!this.currentAdapter) {
throw new Error('No wallet connected');
}
return await this.currentAdapter.switchNetwork(chainId);
}
// 事件监听
on(event: string, listener: Function) {
if (!this.listeners.has(event)) {
this.listeners.set(event, []);
}
this.listeners.get(event)!.push(listener);
}
off(event: string, listener: Function) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
const index = eventListeners.indexOf(listener);
if (index > -1) {
eventListeners.splice(index, 1);
}
}
}
private emit(event: string, data: any) {
const eventListeners = this.listeners.get(event);
if (eventListeners) {
eventListeners.forEach(listener => listener(data));
}
}
// 检查连接状态
isConnected(): boolean {
return this.currentAdapter?.isConnected() || false;
}
// 获取当前网络
getCurrentChain(): string | null {
return this.currentAdapter?.getCurrentChain() || null;
}
}
// 使用示例
const walletManager = new MultiWalletManager();
// 设置事件监听
walletManager.on('walletConnected', (data) => {
console.log(`${data.wallet} connected:`, data.accounts);
});
walletManager.on('walletDisconnected', (data) => {
console.log(`${data.wallet} disconnected`);
});
// 连接钱包
async function connectWallet(walletType: string) {
try {
const accounts = await walletManager.connect(walletType);
console.log('Connected accounts:', accounts);
} catch (error) {
console.error('Connection failed:', error.message);
}
}
// 获取可用钱包
const availableWallets = walletManager.getAvailableWallets();
console.log('Available wallets:', availableWallets); // ['metamask', 'walletconnect']
架构优势:
How should wallet connection state be managed in the application?
How should wallet connection state be managed in the application?
考察点:状态管理最佳实践。
答案:
钱包连接状态管理是Web3应用的核心,需要在全局范围内维护连接状态、账户信息、网络状态等。通过状态管理库(如Redux、Zustand)或Context API,可以实现状态的集中管理和响应式更新,确保UI与钱包状态保持同步。
核心状态结构设计:
// 钱包状态接口定义
interface WalletState {
// 连接状态
isConnected: boolean;
isConnecting: boolean;
connectionError: string | null;
// 钱包信息
walletType: string | null;
accounts: string[];
currentAccount: string | null;
// 网络信息
chainId: string | null;
networkName: string | null;
isWrongNetwork: boolean;
// 余额信息
balance: string | null;
tokenBalances: Record<string, string>;
// 交易状态
pendingTransactions: Transaction[];
// 用户偏好
autoConnect: boolean;
preferredWallet: string | null;
}
interface Transaction {
hash: string;
status: 'pending' | 'confirmed' | 'failed';
type: string;
timestamp: number;
from: string;
to: string;
value: string;
}
使用Zustand的状态管理实现:
import { create } from 'zustand';
import { persist, subscribeWithSelector } from 'zustand/middleware';
// 钱包状态管理
interface WalletStore extends WalletState {
// Actions
setConnectionState: (state: Partial<WalletState>) => void;
connectWallet: (walletType: string) => Promise<void>;
disconnectWallet: () => Promise<void>;
updateAccount: (account: string) => void;
updateChain: (chainId: string) => void;
updateBalance: (balance: string) => void;
addTransaction: (transaction: Transaction) => void;
updateTransaction: (hash: string, updates: Partial<Transaction>) => void;
setError: (error: string | null) => void;
clearError: () => void;
reset: () => void;
}
const useWalletStore = create<WalletStore>()(
subscribeWithSelector(
persist(
(set, get) => ({
// 初始状态
isConnected: false,
isConnecting: false,
connectionError: null,
walletType: null,
accounts: [],
currentAccount: null,
chainId: null,
networkName: null,
isWrongNetwork: false,
balance: null,
tokenBalances: {},
pendingTransactions: [],
autoConnect: false,
preferredWallet: null,
// Actions
setConnectionState: (state) => {
set((prevState) => ({
...prevState,
...state
}));
},
connectWallet: async (walletType: string) => {
const { setConnectionState, setError, updateAccount, updateChain } = get();
try {
setConnectionState({
isConnecting: true,
connectionError: null
});
// 使用钱包管理器连接
const accounts = await walletManager.connect(walletType);
if (accounts.length > 0) {
const chainId = await walletManager.getCurrentChain();
setConnectionState({
isConnected: true,
isConnecting: false,
walletType: walletType,
accounts: accounts,
currentAccount: accounts[0],
chainId: chainId,
preferredWallet: walletType
});
// 更新账户相关信息
updateAccount(accounts[0]);
if (chainId) {
updateChain(chainId);
}
}
} catch (error) {
setError(error.message);
setConnectionState({
isConnecting: false,
connectionError: error.message
});
}
},
disconnectWallet: async () => {
try {
await walletManager.disconnect();
set({
isConnected: false,
isConnecting: false,
connectionError: null,
walletType: null,
accounts: [],
currentAccount: null,
chainId: null,
networkName: null,
balance: null,
tokenBalances: {},
pendingTransactions: []
});
} catch (error) {
console.error('Disconnect error:', error);
}
},
updateAccount: async (account: string) => {
set({ currentAccount: account });
// 获取余额
try {
const balance = await getAccountBalance(account);
set({ balance });
} catch (error) {
console.error('Failed to get balance:', error);
}
},
updateChain: (chainId: string) => {
const networkInfo = getNetworkInfo(chainId);
const supportedChains = ['0x1', '0x89', '0x38']; // ETH, Polygon, BSC
set({
chainId,
networkName: networkInfo.name,
isWrongNetwork: !supportedChains.includes(chainId)
});
},
updateBalance: (balance: string) => {
set({ balance });
},
addTransaction: (transaction: Transaction) => {
set((state) => ({
pendingTransactions: [...state.pendingTransactions, transaction]
}));
},
updateTransaction: (hash: string, updates: Partial<Transaction>) => {
set((state) => ({
pendingTransactions: state.pendingTransactions.map(tx =>
tx.hash === hash ? { ...tx, ...updates } : tx
)
}));
},
setError: (error: string | null) => {
set({ connectionError: error });
},
clearError: () => {
set({ connectionError: null });
},
reset: () => {
set({
isConnected: false,
isConnecting: false,
connectionError: null,
walletType: null,
accounts: [],
currentAccount: null,
chainId: null,
networkName: null,
isWrongNetwork: false,
balance: null,
tokenBalances: {},
pendingTransactions: []
});
}
}),
{
name: 'wallet-storage',
partialize: (state) => ({
// 只持久化部分状态
autoConnect: state.autoConnect,
preferredWallet: state.preferredWallet
})
}
)
)
);
// 网络信息辅助函数
function getNetworkInfo(chainId: string) {
const networks = {
'0x1': { name: 'Ethereum Mainnet', symbol: 'ETH' },
'0x89': { name: 'Polygon', symbol: 'MATIC' },
'0x38': { name: 'BSC', symbol: 'BNB' }
};
return networks[chainId] || { name: 'Unknown Network', symbol: 'Unknown' };
}
// 获取账户余额
async function getAccountBalance(account: string): Promise<string> {
try {
const balance = await window.ethereum.request({
method: 'eth_getBalance',
params: [account, 'latest']
});
// 转换为ETH单位
return (parseInt(balance, 16) / 1e18).toFixed(4);
} catch (error) {
console.error('Get balance error:', error);
return '0';
}
}
钱包事件监听和状态同步:
// 钱包事件监听器
class WalletEventHandler {
private store: any;
private unsubscribers: Function[] = [];
constructor(store: any) {
this.store = store;
this.setupEventListeners();
}
setupEventListeners() {
// 监听钱包管理器事件
walletManager.on('walletConnected', this.handleWalletConnected.bind(this));
walletManager.on('walletDisconnected', this.handleWalletDisconnected.bind(this));
walletManager.on('accountsChanged', this.handleAccountsChanged.bind(this));
walletManager.on('chainChanged', this.handleChainChanged.bind(this));
// 监听页面可见性变化
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
// 监听网络状态
window.addEventListener('online', this.handleNetworkOnline.bind(this));
window.addEventListener('offline', this.handleNetworkOffline.bind(this));
}
handleWalletConnected(data: any) {
const { setConnectionState, updateAccount, updateChain } = this.store.getState();
setConnectionState({
isConnected: true,
isConnecting: false,
walletType: data.wallet,
accounts: data.accounts,
currentAccount: data.accounts[0],
connectionError: null
});
if (data.accounts[0]) {
updateAccount(data.accounts[0]);
}
if (data.chainId) {
updateChain(data.chainId);
}
}
handleWalletDisconnected(data: any) {
const { setConnectionState } = this.store.getState();
setConnectionState({
isConnected: false,
accounts: [],
currentAccount: null,
chainId: null,
networkName: null,
balance: null
});
}
handleAccountsChanged(data: any) {
const { updateAccount, setConnectionState } = this.store.getState();
if (data.accounts.length === 0) {
// 用户断开了连接
setConnectionState({
isConnected: false,
accounts: [],
currentAccount: null
});
} else {
// 用户切换了账户
setConnectionState({
accounts: data.accounts,
currentAccount: data.accounts[0]
});
updateAccount(data.accounts[0]);
}
}
handleChainChanged(data: any) {
const { updateChain } = this.store.getState();
updateChain(data.chainId);
}
handleVisibilityChange() {
if (document.visibilityState === 'visible') {
// 页面重新可见时检查连接状态
this.checkConnectionState();
}
}
handleNetworkOnline() {
// 网络恢复时重新检查状态
this.checkConnectionState();
}
handleNetworkOffline() {
const { setError } = this.store.getState();
setError('网络连接已断开');
}
async checkConnectionState() {
const { isConnected } = this.store.getState();
if (isConnected) {
try {
const accounts = await walletManager.getAccounts();
if (accounts.length === 0) {
// 连接已断开
this.handleWalletDisconnected({});
}
} catch (error) {
console.error('Check connection state error:', error);
}
}
}
destroy() {
// 清理事件监听器
this.unsubscribers.forEach(unsubscribe => unsubscribe());
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
window.removeEventListener('online', this.handleNetworkOnline);
window.removeEventListener('offline', this.handleNetworkOffline);
}
}
// 初始化事件处理器
const eventHandler = new WalletEventHandler(useWalletStore);
React Hook封装:
// 自定义Hook封装钱包操作
import { useEffect, useCallback } from 'react';
export function useWallet() {
const {
isConnected,
isConnecting,
connectionError,
currentAccount,
chainId,
isWrongNetwork,
balance,
connectWallet,
disconnectWallet,
clearError,
autoConnect,
preferredWallet
} = useWalletStore();
// 自动连接逻辑
useEffect(() => {
if (autoConnect && preferredWallet && !isConnected && !isConnecting) {
connectWallet(preferredWallet).catch(console.error);
}
}, [autoConnect, preferredWallet, isConnected, isConnecting]);
// 连接钱包
const connect = useCallback(async (walletType: string) => {
try {
await connectWallet(walletType);
} catch (error) {
console.error('Connect error:', error);
}
}, [connectWallet]);
// 断开连接
const disconnect = useCallback(async () => {
try {
await disconnectWallet();
} catch (error) {
console.error('Disconnect error:', error);
}
}, [disconnectWallet]);
// 切换网络
const switchNetwork = useCallback(async (targetChainId: string) => {
try {
await walletManager.switchNetwork(targetChainId);
} catch (error) {
console.error('Switch network error:', error);
}
}, []);
return {
// 状态
isConnected,
isConnecting,
connectionError,
currentAccount,
chainId,
isWrongNetwork,
balance,
// 操作
connect,
disconnect,
switchNetwork,
clearError
};
}
// 使用示例
function WalletButton() {
const {
isConnected,
isConnecting,
currentAccount,
connect,
disconnect
} = useWallet();
if (isConnecting) {
return <button disabled>连接中...</button>;
}
if (isConnected) {
return (
<div>
<span>{currentAccount?.slice(0, 6)}...{currentAccount?.slice(-4)}</span>
<button onClick={disconnect}>断开连接</button>
</div>
);
}
return (
<button onClick={() => connect('metamask')}>
连接MetaMask
</button>
);
}
状态管理最佳实践:
How to implement automatic wallet reconnection? What factors need to be considered?
How to implement automatic wallet reconnection? What factors need to be considered?
考察点:用户体验优化。
答案:
钱包自动重连功能通过检测连接状态变化、存储连接偏好、实现重连策略等方式,在页面刷新或网络恢复时自动恢复钱包连接。需要考虑用户隐私、安全性、网络状态、重连时机等多个因素,提供流畅的用户体验。
核心重连策略实现:
class WalletAutoReconnect {
private maxRetries = 3;
private retryDelay = 2000;
private currentRetries = 0;
private reconnectTimer: NodeJS.Timeout | null = null;
private isReconnecting = false;
constructor(private walletStore: any) {
this.setupAutoReconnect();
}
setupAutoReconnect() {
// 页面加载时检查自动重连
window.addEventListener('load', this.handlePageLoad.bind(this));
// 网络状态恢复时重连
window.addEventListener('online', this.handleNetworkOnline.bind(this));
// 页面可见性变化时检查连接
document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
// 监听钱包断开事件
this.setupDisconnectionHandler();
}
private async handlePageLoad() {
const { autoConnect, preferredWallet } = this.walletStore.getState();
if (autoConnect && preferredWallet) {
await this.attemptReconnection(preferredWallet, 'PAGE_LOAD');
}
}
private async handleNetworkOnline() {
const { isConnected, preferredWallet } = this.walletStore.getState();
if (!isConnected && preferredWallet) {
await this.attemptReconnection(preferredWallet, 'NETWORK_RESTORED');
}
}
private async handleVisibilityChange() {
if (document.visibilityState === 'visible') {
await this.checkAndReconnect('PAGE_VISIBLE');
}
}
private setupDisconnectionHandler() {
this.walletStore.subscribe((state: any, prevState: any) => {
// 监听连接状态变化
if (prevState.isConnected && !state.isConnected) {
this.handleUnexpectedDisconnection();
}
});
}
private async handleUnexpectedDisconnection() {
const { preferredWallet, autoConnect } = this.walletStore.getState();
if (autoConnect && preferredWallet) {
// 延迟重连,避免立即重连导致的问题
setTimeout(() => {
this.attemptReconnection(preferredWallet, 'UNEXPECTED_DISCONNECT');
}, 1000);
}
}
async attemptReconnection(walletType: string, reason: string) {
if (this.isReconnecting) {
return false; // 避免重复重连
}
this.isReconnecting = true;
this.currentRetries = 0;
console.log(`Starting auto reconnection: ${reason}`);
try {
const success = await this.executeReconnection(walletType);
if (success) {
this.onReconnectionSuccess(walletType, reason);
return true;
} else {
this.onReconnectionFailure(walletType, reason);
return false;
}
} finally {
this.isReconnecting = false;
}
}
private async executeReconnection(walletType: string): Promise<boolean> {
while (this.currentRetries < this.maxRetries) {
try {
this.currentRetries++;
console.log(`Reconnection attempt ${this.currentRetries}/${this.maxRetries}`);
// 检查钱包是否可用
const isAvailable = await this.checkWalletAvailability(walletType);
if (!isAvailable) {
throw new Error('Wallet not available');
}
// 尝试静默连接(不弹窗)
const accounts = await this.silentConnect(walletType);
if (accounts && accounts.length > 0) {
return true;
}
throw new Error('No accounts returned');
} catch (error) {
console.error(`Reconnection attempt ${this.currentRetries} failed:`, error);
if (this.currentRetries < this.maxRetries) {
// 等待后重试
await this.delay(this.retryDelay * this.currentRetries);
}
}
}
return false;
}
private async checkWalletAvailability(walletType: string): Promise<boolean> {
switch (walletType) {
case 'metamask':
return typeof window.ethereum !== 'undefined' &&
window.ethereum.isMetaMask;
case 'walletconnect':
return true; // WalletConnect is always available
default:
return false;
}
}
private async silentConnect(walletType: string): Promise<string[] | null> {
try {
// 先检查是否已有权限
const accounts = await window.ethereum?.request({
method: 'eth_accounts'
});
if (accounts && accounts.length > 0) {
// 更新store状态
const chainId = await window.ethereum.request({
method: 'eth_chainId'
});
this.walletStore.getState().setConnectionState({
isConnected: true,
walletType: walletType,
accounts: accounts,
currentAccount: accounts[0],
chainId: chainId
});
return accounts;
}
return null;
} catch (error) {
console.error('Silent connect failed:', error);
return null;
}
}
private onReconnectionSuccess(walletType: string, reason: string) {
console.log(`Auto reconnection successful: ${walletType} (${reason})`);
// 重置重连计数
this.currentRetries = 0;
// 触发成功事件
this.walletStore.getState().setConnectionState({
connectionError: null
});
// 可以在这里添加用户通知
this.showReconnectionNotification('success', walletType);
}
private onReconnectionFailure(walletType: string, reason: string) {
console.log(`Auto reconnection failed: ${walletType} (${reason})`);
// 更新错误状态
this.walletStore.getState().setError('自动重连失败,请手动连接钱包');
// 显示重连失败通知
this.showReconnectionNotification('failed', walletType);
}
private showReconnectionNotification(type: 'success' | 'failed', walletType: string) {
const messages = {
success: `${walletType} 已自动重新连接`,
failed: `${walletType} 自动重连失败,请手动连接`
};
// 这里可以集成toast通知系统
console.log(messages[type]);
}
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async checkAndReconnect(reason: string) {
const { isConnected, preferredWallet, autoConnect } = this.walletStore.getState();
if (!isConnected && autoConnect && preferredWallet) {
// 验证当前连接状态
try {
const accounts = await window.ethereum?.request({
method: 'eth_accounts'
});
if (!accounts || accounts.length === 0) {
await this.attemptReconnection(preferredWallet, reason);
}
} catch (error) {
console.error('Check connection failed:', error);
}
}
}
// 手动触发重连
async manualReconnect(): Promise<boolean> {
const { preferredWallet } = this.walletStore.getState();
if (preferredWallet) {
return await this.attemptReconnection(preferredWallet, 'MANUAL_TRIGGER');
}
return false;
}
// 启用/禁用自动重连
setAutoReconnect(enabled: boolean, walletType?: string) {
this.walletStore.getState().setConnectionState({
autoConnect: enabled,
preferredWallet: walletType || this.walletStore.getState().preferredWallet
});
}
// 清理资源
destroy() {
window.removeEventListener('load', this.handlePageLoad);
window.removeEventListener('online', this.handleNetworkOnline);
document.removeEventListener('visibilitychange', this.handleVisibilityChange);
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
}
}
重连配置和策略管理:
interface ReconnectConfig {
enabled: boolean;
maxRetries: number;
retryDelay: number;
retryBackoff: boolean;
conditions: ReconnectCondition[];
}
type ReconnectCondition =
| 'PAGE_LOAD'
| 'NETWORK_RESTORED'
| 'PAGE_VISIBLE'
| 'UNEXPECTED_DISCONNECT';
class ReconnectConfigManager {
private config: ReconnectConfig = {
enabled: true,
maxRetries: 3,
retryDelay: 2000,
retryBackoff: true,
conditions: ['PAGE_LOAD', 'NETWORK_RESTORED', 'UNEXPECTED_DISCONNECT']
};
updateConfig(updates: Partial<ReconnectConfig>) {
this.config = { ...this.config, ...updates };
this.saveConfig();
}
shouldReconnect(condition: ReconnectCondition): boolean {
return this.config.enabled &&
this.config.conditions.includes(condition);
}
getRetryDelay(attempt: number): number {
if (this.config.retryBackoff) {
return this.config.retryDelay * Math.pow(2, attempt - 1);
}
return this.config.retryDelay;
}
private saveConfig() {
localStorage.setItem('wallet-reconnect-config', JSON.stringify(this.config));
}
private loadConfig() {
const saved = localStorage.getItem('wallet-reconnect-config');
if (saved) {
this.config = { ...this.config, ...JSON.parse(saved) };
}
}
}
What is EIP-1193? How does it standardize wallet interfaces?
What is EIP-1193? How does it standardize wallet interfaces?
考察点:标准协议理解。
答案:
EIP-1193是以太坊改进提案,定义了钱包提供商(Provider)的标准接口。它规范了DApp与钱包之间的通信方式,包括API方法、事件处理、错误码等,使得不同钱包都能提供一致的开发者体验。
EIP-1193核心接口定义:
// EIP-1193 Provider接口
interface EIP1193Provider {
// 核心请求方法
request(args: RequestArguments): Promise<any>;
// 事件监听
on(eventName: string, listener: Function): void;
removeListener(eventName: string, listener: Function): void;
// 连接状态检查
isConnected(): boolean;
}
interface RequestArguments {
method: string;
params?: Array<any> | Record<string, any>;
}
// 标准事件类型
type ProviderEvents =
| 'connect'
| 'disconnect'
| 'accountsChanged'
| 'chainChanged'
| 'message';
EIP-1193标准方法实现:
class EIP1193Provider {
private isConnectedState = false;
private chainId: string | null = null;
private accounts: string[] = [];
private eventEmitter = new EventTarget();
// 核心请求方法
async request(args: RequestArguments): Promise<any> {
const { method, params = [] } = args;
try {
switch (method) {
case 'eth_requestAccounts':
return await this.requestAccounts();
case 'eth_accounts':
return this.accounts;
case 'eth_chainId':
return this.chainId;
case 'eth_getBalance':
return await this.getBalance(params[0], params[1]);
case 'eth_sendTransaction':
return await this.sendTransaction(params[0]);
case 'personal_sign':
return await this.personalSign(params[0], params[1]);
case 'eth_signTypedData_v4':
return await this.signTypedData(params[0], params[1]);
case 'wallet_switchEthereumChain':
return await this.switchChain(params[0]);
case 'wallet_addEthereumChain':
return await this.addChain(params[0]);
default:
throw new ProviderRpcError(
-32601,
`The method ${method} does not exist/is not available`
);
}
} catch (error) {
if (error instanceof ProviderRpcError) {
throw error;
}
throw new ProviderRpcError(-32603, 'Internal error', error);
}
}
// 事件监听实现
on(eventName: string, listener: Function): void {
this.eventEmitter.addEventListener(eventName, listener as EventListener);
}
removeListener(eventName: string, listener: Function): void {
this.eventEmitter.removeEventListener(eventName, listener as EventListener);
}
// 发出事件
private emit(eventName: string, data: any): void {
const event = new CustomEvent(eventName, { detail: data });
this.eventEmitter.dispatchEvent(event);
}
// 连接状态
isConnected(): boolean {
return this.isConnectedState;
}
// 标准方法实现
private async requestAccounts(): Promise<string[]> {
// 实现账户请求逻辑
if (!this.isConnectedState) {
// 模拟用户授权流程
const granted = await this.requestUserPermission();
if (!granted) {
throw new ProviderRpcError(4001, 'User rejected the request');
}
this.isConnectedState = true;
this.accounts = ['0x742d35Cc6327C0532...'];
this.chainId = '0x1';
// 发出连接事件
this.emit('connect', { chainId: this.chainId });
this.emit('accountsChanged', this.accounts);
}
return this.accounts;
}
private async getBalance(address: string, blockTag: string): Promise<string> {
// 实现余额查询
return '0x1bc16d674ec80000'; // 2 ETH in hex
}
private async sendTransaction(transaction: any): Promise<string> {
// 验证交易参数
this.validateTransaction(transaction);
// 显示交易确认界面
const confirmed = await this.showTransactionConfirmation(transaction);
if (!confirmed) {
throw new ProviderRpcError(4001, 'User rejected the request');
}
// 返回交易哈希
return '0x' + Array(64).fill(0).map(() =>
Math.floor(Math.random() * 16).toString(16)
).join('');
}
private async personalSign(message: string, account: string): Promise<string> {
// 验证账户
if (!this.accounts.includes(account)) {
throw new ProviderRpcError(4100, 'The requested account is not available');
}
// 显示签名确认
const confirmed = await this.showSignConfirmation(message, account);
if (!confirmed) {
throw new ProviderRpcError(4001, 'User rejected the request');
}
// 返回签名结果
return '0x' + Array(130).fill(0).map(() =>
Math.floor(Math.random() * 16).toString(16)
).join('');
}
private async switchChain(chainParams: any): Promise<null> {
const { chainId } = chainParams;
if (this.chainId === chainId) {
return null; // 已经在目标网络
}
// 检查网络是否支持
if (!this.isSupportedChain(chainId)) {
throw new ProviderRpcError(4902, 'Unrecognized chain ID');
}
// 切换网络
const previousChainId = this.chainId;
this.chainId = chainId;
// 发出网络变化事件
this.emit('chainChanged', chainId);
return null;
}
private async addChain(chainParams: any): Promise<null> {
const { chainId, chainName, rpcUrls, nativeCurrency } = chainParams;
// 验证参数
if (!chainId || !chainName || !rpcUrls || !nativeCurrency) {
throw new ProviderRpcError(-32602, 'Invalid parameters');
}
// 显示添加网络确认
const confirmed = await this.showAddChainConfirmation(chainParams);
if (!confirmed) {
throw new ProviderRpcError(4001, 'User rejected the request');
}
// 添加网络成功
return null;
}
// 辅助方法
private validateTransaction(tx: any): void {
if (!tx.to && !tx.data) {
throw new ProviderRpcError(-32602, 'Invalid transaction params');
}
}
private async requestUserPermission(): Promise<boolean> {
// 模拟用户授权界面
return new Promise(resolve => {
setTimeout(() => resolve(true), 1000);
});
}
private async showTransactionConfirmation(tx: any): Promise<boolean> {
// 模拟交易确认界面
return new Promise(resolve => {
setTimeout(() => resolve(true), 1000);
});
}
private async showSignConfirmation(message: string, account: string): Promise<boolean> {
// 模拟签名确认界面
return new Promise(resolve => {
setTimeout(() => resolve(true), 1000);
});
}
private async showAddChainConfirmation(chainParams: any): Promise<boolean> {
// 模拟添加网络确认界面
return new Promise(resolve => {
setTimeout(() => resolve(true), 1000);
});
}
private isSupportedChain(chainId: string): boolean {
const supportedChains = ['0x1', '0x89', '0x38'];
return supportedChains.includes(chainId);
}
}
// 标准错误类
class ProviderRpcError extends Error {
public code: number;
public data?: any;
constructor(code: number, message: string, data?: any) {
super(message);
this.code = code;
this.data = data;
this.name = 'ProviderRpcError';
}
}
EIP-1193错误码标准:
// 标准错误码定义
const EIP1193_ERROR_CODES = {
// 用户拒绝请求
USER_REJECTED: 4001,
// 未授权访问请求的方法
UNAUTHORIZED: 4100,
// 不支持的方法
UNSUPPORTED_METHOD: 4200,
// 提供商断开连接
DISCONNECTED: 4900,
// 未识别的链ID
UNRECOGNIZED_CHAIN: 4902,
// RPC错误
PARSE_ERROR: -32700,
INVALID_REQUEST: -32600,
METHOD_NOT_FOUND: -32601,
INVALID_PARAMS: -32602,
INTERNAL_ERROR: -32603
};
// 错误处理工具
class EIP1193ErrorHandler {
static createError(code: number, message?: string, data?: any): ProviderRpcError {
const errorMessages = {
4001: 'User rejected the request',
4100: 'The requested account and/or method has not been authorized',
4200: 'The requested method is not supported',
4900: 'The provider is disconnected from all chains',
4902: 'Unrecognized chain ID',
-32700: 'Parse error',
-32600: 'Invalid Request',
-32601: 'Method not found',
-32602: 'Invalid params',
-32603: 'Internal error'
};
const errorMessage = message || errorMessages[code] || 'Unknown error';
return new ProviderRpcError(code, errorMessage, data);
}
static isUserRejection(error: any): boolean {
return error?.code === 4001;
}
static isMethodNotSupported(error: any): boolean {
return error?.code === 4200 || error?.code === -32601;
}
}
How to handle API differences between different wallets? Implement unified interface abstraction.
How to handle API differences between different wallets? Implement unified interface abstraction.
考察点:接口抽象设计。
答案:
不同钱包在API实现上存在差异,需要通过适配器模式和接口抽象来统一处理。核心思路是定义统一的接口规范,为每个钱包实现具体的适配器,处理API差异、参数格式、错误码等不同之处。
统一接口抽象设计:
// 统一钱包接口定义
interface UnifiedWalletInterface {
// 基础信息
readonly name: string;
readonly type: WalletType;
readonly version?: string;
// 连接管理
connect(): Promise<WalletConnectionResult>;
disconnect(): Promise<void>;
isConnected(): boolean;
// 账户操作
getAccounts(): Promise<string[]>;
getCurrentAccount(): Promise<string | null>;
// 网络操作
getCurrentNetwork(): Promise<NetworkInfo>;
switchNetwork(chainId: string): Promise<boolean>;
addNetwork(network: NetworkConfig): Promise<boolean>;
// 交易操作
sendTransaction(params: TransactionParams): Promise<string>;
signTransaction(params: TransactionParams): Promise<string>;
// 签名操作
signMessage(message: string, account?: string): Promise<string>;
signTypedData(typedData: TypedData, account?: string): Promise<string>;
// 事件监听
on(event: WalletEvent, listener: Function): void;
off(event: WalletEvent, listener: Function): void;
// 能力检测
supports(capability: WalletCapability): boolean;
}
// 类型定义
type WalletType = 'injected' | 'walletconnect' | 'hardware' | 'mobile';
type WalletEvent = 'connect' | 'disconnect' | 'accountsChanged' | 'chainChanged' | 'message';
type WalletCapability = 'signMessage' | 'signTypedData' | 'switchNetwork' | 'addNetwork';
interface WalletConnectionResult {
success: boolean;
accounts: string[];
chainId: string;
error?: string;
}
interface NetworkInfo {
chainId: string;
name: string;
symbol: string;
rpcUrl?: string;
blockExplorer?: string;
}
interface TransactionParams {
to: string;
value?: string;
data?: string;
gas?: string;
gasPrice?: string;
from?: string;
}
API差异处理适配器:
// MetaMask适配器 - 处理标准EIP-1193接口
class MetaMaskWalletAdapter implements UnifiedWalletInterface {
readonly name = 'MetaMask';
readonly type: WalletType = 'injected';
private provider = window.ethereum;
private eventListeners = new Map<string, Function[]>();
async connect(): Promise<WalletConnectionResult> {
try {
const accounts = await this.provider.request({
method: 'eth_requestAccounts'
});
const chainId = await this.provider.request({
method: 'eth_chainId'
});
return {
success: true,
accounts,
chainId
};
} catch (error) {
return {
success: false,
accounts: [],
chainId: '',
error: this.normalizeError(error).message
};
}
}
async signMessage(message: string, account?: string): Promise<string> {
const from = account || await this.getCurrentAccount();
if (!from) throw new Error('No account available');
return await this.provider.request({
method: 'personal_sign',
params: [message, from]
});
}
async signTypedData(typedData: TypedData, account?: string): Promise<string> {
const from = account || await this.getCurrentAccount();
if (!from) throw new Error('No account available');
return await this.provider.request({
method: 'eth_signTypedData_v4',
params: [from, JSON.stringify(typedData)]
});
}
supports(capability: WalletCapability): boolean {
const capabilities = {
'signMessage': true,
'signTypedData': true,
'switchNetwork': true,
'addNetwork': true
};
return capabilities[capability] || false;
}
private normalizeError(error: any): Error {
if (error.code === 4001) {
return new Error('User rejected the request');
}
return new Error(error.message || 'Unknown error');
}
}
// WalletConnect适配器 - 处理WalletConnect协议差异
class WalletConnectAdapter implements UnifiedWalletInterface {
readonly name = 'WalletConnect';
readonly type: WalletType = 'walletconnect';
private connector: any;
private eventListeners = new Map<string, Function[]>();
async connect(): Promise<WalletConnectionResult> {
try {
// WalletConnect v1.x
if (!this.connector) {
const { WalletConnect } = await import('@walletconnect/client');
this.connector = new WalletConnect({
bridge: 'https://bridge.walletconnect.org',
qrcodeModal: await import('@walletconnect/qrcode-modal')
});
}
if (this.connector.connected) {
return {
success: true,
accounts: this.connector.accounts,
chainId: `0x${this.connector.chainId.toString(16)}`
};
}
await this.connector.createSession();
return new Promise((resolve) => {
this.connector.on('connect', (error: any, payload: any) => {
if (error) {
resolve({
success: false,
accounts: [],
chainId: '',
error: error.message
});
} else {
const { accounts, chainId } = payload.params[0];
resolve({
success: true,
accounts,
chainId: `0x${chainId.toString(16)}`
});
}
});
});
} catch (error) {
return {
success: false,
accounts: [],
chainId: '',
error: error.message
};
}
}
async signMessage(message: string, account?: string): Promise<string> {
if (!this.connector?.connected) {
throw new Error('WalletConnect not connected');
}
const from = account || this.connector.accounts[0];
// WalletConnect使用不同的方法名
return await this.connector.signPersonalMessage({
data: message,
from: from
});
}
async signTypedData(typedData: TypedData, account?: string): Promise<string> {
if (!this.connector?.connected) {
throw new Error('WalletConnect not connected');
}
const from = account || this.connector.accounts[0];
// WalletConnect的类型化数据签名
return await this.connector.signTypedData({
data: typedData,
from: from
});
}
async sendTransaction(params: TransactionParams): Promise<string> {
if (!this.connector?.connected) {
throw new Error('WalletConnect not connected');
}
// WalletConnect参数格式适配
const wcParams = {
from: params.from || this.connector.accounts[0],
to: params.to,
value: params.value || '0x0',
data: params.data || '0x',
gas: params.gas,
gasPrice: params.gasPrice
};
return await this.connector.sendTransaction(wcParams);
}
supports(capability: WalletCapability): boolean {
// WalletConnect支持的功能有限
const capabilities = {
'signMessage': true,
'signTypedData': true,
'switchNetwork': false, // 依赖连接的钱包
'addNetwork': false
};
return capabilities[capability] || false;
}
}
// Coinbase Wallet适配器 - 处理Coinbase特有API
class CoinbaseWalletAdapter implements UnifiedWalletInterface {
readonly name = 'Coinbase Wallet';
readonly type: WalletType = 'injected';
private provider: any;
constructor() {
// Coinbase Wallet使用不同的注入对象
this.provider = window.ethereum?.isCoinbaseWallet ?
window.ethereum :
window.coinbaseWalletExtension;
}
async signMessage(message: string, account?: string): Promise<string> {
const from = account || await this.getCurrentAccount();
// Coinbase Wallet可能使用不同的方法
try {
return await this.provider.request({
method: 'personal_sign',
params: [message, from]
});
} catch (error) {
// 降级到eth_sign (不推荐,但作为备选)
if (error.code === -32601) {
return await this.provider.request({
method: 'eth_sign',
params: [from, message]
});
}
throw error;
}
}
async switchNetwork(chainId: string): Promise<boolean> {
try {
await this.provider.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId }]
});
return true;
} catch (error) {
// Coinbase Wallet特殊错误处理
if (error.code === 4902 || error.code === -32603) {
throw new Error('Network not supported by Coinbase Wallet');
}
throw error;
}
}
}
差异统一处理中心:
class WalletAPIUnifier {
private adapters = new Map<string, UnifiedWalletInterface>();
registerAdapter(name: string, adapter: UnifiedWalletInterface) {
this.adapters.set(name, adapter);
}
// 统一的方法调用处理
async executeMethod<T>(
walletName: string,
method: keyof UnifiedWalletInterface,
...args: any[]
): Promise<T> {
const adapter = this.adapters.get(walletName);
if (!adapter) {
throw new Error(`Wallet ${walletName} not registered`);
}
// 检查方法支持
if (typeof adapter[method] !== 'function') {
throw new Error(`Method ${method} not supported by ${walletName}`);
}
try {
return await (adapter[method] as Function).apply(adapter, args);
} catch (error) {
// 统一错误处理和转换
throw this.normalizeError(error, walletName, method);
}
}
// 批量操作处理
async executeBatch(operations: Array<{
wallet: string;
method: string;
args: any[];
}>) {
const results = await Promise.allSettled(
operations.map(op =>
this.executeMethod(op.wallet, op.method as any, ...op.args)
)
);
return results.map((result, index) => ({
operation: operations[index],
success: result.status === 'fulfilled',
data: result.status === 'fulfilled' ? result.value : undefined,
error: result.status === 'rejected' ? result.reason : undefined
}));
}
// 能力检测和降级处理
async signMessageWithFallback(
walletName: string,
message: string,
account?: string
): Promise<string> {
const adapter = this.adapters.get(walletName);
if (!adapter) {
throw new Error(`Wallet ${walletName} not registered`);
}
// 检查支持的签名方法
if (adapter.supports('signMessage')) {
return await adapter.signMessage(message, account);
} else {
// 降级处理或提供替代方案
throw new Error(`${walletName} does not support message signing`);
}
}
private normalizeError(error: any, walletName: string, method: string): Error {
// 统一不同钱包的错误格式
const normalizedError = new Error();
if (error.code === 4001 || error.code === 'ACTION_REJECTED') {
normalizedError.message = 'User rejected the request';
(normalizedError as any).code = 4001;
} else if (error.code === -32601 || error.message?.includes('not supported')) {
normalizedError.message = `Method ${method} not supported by ${walletName}`;
(normalizedError as any).code = -32601;
} else {
normalizedError.message = error.message || 'Unknown error';
(normalizedError as any).code = error.code || -32603;
}
return normalizedError;
}
}
// 使用示例
const unifier = new WalletAPIUnifier();
unifier.registerAdapter('metamask', new MetaMaskWalletAdapter());
unifier.registerAdapter('walletconnect', new WalletConnectAdapter());
unifier.registerAdapter('coinbase', new CoinbaseWalletAdapter());
// 统一调用
try {
const signature = await unifier.signMessageWithFallback(
'metamask',
'Hello World',
'0x123...'
);
console.log('Signature:', signature);
} catch (error) {
console.error('Sign failed:', error.message);
}
What is EIP-712 typed signing? How to implement structured data signing?
What is EIP-712 typed signing? How to implement structured data signing?
考察点:高级签名机制。
答案:
EIP-712定义了结构化数据的签名标准,允许用户签名复杂的数据结构而非简单字符串。它提供了类型安全、人类可读的签名体验,广泛应用于DeFi协议、NFT市场等场景,如授权交易、投票治理、订单签名等。
EIP-712数据结构定义:
// EIP-712基础结构
interface EIP712Domain {
name?: string; // DApp名称
version?: string; // 合约版本
chainId?: number; // 链ID
verifyingContract?: string; // 验证合约地址
salt?: string; // 随机盐值
}
interface TypedData {
types: Record<string, Array<{name: string, type: string}>>;
primaryType: string; // 主要类型名称
domain: EIP712Domain; // 域分隔符
message: Record<string, any>; // 实际消息数据
}
// 示例:代币授权签名
const permitTypedData: TypedData = {
types: {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
],
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
},
primaryType: 'Permit',
domain: {
name: 'MyToken',
version: '1',
chainId: 1,
verifyingContract: '0x1234567890123456789012345678901234567890'
},
message: {
owner: '0xowner...',
spender: '0xspender...',
value: '1000000000000000000', // 1 token
nonce: 0,
deadline: Math.floor(Date.now() / 1000) + 3600 // 1 hour
}
};
EIP-712签名实现:
class EIP712Signer {
constructor(private provider: any) {}
// 标准EIP-712签名
async signTypedData(typedData: TypedData, account: string): Promise<string> {
try {
// 使用eth_signTypedData_v4方法
const signature = await this.provider.request({
method: 'eth_signTypedData_v4',
params: [account, JSON.stringify(typedData)]
});
return signature;
} catch (error) {
// 降级处理
return await this.fallbackSignTypedData(typedData, account);
}
}
// 降级签名处理
async fallbackSignTypedData(typedData: TypedData, account: string): Promise<string> {
try {
// 尝试v3版本
return await this.provider.request({
method: 'eth_signTypedData_v3',
params: [account, JSON.stringify(typedData)]
});
} catch (error) {
// 最后降级到v1
return await this.provider.request({
method: 'eth_signTypedData',
params: [account, typedData]
});
}
}
// 构建类型化数据
buildTypedData(
domain: EIP712Domain,
types: Record<string, Array<{name: string, type: string}>>,
primaryType: string,
message: Record<string, any>
): TypedData {
// 确保包含EIP712Domain类型
const fullTypes = {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
].filter(field => domain[field.name as keyof EIP712Domain] !== undefined),
...types
};
return {
types: fullTypes,
primaryType,
domain,
message
};
}
// 验证类型化数据结构
validateTypedData(typedData: TypedData): boolean {
const { types, primaryType, domain, message } = typedData;
// 检查必需字段
if (!types || !primaryType || !domain || !message) {
return false;
}
// 检查primaryType是否存在于types中
if (!types[primaryType]) {
return false;
}
// 验证消息字段类型
const primaryTypeFields = types[primaryType];
for (const field of primaryTypeFields) {
if (message[field.name] === undefined) {
console.warn(`Missing field: ${field.name}`);
}
}
return true;
}
// 计算类型哈希
encodeType(primaryType: string, types: Record<string, Array<{name: string, type: string}>>): string {
const deps = this.findTypeDependencies(primaryType, types);
const depTypes = deps.sort().filter(type => type !== primaryType);
return primaryType + '(' +
types[primaryType].map(field => `${field.type} ${field.name}`).join(',') +
')' +
depTypes.map(type =>
type + '(' +
types[type].map(field => `${field.type} ${field.name}`).join(',') +
')'
).join('');
}
private findTypeDependencies(
primaryType: string,
types: Record<string, Array<{name: string, type: string}>>,
found: string[] = []
): string[] {
if (found.includes(primaryType) || !types[primaryType]) {
return found;
}
found.push(primaryType);
for (const field of types[primaryType]) {
const typeName = field.type.replace(/\[\]$/, '');
if (types[typeName]) {
this.findTypeDependencies(typeName, types, found);
}
}
return found;
}
}
常用EIP-712应用场景:
// 1. DEX订单签名
class DEXOrderSigner extends EIP712Signer {
async signOrder(orderData: any, account: string): Promise<string> {
const typedData = this.buildTypedData(
{
name: 'DEX Protocol',
version: '1.0',
chainId: 1,
verifyingContract: '0xDEXContract...'
},
{
Order: [
{ name: 'trader', type: 'address' },
{ name: 'baseToken', type: 'address' },
{ name: 'quoteToken', type: 'address' },
{ name: 'baseAmount', type: 'uint256' },
{ name: 'quoteAmount', type: 'uint256' },
{ name: 'expiration', type: 'uint256' },
{ name: 'nonce', type: 'uint256' }
]
},
'Order',
orderData
);
return await this.signTypedData(typedData, account);
}
}
// 2. 治理投票签名
class GovernanceVoteSigner extends EIP712Signer {
async signVote(voteData: any, account: string): Promise<string> {
const typedData = this.buildTypedData(
{
name: 'Governance',
version: '1',
chainId: await this.getCurrentChainId()
},
{
Vote: [
{ name: 'voter', type: 'address' },
{ name: 'proposalId', type: 'uint256' },
{ name: 'support', type: 'uint8' },
{ name: 'weight', type: 'uint256' },
{ name: 'nonce', type: 'uint256' }
]
},
'Vote',
voteData
);
return await this.signTypedData(typedData, account);
}
}
// 3. NFT铸造签名
class NFTMintSigner extends EIP712Signer {
async signMintAuthorization(mintData: any, account: string): Promise<string> {
const typedData = this.buildTypedData(
{
name: 'NFT Collection',
version: '1.0',
chainId: 1,
verifyingContract: '0xNFTContract...'
},
{
MintAuthorization: [
{ name: 'to', type: 'address' },
{ name: 'tokenId', type: 'uint256' },
{ name: 'uri', type: 'string' },
{ name: 'price', type: 'uint256' },
{ name: 'deadline', type: 'uint256' },
{ name: 'nonce', type: 'uint256' }
]
},
'MintAuthorization',
mintData
);
return await this.signTypedData(typedData, account);
}
}
签名验证和恢复:
class EIP712Verifier {
// 验证EIP-712签名
async verifySignature(
typedData: TypedData,
signature: string,
expectedSigner: string
): Promise<boolean> {
try {
// 使用ethers.js验证
const { ethers } = await import('ethers');
// 恢复签名者地址
const recoveredAddress = ethers.utils.verifyTypedData(
typedData.domain,
{ [typedData.primaryType]: typedData.types[typedData.primaryType] },
typedData.message,
signature
);
// 比较地址(忽略大小写)
return recoveredAddress.toLowerCase() === expectedSigner.toLowerCase();
} catch (error) {
console.error('签名验证失败:', error);
return false;
}
}
// 从签名恢复地址
async recoverSigner(typedData: TypedData, signature: string): Promise<string> {
const { ethers } = await import('ethers');
return ethers.utils.verifyTypedData(
typedData.domain,
{ [typedData.primaryType]: typedData.types[typedData.primaryType] },
typedData.message,
signature
);
}
// 生成签名哈希
async getSignatureHash(typedData: TypedData): Promise<string> {
const { ethers } = await import('ethers');
return ethers.utils._TypedDataEncoder.hash(
typedData.domain,
{ [typedData.primaryType]: typedData.types[typedData.primaryType] },
typedData.message
);
}
}
完整应用示例:
// 完整的EIP-712签名流程
class EIP712Application {
private signer: EIP712Signer;
private verifier: EIP712Verifier;
constructor(provider: any) {
this.signer = new EIP712Signer(provider);
this.verifier = new EIP712Verifier();
}
// 代币授权流程
async authorizeTokenTransfer(tokenData: any, account: string) {
try {
// 1. 构建类型化数据
const typedData = this.signer.buildTypedData(
{
name: tokenData.name,
version: '1',
chainId: tokenData.chainId,
verifyingContract: tokenData.address
},
{
Permit: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
},
'Permit',
{
owner: account,
spender: tokenData.spender,
value: tokenData.amount,
nonce: tokenData.nonce,
deadline: Math.floor(Date.now() / 1000) + 3600
}
);
// 2. 验证数据结构
if (!this.signer.validateTypedData(typedData)) {
throw new Error('Invalid typed data structure');
}
// 3. 请求用户签名
const signature = await this.signer.signTypedData(typedData, account);
// 4. 验证签名
const isValid = await this.verifier.verifySignature(
typedData,
signature,
account
);
if (!isValid) {
throw new Error('Signature verification failed');
}
return {
typedData,
signature,
hash: await this.verifier.getSignatureHash(typedData)
};
} catch (error) {
console.error('Authorization failed:', error);
throw error;
}
}
}
// 使用示例
const app = new EIP712Application(window.ethereum);
const authResult = await app.authorizeTokenTransfer({
name: 'USDC',
chainId: 1,
address: '0xA0b86a33E6441c6e851E6c287f2E89F4d4aa0e65',
spender: '0xSpenderContract...',
amount: '1000000', // 1 USDC
nonce: 0
}, '0xUserAccount...');
console.log('Authorization signature:', authResult.signature);
EIP-712优势:
How to synchronize wallet connection state across different devices?
How to synchronize wallet connection state across different devices?
考察点:跨设备状态同步。
答案:
跨设备钱包状态同步通过云端状态管理、设备指纹识别、实时通信等技术实现。核心挑战在于保护用户隐私的同时提供无缝体验,需要考虑安全性、一致性和实时性等因素。
同步架构设计:
// 跨设备状态同步管理器
class CrossDeviceWalletSync {
private syncService: SyncService;
private encryptionKey: CryptoKey | null = null;
private deviceId: string;
private userId: string | null = null;
constructor() {
this.syncService = new SyncService();
this.deviceId = this.generateDeviceFingerprint();
this.setupSyncListeners();
}
// 设备指纹生成
private generateDeviceFingerprint(): string {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx?.fillText('Device fingerprint', 10, 10);
const fingerprint = {
userAgent: navigator.userAgent,
language: navigator.language,
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
screen: `${screen.width}x${screen.height}`,
canvas: canvas.toDataURL(),
timestamp: Date.now()
};
return btoa(JSON.stringify(fingerprint)).substring(0, 32);
}
// 初始化用户会话
async initializeUserSession(walletAddress: string): Promise<void> {
try {
// 使用钱包地址作为用户标识
this.userId = walletAddress.toLowerCase();
// 生成会话密钥
this.encryptionKey = await this.generateSessionKey(walletAddress);
// 注册设备
await this.registerDevice();
// 拉取远程状态
await this.pullRemoteState();
} catch (error) {
console.error('Initialize user session failed:', error);
}
}
// 生成会话加密密钥
private async generateSessionKey(seed: string): Promise<CryptoKey> {
const encoder = new TextEncoder();
const data = encoder.encode(seed + this.deviceId);
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
return await crypto.subtle.importKey(
'raw',
hashBuffer,
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
}
// 注册设备到云端
private async registerDevice(): Promise<void> {
const deviceInfo = {
deviceId: this.deviceId,
userId: this.userId,
platform: navigator.platform,
timestamp: Date.now(),
lastActive: Date.now()
};
await this.syncService.registerDevice(deviceInfo);
}
// 同步钱包状态到云端
async syncWalletState(walletState: any): Promise<void> {
if (!this.userId || !this.encryptionKey) {
console.warn('User session not initialized');
return;
}
try {
// 过滤敏感数据
const sanitizedState = this.sanitizeWalletState(walletState);
// 加密状态数据
const encryptedState = await this.encryptData(sanitizedState);
// 上传到云端
await this.syncService.uploadState({
userId: this.userId,
deviceId: this.deviceId,
state: encryptedState,
timestamp: Date.now(),
version: this.generateStateVersion(sanitizedState)
});
} catch (error) {
console.error('Sync wallet state failed:', error);
}
}
// 从云端拉取状态
async pullRemoteState(): Promise<any> {
if (!this.userId || !this.encryptionKey) {
return null;
}
try {
const remoteState = await this.syncService.getState(this.userId);
if (remoteState && remoteState.state) {
// 解密状态数据
const decryptedState = await this.decryptData(remoteState.state);
// 验证状态完整性
if (this.validateStateIntegrity(decryptedState, remoteState.version)) {
return decryptedState;
}
}
return null;
} catch (error) {
console.error('Pull remote state failed:', error);
return null;
}
}
// 数据加密
private async encryptData(data: any): Promise<string> {
if (!this.encryptionKey) {
throw new Error('Encryption key not available');
}
const encoder = new TextEncoder();
const dataBuffer = encoder.encode(JSON.stringify(data));
const iv = crypto.getRandomValues(new Uint8Array(12));
const encryptedBuffer = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
this.encryptionKey,
dataBuffer
);
const combined = new Uint8Array(iv.length + encryptedBuffer.byteLength);
combined.set(iv);
combined.set(new Uint8Array(encryptedBuffer), iv.length);
return btoa(String.fromCharCode(...combined));
}
// 数据解密
private async decryptData(encryptedData: string): Promise<any> {
if (!this.encryptionKey) {
throw new Error('Encryption key not available');
}
const combined = Uint8Array.from(atob(encryptedData), c => c.charCodeAt(0));
const iv = combined.slice(0, 12);
const encrypted = combined.slice(12);
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
this.encryptionKey,
encrypted
);
const decoder = new TextDecoder();
const decryptedText = decoder.decode(decryptedBuffer);
return JSON.parse(decryptedText);
}
// 清理敏感数据
private sanitizeWalletState(state: any): any {
const sensitiveFields = [
'privateKey', 'mnemonic', 'seed', 'password'
];
const sanitized = { ...state };
// 移除敏感字段
sensitiveFields.forEach(field => {
delete sanitized[field];
});
// 只保留必要的状态信息
return {
isConnected: sanitized.isConnected,
walletType: sanitized.walletType,
currentAccount: sanitized.currentAccount,
chainId: sanitized.chainId,
networkName: sanitized.networkName,
lastConnected: Date.now(),
preferences: sanitized.preferences || {}
};
}
// 生成状态版本哈希
private generateStateVersion(state: any): string {
const stateString = JSON.stringify(state, Object.keys(state).sort());
return btoa(stateString).substring(0, 16);
}
// 验证状态完整性
private validateStateIntegrity(state: any, expectedVersion: string): boolean {
const currentVersion = this.generateStateVersion(state);
return currentVersion === expectedVersion;
}
// 设置同步监听器
private setupSyncListeners(): void {
// 监听页面可见性变化
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
this.handlePageVisible();
}
});
// 监听在线状态变化
window.addEventListener('online', () => {
this.handleNetworkOnline();
});
// 定期同步
setInterval(() => {
this.periodicSync();
}, 60000); // 每分钟同步一次
}
private async handlePageVisible(): Promise<void> {
// 页面重新可见时拉取最新状态
await this.pullRemoteState();
}
private async handleNetworkOnline(): Promise<void> {
// 网络恢复时同步状态
await this.pullRemoteState();
}
private async periodicSync(): Promise<void> {
// 定期同步当前状态
const currentState = this.getCurrentWalletState();
if (currentState) {
await this.syncWalletState(currentState);
}
}
private getCurrentWalletState(): any {
// 获取当前钱包状态的接口
// 这里需要与实际的钱包状态管理器集成
return null;
}
}
// 云端同步服务
class SyncService {
private apiEndpoint = 'https://api.walletSync.com';
private apiKey: string;
constructor() {
this.apiKey = process.env.WALLET_SYNC_API_KEY || '';
}
async registerDevice(deviceInfo: any): Promise<void> {
await this.makeRequest('POST', '/devices', deviceInfo);
}
async uploadState(stateData: any): Promise<void> {
await this.makeRequest('POST', '/sync/upload', stateData);
}
async getState(userId: string): Promise<any> {
return await this.makeRequest('GET', `/sync/${userId}`);
}
async getConnectedDevices(userId: string): Promise<any[]> {
return await this.makeRequest('GET', `/devices/${userId}`);
}
private async makeRequest(method: string, endpoint: string, data?: any): Promise<any> {
const response = await fetch(`${this.apiEndpoint}${endpoint}`, {
method,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${this.apiKey}`
},
body: data ? JSON.stringify(data) : undefined
});
if (!response.ok) {
throw new Error(`Sync service error: ${response.status}`);
}
return await response.json();
}
}
实时状态同步实现:
// WebSocket实时同步
class RealtimeWalletSync {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectAttempts = 5;
async connect(userId: string): Promise<void> {
try {
this.ws = new WebSocket(`wss://sync.walletapp.com/ws/${userId}`);
this.ws.onopen = () => {
console.log('Realtime sync connected');
this.reconnectAttempts = 0;
};
this.ws.onmessage = (event) => {
this.handleSyncMessage(JSON.parse(event.data));
};
this.ws.onclose = () => {
this.handleDisconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
} catch (error) {
console.error('Failed to connect realtime sync:', error);
}
}
private handleSyncMessage(message: any): void {
switch (message.type) {
case 'state_update':
this.handleStateUpdate(message.data);
break;
case 'device_connected':
this.handleDeviceConnected(message.data);
break;
case 'device_disconnected':
this.handleDeviceDisconnected(message.data);
break;
}
}
private handleStateUpdate(stateData: any): void {
// 更新本地状态
console.log('Received state update:', stateData);
// 触发本地状态更新事件
}
private handleDisconnect(): void {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
setTimeout(() => {
this.reconnectAttempts++;
// 重连逻辑
}, Math.pow(2, this.reconnectAttempts) * 1000);
}
}
sendStateUpdate(stateData: any): void {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
type: 'state_update',
data: stateData,
timestamp: Date.now()
}));
}
}
}
同步冲突解决:
// 状态冲突解决器
class StateMerger {
// 合并冲突状态
mergeStates(localState: any, remoteState: any): any {
// 基于时间戳的简单合并策略
const merged = { ...localState };
Object.keys(remoteState).forEach(key => {
if (this.shouldUseRemoteValue(localState[key], remoteState[key])) {
merged[key] = remoteState[key];
}
});
return merged;
}
private shouldUseRemoteValue(local: any, remote: any): boolean {
// 如果本地没有值,使用远程值
if (local === undefined || local === null) {
return true;
}
// 如果远程值更新,使用远程值
if (remote?.lastUpdated > local?.lastUpdated) {
return true;
}
return false;
}
}
隐私和安全考虑:
答案:
跨设备钱包状态同步通过云端状态管理、设备指纹识别、实时通信等技术实现。核心挑战在于保护用户隐私的同时提供无缝体验,需要考虑安全性、一致性和实时性等因素。
What are the best practices for mobile wallet integration? How to handle deep links?
What are the best practices for mobile wallet integration? How to handle deep links?
考察点:移动端适配策略。
答案:
移动端钱包集成需要考虑设备限制、用户体验和安全性等因素。深链接(Deep Link)是连接DApp和钱包应用的关键机制,通过URL Scheme或Universal Links实现应用间跳转和数据传递,提供无缝的移动端Web3体验。
深链接处理实现:
// 移动端深链接管理器
class MobileWalletDeepLink {
private supportedWallets: Map<string, WalletConfig> = new Map();
private currentPlatform: 'ios' | 'android' | 'web';
constructor() {
this.currentPlatform = this.detectPlatform();
this.initializeSupportedWallets();
this.setupDeepLinkHandlers();
}
// 检测平台
private detectPlatform(): 'ios' | 'android' | 'web' {
const userAgent = navigator.userAgent;
if (/iPad|iPhone|iPod/.test(userAgent)) return 'ios';
if (/Android/.test(userAgent)) return 'android';
return 'web';
}
// 初始化支持的钱包配置
private initializeSupportedWallets() {
this.supportedWallets.set('metamask', {
name: 'MetaMask',
schemes: {
ios: 'metamask://',
android: 'metamask://',
universalLink: 'https://metamask.app.link/'
},
appStore: 'https://apps.apple.com/app/metamask/id1438144202',
playStore: 'https://play.google.com/store/apps/details?id=io.metamask',
fallbackUrl: 'https://metamask.io/download/'
});
this.supportedWallets.set('trust', {
name: 'Trust Wallet',
schemes: {
ios: 'trust://',
android: 'trust://',
universalLink: 'https://link.trustwallet.com/'
},
appStore: 'https://apps.apple.com/app/trust-crypto-bitcoin-wallet/id1288339409',
playStore: 'https://play.google.com/store/apps/details?id=com.wallet.crypto.trustapp'
});
this.supportedWallets.set('coinbase', {
name: 'Coinbase Wallet',
schemes: {
ios: 'cbwallet://',
android: 'cbwallet://',
universalLink: 'https://go.cb-w.com/'
},
appStore: 'https://apps.apple.com/app/coinbase-wallet/id1278383455',
playStore: 'https://play.google.com/store/apps/details?id=org.toshi'
});
}
// 生成钱包调用链接
generateWalletLink(walletType: string, action: string, params: any): string {
const walletConfig = this.supportedWallets.get(walletType);
if (!walletConfig) {
throw new Error(`Unsupported wallet: ${walletType}`);
}
const baseUrl = this.getWalletBaseUrl(walletConfig);
const actionPath = this.buildActionPath(action, params);
return `${baseUrl}${actionPath}`;
}
// 获取钱包基础URL
private getWalletBaseUrl(config: WalletConfig): string {
// 优先使用Universal Link(更可靠)
if (config.schemes.universalLink) {
return config.schemes.universalLink;
}
// 降级到平台特定的URL Scheme
return config.schemes[this.currentPlatform] || config.schemes.ios;
}
// 构建操作路径
private buildActionPath(action: string, params: any): string {
const queryParams = new URLSearchParams();
switch (action) {
case 'connect':
queryParams.set('action', 'connect');
if (params.dappUrl) queryParams.set('dappUrl', params.dappUrl);
if (params.chainId) queryParams.set('chainId', params.chainId);
break;
case 'sign':
queryParams.set('action', 'sign');
queryParams.set('message', params.message);
queryParams.set('address', params.address);
break;
case 'transaction':
queryParams.set('action', 'send');
queryParams.set('to', params.to);
queryParams.set('value', params.value || '0');
if (params.data) queryParams.set('data', params.data);
if (params.gas) queryParams.set('gas', params.gas);
break;
}
// 添加回调URL
if (params.callbackUrl) {
queryParams.set('callback', params.callbackUrl);
}
return `dapp?${queryParams.toString()}`;
}
// 连接钱包
async connectWallet(walletType: string): Promise<void> {
if (!this.isWalletInstalled(walletType)) {
await this.promptWalletInstallation(walletType);
return;
}
const connectUrl = this.generateWalletLink(walletType, 'connect', {
dappUrl: window.location.origin,
chainId: '1',
callbackUrl: `${window.location.origin}/wallet-callback`
});
await this.openWalletApp(connectUrl, walletType);
}
// 检查钱包是否已安装
private isWalletInstalled(walletType: string): boolean {
const walletConfig = this.supportedWallets.get(walletType);
if (!walletConfig) return false;
// 在移动端,通过尝试打开URL Scheme来检测
if (this.currentPlatform !== 'web') {
return true; // 假设已安装,让系统处理未安装的情况
}
// 在桌面端,检查注入的钱包对象
return this.checkInjectedWallet(walletType);
}
// 检查注入钱包
private checkInjectedWallet(walletType: string): boolean {
switch (walletType) {
case 'metamask':
return !!(window as any).ethereum?.isMetaMask;
case 'coinbase':
return !!(window as any).ethereum?.isCoinbaseWallet;
default:
return false;
}
}
// 打开钱包应用
private async openWalletApp(url: string, walletType: string): Promise<void> {
const startTime = Date.now();
try {
// 尝试打开深链接
window.location.href = url;
// 检测是否成功打开应用
const checkAppOpened = () => {
const elapsed = Date.now() - startTime;
// 如果页面在短时间内失去焦点,说明应用可能已打开
return elapsed < 2000 && document.hidden;
};
// 等待一段时间检查应用是否打开
await new Promise(resolve => setTimeout(resolve, 1500));
if (!checkAppOpened()) {
// 应用可能未打开,显示备用选项
this.showWalletOptions(walletType);
}
} catch (error) {
console.error('Failed to open wallet app:', error);
this.showWalletOptions(walletType);
}
}
// 提示安装钱包
private async promptWalletInstallation(walletType: string): Promise<void> {
const walletConfig = this.supportedWallets.get(walletType);
if (!walletConfig) return;
const installUrl = this.getInstallUrl(walletConfig);
const shouldInstall = confirm(
`${walletConfig.name} is not installed. Would you like to install it?`
);
if (shouldInstall) {
window.open(installUrl, '_blank');
}
}
// 获取安装URL
private getInstallUrl(config: WalletConfig): string {
switch (this.currentPlatform) {
case 'ios':
return config.appStore || config.fallbackUrl;
case 'android':
return config.playStore || config.fallbackUrl;
default:
return config.fallbackUrl;
}
}
// 显示钱包选项
private showWalletOptions(walletType: string): void {
const walletConfig = this.supportedWallets.get(walletType);
if (!walletConfig) return;
// 创建选项对话框
const modal = this.createWalletModal(walletConfig);
document.body.appendChild(modal);
}
// 创建钱包选择模态框
private createWalletModal(config: WalletConfig): HTMLElement {
const modal = document.createElement('div');
modal.className = 'wallet-modal';
modal.innerHTML = `
<div class="wallet-modal-content">
<h3>Open ${config.name}</h3>
<p>Choose how to continue:</p>
<button class="wallet-option" data-action="retry">
Try Again
</button>
<button class="wallet-option" data-action="install">
Install ${config.name}
</button>
<button class="wallet-option" data-action="copy">
Copy Link
</button>
<button class="wallet-close">Close</button>
</div>
`;
// 添加事件监听
modal.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
if (target.classList.contains('wallet-close')) {
modal.remove();
} else if (target.classList.contains('wallet-option')) {
const action = target.dataset.action;
this.handleModalAction(action, config);
modal.remove();
}
});
return modal;
}
// 处理模态框操作
private handleModalAction(action: string | undefined, config: WalletConfig): void {
switch (action) {
case 'retry':
// 重试打开应用
break;
case 'install':
window.open(this.getInstallUrl(config), '_blank');
break;
case 'copy':
// 复制链接到剪贴板
break;
}
}
// 设置深链接处理器
private setupDeepLinkHandlers(): void {
// 监听页面可见性变化(应用回到前台)
document.addEventListener('visibilitychange', () => {
if (!document.hidden) {
this.handleAppResume();
}
});
// 监听URL参数(钱包回调)
this.handleWalletCallback();
}
// 处理应用恢复
private handleAppResume(): void {
// 检查是否有待处理的钱包操作
const pendingAction = localStorage.getItem('pendingWalletAction');
if (pendingAction) {
try {
const actionData = JSON.parse(pendingAction);
this.processPendingAction(actionData);
localStorage.removeItem('pendingWalletAction');
} catch (error) {
console.error('Failed to process pending action:', error);
}
}
}
// 处理钱包回调
private handleWalletCallback(): void {
const urlParams = new URLSearchParams(window.location.search);
const walletResponse = urlParams.get('walletResponse');
if (walletResponse) {
try {
const response = JSON.parse(decodeURIComponent(walletResponse));
this.processWalletResponse(response);
} catch (error) {
console.error('Failed to process wallet response:', error);
}
}
}
// 处理钱包响应
private processWalletResponse(response: any): void {
// 触发相应的事件
const event = new CustomEvent('walletResponse', {
detail: response
});
window.dispatchEvent(event);
}
// 处理待处理操作
private processPendingAction(actionData: any): void {
// 根据操作类型处理相应逻辑
console.log('Processing pending action:', actionData);
}
}
// 钱包配置接口
interface WalletConfig {
name: string;
schemes: {
ios: string;
android: string;
universalLink?: string;
};
appStore?: string;
playStore?: string;
fallbackUrl?: string;
}
移动端适配策略:
// 移动端钱包适配器
class MobileWalletAdapter {
private deepLinkManager: MobileWalletDeepLink;
private fallbackProvider: any = null;
constructor() {
this.deepLinkManager = new MobileWalletDeepLink();
this.setupMobileOptimizations();
}
// 移动端优化设置
private setupMobileOptimizations(): void {
// 禁用移动端的缩放
const viewport = document.querySelector('meta[name="viewport"]');
if (viewport) {
viewport.setAttribute('content',
'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
);
}
// 优化触摸体验
document.body.style.touchAction = 'manipulation';
// 监听网络状态
if ('onLine' in navigator) {
window.addEventListener('online', this.handleNetworkOnline.bind(this));
window.addEventListener('offline', this.handleNetworkOffline.bind(this));
}
}
// 网络状态处理
private handleNetworkOnline(): void {
console.log('Network restored');
// 重新尝试连接钱包
}
private handleNetworkOffline(): void {
console.log('Network lost');
// 显示离线提示
this.showOfflineMessage();
}
// 显示离线消息
private showOfflineMessage(): void {
const message = document.createElement('div');
message.className = 'offline-message';
message.textContent = 'No internet connection. Please check your network.';
document.body.appendChild(message);
setTimeout(() => {
message.remove();
}, 5000);
}
// 智能钱包检测
async detectAvailableWallets(): Promise<string[]> {
const availableWallets: string[] = [];
// 检测注入的钱包(WebView内)
if ((window as any).ethereum) {
if ((window as any).ethereum.isMetaMask) {
availableWallets.push('metamask');
}
if ((window as any).ethereum.isCoinbaseWallet) {
availableWallets.push('coinbase');
}
if ((window as any).ethereum.isTrustWallet) {
availableWallets.push('trust');
}
}
// 在移动端,假设流行钱包都可用(通过深链接)
if (availableWallets.length === 0 && this.isMobileDevice()) {
availableWallets.push('metamask', 'trust', 'coinbase');
}
return availableWallets;
}
// 检测是否为移动设备
private isMobileDevice(): boolean {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i
.test(navigator.userAgent);
}
// 智能连接策略
async smartConnect(preferredWallet?: string): Promise<any> {
const availableWallets = await this.detectAvailableWallets();
if (availableWallets.length === 0) {
throw new Error('No wallets available');
}
// 优先使用用户首选钱包
if (preferredWallet && availableWallets.includes(preferredWallet)) {
return await this.connectSpecificWallet(preferredWallet);
}
// 如果在钱包WebView内,直接使用注入的provider
if ((window as any).ethereum && !this.isMobileDevice()) {
return await this.connectInjectedWallet();
}
// 移动端使用深链接连接
if (this.isMobileDevice()) {
return await this.connectViaDeepLink(availableWallets[0]);
}
throw new Error('Unable to connect wallet');
}
// 连接特定钱包
private async connectSpecificWallet(walletType: string): Promise<any> {
if ((window as any).ethereum) {
return await this.connectInjectedWallet();
} else {
return await this.connectViaDeepLink(walletType);
}
}
// 连接注入钱包
private async connectInjectedWallet(): Promise<any> {
try {
const accounts = await (window as any).ethereum.request({
method: 'eth_requestAccounts'
});
return {
accounts,
provider: (window as any).ethereum
};
} catch (error) {
throw new Error('Failed to connect injected wallet');
}
}
// 通过深链接连接
private async connectViaDeepLink(walletType: string): Promise<any> {
await this.deepLinkManager.connectWallet(walletType);
// 等待钱包响应
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error('Wallet connection timeout'));
}, 30000);
window.addEventListener('walletResponse', (event: any) => {
clearTimeout(timeout);
const response = event.detail;
if (response.success) {
resolve(response);
} else {
reject(new Error(response.error || 'Connection failed'));
}
}, { once: true });
});
}
}
最佳实践要点:
How to implement permission management for wallet connections? Control access to different features.
How to implement permission management for wallet connections? Control access to different features.
考察点:权限控制设计。
答案:
钱包权限管理通过细粒度的权限控制系统,管理DApp对钱包功能的访问权限。包括账户访问、交易权限、签名权限、网络切换等,确保用户资产安全的同时提供良好的使用体验。
权限系统架构设计:
// 钱包权限管理系统
class WalletPermissionManager {
private permissions: Map<string, PermissionSet> = new Map();
private sessionPermissions: Map<string, SessionPermission> = new Map();
private permissionCallbacks: Map<string, Function[]> = new Map();
constructor() {
this.initializeDefaultPermissions();
this.setupPermissionStorage();
}
// 初始化默认权限
private initializeDefaultPermissions(): void {
const defaultPermissions: PermissionDefinition[] = [
{
id: 'account_access',
name: 'Account Access',
description: 'Access to wallet accounts',
category: 'basic',
required: true,
autoGrant: false
},
{
id: 'balance_read',
name: 'Balance Reading',
description: 'Read account balances',
category: 'basic',
required: false,
autoGrant: true
},
{
id: 'transaction_send',
name: 'Send Transactions',
description: 'Send blockchain transactions',
category: 'transaction',
required: false,
autoGrant: false
},
{
id: 'message_sign',
name: 'Message Signing',
description: 'Sign messages and typed data',
category: 'signature',
required: false,
autoGrant: false
},
{
id: 'network_switch',
name: 'Network Switching',
description: 'Switch blockchain networks',
category: 'network',
required: false,
autoGrant: false
},
{
id: 'token_approval',
name: 'Token Approvals',
description: 'Approve token spending',
category: 'transaction',
required: false,
autoGrant: false
}
];
this.registerPermissions(defaultPermissions);
}
// 注册权限定义
registerPermissions(permissions: PermissionDefinition[]): void {
permissions.forEach(permission => {
// 存储权限定义
this.storePermissionDefinition(permission);
});
}
// 请求权限
async requestPermissions(
origin: string,
requestedPermissions: string[],
context?: PermissionContext
): Promise<PermissionGrantResult> {
try {
// 验证请求来源
if (!this.isValidOrigin(origin)) {
throw new Error('Invalid origin');
}
// 获取现有权限
const existingPermissions = this.permissions.get(origin) || {
origin,
grants: new Map(),
createdAt: Date.now(),
lastUsed: Date.now()
};
const result: PermissionGrantResult = {
granted: [],
denied: [],
pending: []
};
// 处理每个权限请求
for (const permissionId of requestedPermissions) {
const grantResult = await this.processPermissionRequest(
origin,
permissionId,
existingPermissions,
context
);
switch (grantResult.status) {
case 'granted':
result.granted.push(permissionId);
break;
case 'denied':
result.denied.push(permissionId);
break;
case 'pending':
result.pending.push(permissionId);
break;
}
}
// 保存更新后的权限
this.permissions.set(origin, existingPermissions);
this.savePermissions();
return result;
} catch (error) {
throw new Error(`Permission request failed: ${error.message}`);
}
}
// 处理单个权限请求
private async processPermissionRequest(
origin: string,
permissionId: string,
permissionSet: PermissionSet,
context?: PermissionContext
): Promise<{ status: 'granted' | 'denied' | 'pending' }> {
// 检查是否已有权限
const existingGrant = permissionSet.grants.get(permissionId);
if (existingGrant && existingGrant.status === 'granted' &&
!this.isPermissionExpired(existingGrant)) {
return { status: 'granted' };
}
// 获取权限定义
const permission = this.getPermissionDefinition(permissionId);
if (!permission) {
return { status: 'denied' };
}
// 自动授权的权限
if (permission.autoGrant) {
permissionSet.grants.set(permissionId, {
permissionId,
status: 'granted',
grantedAt: Date.now(),
expiresAt: Date.now() + (24 * 60 * 60 * 1000), // 24小时
conditions: []
});
return { status: 'granted' };
}
// 需要用户确认的权限
const userResponse = await this.requestUserApproval(
origin,
permission,
context
);
const grant: PermissionGrant = {
permissionId,
status: userResponse.approved ? 'granted' : 'denied',
grantedAt: Date.now(),
expiresAt: userResponse.approved ?
(userResponse.duration ? Date.now() + userResponse.duration : null) :
null,
conditions: userResponse.conditions || []
};
permissionSet.grants.set(permissionId, grant);
return { status: grant.status };
}
// 请求用户授权
private async requestUserApproval(
origin: string,
permission: PermissionDefinition,
context?: PermissionContext
): Promise<UserApprovalResponse> {
return new Promise((resolve) => {
// 创建权限请求对话框
const modal = this.createPermissionModal(origin, permission, context);
// 处理用户响应
const handleResponse = (approved: boolean, options: any = {}) => {
modal.remove();
resolve({
approved,
duration: options.duration,
conditions: options.conditions
});
};
// 添加事件监听
modal.addEventListener('permission-response', (event: any) => {
handleResponse(event.detail.approved, event.detail.options);
});
document.body.appendChild(modal);
});
}
// 创建权限请求模态框
private createPermissionModal(
origin: string,
permission: PermissionDefinition,
context?: PermissionContext
): HTMLElement {
const modal = document.createElement('div');
modal.className = 'permission-modal';
modal.innerHTML = `
<div class="permission-modal-content">
<div class="permission-header">
<h3>Permission Request</h3>
<div class="origin-info">${this.formatOrigin(origin)}</div>
</div>
<div class="permission-details">
<div class="permission-icon">🔐</div>
<h4>${permission.name}</h4>
<p>${permission.description}</p>
${context ? `
<div class="permission-context">
<strong>Context:</strong> ${context.description}
</div>
` : ''}
</div>
<div class="permission-options">
<div class="duration-options">
<label>
<input type="radio" name="duration" value="session" checked>
This session only
</label>
<label>
<input type="radio" name="duration" value="24h">
24 hours
</label>
<label>
<input type="radio" name="duration" value="forever">
Forever (until revoked)
</label>
</div>
${this.renderPermissionConditions(permission)}
</div>
<div class="permission-actions">
<button class="btn-deny">Deny</button>
<button class="btn-allow">Allow</button>
</div>
</div>
`;
// 添加事件处理
this.setupModalEvents(modal);
return modal;
}
// 设置模态框事件
private setupModalEvents(modal: HTMLElement): void {
const allowBtn = modal.querySelector('.btn-allow') as HTMLButtonElement;
const denyBtn = modal.querySelector('.btn-deny') as HTMLButtonElement;
const durationInputs = modal.querySelectorAll('input[name="duration"]');
allowBtn.addEventListener('click', () => {
const selectedDuration = (modal.querySelector('input[name="duration"]:checked') as HTMLInputElement)?.value;
const duration = this.parseDuration(selectedDuration);
const event = new CustomEvent('permission-response', {
detail: {
approved: true,
options: { duration }
}
});
modal.dispatchEvent(event);
});
denyBtn.addEventListener('click', () => {
const event = new CustomEvent('permission-response', {
detail: { approved: false }
});
modal.dispatchEvent(event);
});
}
// 解析持续时间
private parseDuration(duration: string): number | null {
switch (duration) {
case 'session':
return null; // 会话级权限
case '24h':
return 24 * 60 * 60 * 1000;
case 'forever':
return null;
default:
return 60 * 60 * 1000; // 默认1小时
}
}
// 检查权限
checkPermission(origin: string, permissionId: string): boolean {
const permissionSet = this.permissions.get(origin);
if (!permissionSet) return false;
const grant = permissionSet.grants.get(permissionId);
if (!grant || grant.status !== 'granted') return false;
// 检查是否过期
if (this.isPermissionExpired(grant)) {
// 移除过期权限
permissionSet.grants.delete(permissionId);
this.savePermissions();
return false;
}
// 更新最后使用时间
permissionSet.lastUsed = Date.now();
return true;
}
// 权限拦截器
createPermissionInterceptor(origin: string): PermissionInterceptor {
return {
// 账户访问拦截
requestAccounts: async () => {
if (!this.checkPermission(origin, 'account_access')) {
const result = await this.requestPermissions(origin, ['account_access']);
if (!result.granted.includes('account_access')) {
throw new Error('Account access denied');
}
}
return this.getAuthorizedAccounts(origin);
},
// 交易发送拦截
sendTransaction: async (txData: any) => {
if (!this.checkPermission(origin, 'transaction_send')) {
const result = await this.requestPermissions(origin, ['transaction_send'], {
type: 'transaction',
description: `Send transaction to ${txData.to}`,
metadata: txData
});
if (!result.granted.includes('transaction_send')) {
throw new Error('Transaction permission denied');
}
}
return this.executeTransaction(origin, txData);
},
// 消息签名拦截
signMessage: async (message: string) => {
if (!this.checkPermission(origin, 'message_sign')) {
const result = await this.requestPermissions(origin, ['message_sign'], {
type: 'signature',
description: 'Sign message',
metadata: { message }
});
if (!result.granted.includes('message_sign')) {
throw new Error('Signature permission denied');
}
}
return this.executeSignature(origin, message);
},
// 网络切换拦截
switchNetwork: async (chainId: string) => {
if (!this.checkPermission(origin, 'network_switch')) {
const result = await this.requestPermissions(origin, ['network_switch'], {
type: 'network',
description: `Switch to network ${chainId}`,
metadata: { chainId }
});
if (!result.granted.includes('network_switch')) {
throw new Error('Network switch permission denied');
}
}
return this.executeNetworkSwitch(origin, chainId);
}
};
}
// 撤销权限
revokePermission(origin: string, permissionId?: string): void {
const permissionSet = this.permissions.get(origin);
if (!permissionSet) return;
if (permissionId) {
// 撤销特定权限
permissionSet.grants.delete(permissionId);
} else {
// 撤销所有权限
permissionSet.grants.clear();
}
this.savePermissions();
this.notifyPermissionChange(origin, permissionId);
}
// 获取权限列表
getPermissions(origin: string): PermissionInfo[] {
const permissionSet = this.permissions.get(origin);
if (!permissionSet) return [];
const result: PermissionInfo[] = [];
permissionSet.grants.forEach((grant, permissionId) => {
const definition = this.getPermissionDefinition(permissionId);
if (definition) {
result.push({
id: permissionId,
name: definition.name,
description: definition.description,
category: definition.category,
status: grant.status,
grantedAt: grant.grantedAt,
expiresAt: grant.expiresAt,
lastUsed: permissionSet.lastUsed
});
}
});
return result;
}
// 权限过期检查
private isPermissionExpired(grant: PermissionGrant): boolean {
if (!grant.expiresAt) return false;
return Date.now() > grant.expiresAt;
}
// 清理过期权限
cleanupExpiredPermissions(): void {
this.permissions.forEach((permissionSet, origin) => {
let hasChanges = false;
permissionSet.grants.forEach((grant, permissionId) => {
if (this.isPermissionExpired(grant)) {
permissionSet.grants.delete(permissionId);
hasChanges = true;
}
});
if (hasChanges) {
this.notifyPermissionChange(origin);
}
});
if (this.permissions.size > 0) {
this.savePermissions();
}
}
// 权限变更通知
private notifyPermissionChange(origin: string, permissionId?: string): void {
const callbacks = this.permissionCallbacks.get(origin) || [];
callbacks.forEach(callback => {
try {
callback({ origin, permissionId, type: 'revoked' });
} catch (error) {
console.error('Permission callback error:', error);
}
});
}
// 辅助方法
private isValidOrigin(origin: string): boolean {
try {
new URL(origin);
return true;
} catch {
return false;
}
}
private formatOrigin(origin: string): string {
try {
const url = new URL(origin);
return url.hostname;
} catch {
return origin;
}
}
private getPermissionDefinition(permissionId: string): PermissionDefinition | null {
// 从存储中获取权限定义
return null; // 实现省略
}
private storePermissionDefinition(permission: PermissionDefinition): void {
// 存储权限定义到本地存储
}
private setupPermissionStorage(): void {
// 从本地存储加载权限
this.loadPermissions();
// 定期清理过期权限
setInterval(() => {
this.cleanupExpiredPermissions();
}, 60000); // 每分钟检查一次
}
private loadPermissions(): void {
const stored = localStorage.getItem('walletPermissions');
if (stored) {
try {
const data = JSON.parse(stored);
this.permissions = new Map(Object.entries(data));
} catch (error) {
console.error('Failed to load permissions:', error);
}
}
}
private savePermissions(): void {
const data = Object.fromEntries(this.permissions);
localStorage.setItem('walletPermissions', JSON.stringify(data));
}
// 占位实现
private async getAuthorizedAccounts(origin: string): Promise<string[]> { return []; }
private async executeTransaction(origin: string, txData: any): Promise<string> { return ''; }
private async executeSignature(origin: string, message: string): Promise<string> { return ''; }
private async executeNetworkSwitch(origin: string, chainId: string): Promise<void> { }
private renderPermissionConditions(permission: PermissionDefinition): string { return ''; }
}
// 类型定义
interface PermissionDefinition {
id: string;
name: string;
description: string;
category: 'basic' | 'transaction' | 'signature' | 'network';
required: boolean;
autoGrant: boolean;
}
interface PermissionSet {
origin: string;
grants: Map<string, PermissionGrant>;
createdAt: number;
lastUsed: number;
}
interface PermissionGrant {
permissionId: string;
status: 'granted' | 'denied';
grantedAt: number;
expiresAt: number | null;
conditions: string[];
}
interface PermissionGrantResult {
granted: string[];
denied: string[];
pending: string[];
}
interface PermissionContext {
type: string;
description: string;
metadata?: any;
}
interface UserApprovalResponse {
approved: boolean;
duration?: number | null;
conditions?: string[];
}
interface PermissionInterceptor {
requestAccounts(): Promise<string[]>;
sendTransaction(txData: any): Promise<string>;
signMessage(message: string): Promise<string>;
switchNetwork(chainId: string): Promise<void>;
}
interface PermissionInfo {
id: string;
name: string;
description: string;
category: string;
status: 'granted' | 'denied';
grantedAt: number;
expiresAt: number | null;
lastUsed: number;
}
interface SessionPermission {
origin: string;
permissions: string[];
expiresAt: number;
}
权限控制最佳实践:
What is Account Abstraction? What impact does it have on wallet integration?
What is Account Abstraction? What impact does it have on wallet integration?
考察点:新技术趋势理解。
答案:
账户抽象(Account Abstraction,AA)是以太坊的重要升级,通过EIP-4337实现,将外部拥有账户(EOA)的功能抽象到智能合约层。它允许用户使用智能合约作为钱包账户,实现可编程的账户逻辑,支持Gas代付、多签验证、社交恢复等高级功能。
账户抽象核心概念:
// 账户抽象钱包接口
interface AbstractAccountWallet {
// 基础账户信息
getAccountAddress(): string;
getAccountType(): 'eoa' | 'smart-account';
// 智能账户特有功能
executeUserOperation(userOp: UserOperation): Promise<string>;
simulateUserOperation(userOp: UserOperation): Promise<SimulationResult>;
// Gas抽象
estimateUserOperationGas(userOp: UserOperation): Promise<GasEstimate>;
getSponsoredUserOperation(userOp: UserOperation): Promise<UserOperation>;
// 批量操作
createBatchUserOperation(calls: Call[]): Promise<UserOperation>;
// 权限管理
addSessionKey(sessionKey: SessionKey): Promise<void>;
revokeSessionKey(keyId: string): Promise<void>;
}
// UserOperation结构
interface UserOperation {
sender: string; // 智能账户地址
nonce: bigint; // 防重放随机数
initCode: string; // 账户创建代码(首次使用)
callData: string; // 执行数据
callGasLimit: bigint; // 执行Gas限制
verificationGasLimit: bigint; // 验证Gas限制
preVerificationGas: bigint; // 预验证Gas
maxFeePerGas: bigint; // 最大Gas费用
maxPriorityFeePerGas: bigint; // 最大优先费用
paymasterAndData: string; // Paymaster数据
signature: string; // 签名数据
}
// 会话密钥配置
interface SessionKey {
keyAddress: string;
permissions: KeyPermission[];
validUntil: number;
validAfter: number;
}
interface KeyPermission {
target: string; // 目标合约
selector: string; // 函数选择器
rules: PermissionRule[]; // 权限规则
}
智能账户钱包实现:
class SmartAccountWallet implements AbstractAccountWallet {
private provider: JsonRpcProvider;
private bundlerUrl: string;
private paymasterUrl: string;
private accountFactory: string;
private entryPoint: string;
private ownerKey: string;
private accountAddress: string | null = null;
constructor(config: SmartAccountConfig) {
this.provider = new JsonRpcProvider(config.rpcUrl);
this.bundlerUrl = config.bundlerUrl;
this.paymasterUrl = config.paymasterUrl;
this.accountFactory = config.accountFactory;
this.entryPoint = config.entryPoint;
this.ownerKey = config.ownerKey;
}
// 获取账户地址(计算确定性地址)
getAccountAddress(): string {
if (!this.accountAddress) {
this.accountAddress = this.calculateAccountAddress();
}
return this.accountAddress;
}
getAccountType(): 'eoa' | 'smart-account' {
return 'smart-account';
}
// 计算智能账户地址
private calculateAccountAddress(): string {
const initCode = this.getInitCode();
const salt = keccak256(this.ownerKey);
// 使用CREATE2计算地址
return getCreate2Address(
this.accountFactory,
salt,
keccak256(initCode)
);
}
// 获取初始化代码
private getInitCode(): string {
const factoryInterface = new Interface([
'function createAccount(address owner, uint256 salt) returns (address)'
]);
return concat([
this.accountFactory,
factoryInterface.encodeFunctionData('createAccount', [
this.ownerKey,
keccak256(this.ownerKey)
])
]);
}
// 执行UserOperation
async executeUserOperation(userOp: UserOperation): Promise<string> {
try {
// 1. 填充UserOperation
const filledUserOp = await this.fillUserOperation(userOp);
// 2. 签名UserOperation
const signedUserOp = await this.signUserOperation(filledUserOp);
// 3. 提交到Bundler
const userOpHash = await this.submitUserOperation(signedUserOp);
// 4. 等待执行结果
const receipt = await this.waitForUserOperationReceipt(userOpHash);
return receipt.transactionHash;
} catch (error) {
throw new Error(`UserOperation execution failed: ${error.message}`);
}
}
// 填充UserOperation
private async fillUserOperation(userOp: Partial<UserOperation>): Promise<UserOperation> {
const accountAddress = this.getAccountAddress();
// 检查账户是否已部署
const code = await this.provider.getCode(accountAddress);
const isDeployed = code !== '0x';
const filledUserOp: UserOperation = {
sender: accountAddress,
nonce: userOp.nonce || await this.getNonce(),
initCode: isDeployed ? '0x' : this.getInitCode(),
callData: userOp.callData || '0x',
callGasLimit: userOp.callGasLimit || 0n,
verificationGasLimit: userOp.verificationGasLimit || 0n,
preVerificationGas: userOp.preVerificationGas || 0n,
maxFeePerGas: userOp.maxFeePerGas || 0n,
maxPriorityFeePerGas: userOp.maxPriorityFeePerGas || 0n,
paymasterAndData: userOp.paymasterAndData || '0x',
signature: '0x'
};
// 估算Gas
if (filledUserOp.callGasLimit === 0n) {
const gasEstimate = await this.estimateUserOperationGas(filledUserOp);
filledUserOp.callGasLimit = gasEstimate.callGasLimit;
filledUserOp.verificationGasLimit = gasEstimate.verificationGasLimit;
filledUserOp.preVerificationGas = gasEstimate.preVerificationGas;
}
// 设置Gas价格
if (filledUserOp.maxFeePerGas === 0n) {
const feeData = await this.provider.getFeeData();
filledUserOp.maxFeePerGas = feeData.maxFeePerGas || parseUnits('20', 'gwei');
filledUserOp.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas || parseUnits('1', 'gwei');
}
return filledUserOp;
}
// 签名UserOperation
private async signUserOperation(userOp: UserOperation): Promise<UserOperation> {
const userOpHash = this.getUserOperationHash(userOp);
const signature = await this.signMessage(userOpHash);
return {
...userOp,
signature
};
}
// 计算UserOperation哈希
private getUserOperationHash(userOp: UserOperation): string {
const packedUserOp = packUserOperation(userOp);
const encoded = AbiCoder.defaultAbiCoder().encode(
['bytes32', 'address', 'uint256'],
[keccak256(packedUserOp), this.entryPoint, this.provider.network.chainId]
);
return keccak256(encoded);
}
// 提交UserOperation到Bundler
private async submitUserOperation(userOp: UserOperation): Promise<string> {
const response = await fetch(this.bundlerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_sendUserOperation',
params: [userOp, this.entryPoint]
})
});
const result = await response.json();
if (result.error) {
throw new Error(result.error.message);
}
return result.result;
}
// 等待UserOperation执行结果
private async waitForUserOperationReceipt(userOpHash: string): Promise<UserOperationReceipt> {
const maxRetries = 30;
const retryInterval = 2000;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(this.bundlerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'eth_getUserOperationReceipt',
params: [userOpHash]
})
});
const result = await response.json();
if (result.result) {
return result.result;
}
await new Promise(resolve => setTimeout(resolve, retryInterval));
} catch (error) {
if (i === maxRetries - 1) {
throw error;
}
}
}
throw new Error('UserOperation receipt timeout');
}
// Gas代付功能
async getSponsoredUserOperation(userOp: UserOperation): Promise<UserOperation> {
if (!this.paymasterUrl) {
return userOp;
}
try {
const response = await fetch(this.paymasterUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'pm_sponsorUserOperation',
params: [userOp, { type: 'payg' }] // Pay-as-you-go
})
});
const result = await response.json();
if (result.error) {
console.warn('Paymaster sponsorship failed:', result.error.message);
return userOp;
}
return {
...userOp,
paymasterAndData: result.result.paymasterAndData,
callGasLimit: BigInt(result.result.callGasLimit),
verificationGasLimit: BigInt(result.result.verificationGasLimit),
preVerificationGas: BigInt(result.result.preVerificationGas)
};
} catch (error) {
console.warn('Paymaster request failed:', error);
return userOp;
}
}
// 批量操作
async createBatchUserOperation(calls: Call[]): Promise<UserOperation> {
const accountInterface = new Interface([
'function executeBatch(address[] targets, uint256[] values, bytes[] calldatas)'
]);
const targets = calls.map(call => call.to);
const values = calls.map(call => call.value || 0);
const calldatas = calls.map(call => call.data);
const callData = accountInterface.encodeFunctionData('executeBatch', [
targets,
values,
calldatas
]);
return await this.fillUserOperation({ callData });
}
// 会话密钥管理
async addSessionKey(sessionKey: SessionKey): Promise<void> {
const accountInterface = new Interface([
'function addSessionKey(address key, uint48 validUntil, uint48 validAfter, bytes32[] permissions)'
]);
const permissionHashes = sessionKey.permissions.map(perm =>
keccak256(AbiCoder.defaultAbiCoder().encode(
['address', 'bytes4'],
[perm.target, perm.selector]
))
);
const callData = accountInterface.encodeFunctionData('addSessionKey', [
sessionKey.keyAddress,
sessionKey.validUntil,
sessionKey.validAfter,
permissionHashes
]);
const userOp = await this.fillUserOperation({ callData });
await this.executeUserOperation(userOp);
}
// 辅助方法
private async getNonce(): Promise<bigint> {
const entryPointInterface = new Interface([
'function getNonce(address sender, uint192 key) view returns (uint256)'
]);
const entryPointContract = new Contract(this.entryPoint, entryPointInterface, this.provider);
return await entryPointContract.getNonce(this.getAccountAddress(), 0);
}
private async signMessage(message: string): Promise<string> {
const wallet = new Wallet(this.ownerKey);
return await wallet.signMessage(arrayify(message));
}
async estimateUserOperationGas(userOp: UserOperation): Promise<GasEstimate> {
// Gas估算实现
return {
callGasLimit: parseUnits('100000', 'wei'),
verificationGasLimit: parseUnits('100000', 'wei'),
preVerificationGas: parseUnits('21000', 'wei')
};
}
async simulateUserOperation(userOp: UserOperation): Promise<SimulationResult> {
// 模拟执行实现
return {
success: true,
returnData: '0x',
gasUsed: 0n
};
}
}
// 智能账户钱包管理器
class SmartAccountWalletManager {
private wallets: Map<string, SmartAccountWallet> = new Map();
// 创建智能账户钱包
async createSmartWallet(config: SmartAccountConfig): Promise<SmartAccountWallet> {
const wallet = new SmartAccountWallet(config);
const address = wallet.getAccountAddress();
this.wallets.set(address, wallet);
return wallet;
}
// 检测钱包类型
async detectWalletType(address: string): Promise<'eoa' | 'smart-account'> {
const code = await this.provider.getCode(address);
return code === '0x' ? 'eoa' : 'smart-account';
}
// 适配不同账户类型
async createUniversalTransaction(params: TransactionParams): Promise<any> {
const walletType = await this.detectWalletType(params.from);
if (walletType === 'smart-account') {
// 使用UserOperation
const wallet = this.wallets.get(params.from);
if (!wallet) throw new Error('Smart wallet not found');
return await wallet.executeUserOperation({
sender: params.from,
callData: params.data,
// ... 其他参数
} as UserOperation);
} else {
// 使用传统交易
return await this.sendLegacyTransaction(params);
}
}
private async sendLegacyTransaction(params: TransactionParams): Promise<string> {
// 传统EOA交易实现
return '';
}
}
// 类型定义补充
interface SmartAccountConfig {
rpcUrl: string;
bundlerUrl: string;
paymasterUrl?: string;
accountFactory: string;
entryPoint: string;
ownerKey: string;
}
interface Call {
to: string;
value?: bigint;
data: string;
}
interface GasEstimate {
callGasLimit: bigint;
verificationGasLimit: bigint;
preVerificationGas: bigint;
}
interface SimulationResult {
success: boolean;
returnData: string;
gasUsed: bigint;
}
interface UserOperationReceipt {
userOpHash: string;
transactionHash: string;
blockNumber: number;
success: boolean;
}
interface TransactionParams {
from: string;
to: string;
value?: bigint;
data?: string;
gas?: bigint;
}
interface PermissionRule {
ruleType: 'allowlist' | 'gas-limit' | 'value-limit';
data: string;
}
// 辅助函数(占位实现)
function packUserOperation(userOp: UserOperation): string { return '0x'; }
function getCreate2Address(factory: string, salt: string, codeHash: string): string { return '0x'; }
function keccak256(data: string): string { return '0x'; }
function concat(arrays: string[]): string { return '0x'; }
function parseUnits(value: string, unit: string): bigint { return 0n; }
function arrayify(data: string): Uint8Array { return new Uint8Array(); }
对钱包集成的影响:
How to optimize the user experience of wallet interactions? Reduce user operation steps.
How to optimize the user experience of wallet interactions? Reduce user operation steps.
考察点:用户体验设计。
答案:
钱包交互用户体验优化通过简化流程、智能预判、批量操作、状态反馈等方式,减少用户操作步骤和认知负担。关键在于平衡安全性与便利性,提供直观、流畅的Web3交互体验。
用户体验优化策略:
// 钱包用户体验优化管理器
class WalletUXOptimizer {
private transactionBatcher: TransactionBatcher;
private gasOptimizer: GasOptimizer;
private stateManager: UXStateManager;
private preferencesManager: UserPreferencesManager;
constructor() {
this.transactionBatcher = new TransactionBatcher();
this.gasOptimizer = new GasOptimizer();
this.stateManager = new UXStateManager();
this.preferencesManager = new UserPreferencesManager();
}
// 智能交互流程优化
async optimizeInteractionFlow(interaction: UserInteraction): Promise<OptimizedFlow> {
const userPrefs = await this.preferencesManager.getUserPreferences();
const context = await this.analyzeInteractionContext(interaction);
return {
steps: await this.minimizeSteps(interaction, context, userPrefs),
batching: await this.identifyBatchingOpportunities(interaction),
gasOptimization: await this.optimizeGasUsage(interaction),
preApprovals: await this.suggestPreApprovals(interaction, userPrefs),
shortcuts: await this.createShortcuts(interaction, context)
};
}
// 步骤最小化
private async minimizeSteps(
interaction: UserInteraction,
context: InteractionContext,
preferences: UserPreferences
): Promise<OptimizedStep[]> {
const steps: OptimizedStep[] = [];
switch (interaction.type) {
case 'token_swap':
steps.push(...await this.optimizeSwapFlow(interaction, context, preferences));
break;
case 'nft_purchase':
steps.push(...await this.optimizeNFTFlow(interaction, context, preferences));
break;
case 'defi_interaction':
steps.push(...await this.optimizeDeFiFlow(interaction, context, preferences));
break;
default:
steps.push(...await this.optimizeGenericFlow(interaction, context, preferences));
}
return steps;
}
// 代币交换流程优化
private async optimizeSwapFlow(
interaction: UserInteraction,
context: InteractionContext,
preferences: UserPreferences
): Promise<OptimizedStep[]> {
const steps: OptimizedStep[] = [];
const swapParams = interaction.params as SwapParams;
// 1. 智能滑点设置
if (!swapParams.slippage && preferences.autoSlippage) {
swapParams.slippage = await this.calculateOptimalSlippage(swapParams);
}
// 2. 自动批准优化
const approvalStep = await this.optimizeApprovalStep(swapParams, preferences);
if (approvalStep) steps.push(approvalStep);
// 3. 交换执行
steps.push({
type: 'transaction',
title: `Swap ${swapParams.amountIn} ${swapParams.tokenIn.symbol} for ${swapParams.tokenOut.symbol}`,
description: 'Execute the token swap',
autoConfirm: preferences.autoConfirmSwaps && swapParams.amountUSD < preferences.autoConfirmLimit,
estimatedTime: 15,
gasEstimate: await this.gasOptimizer.estimateSwapGas(swapParams)
});
return steps;
}
// 审批步骤优化
private async optimizeApprovalStep(
params: SwapParams,
preferences: UserPreferences
): Promise<OptimizedStep | null> {
const currentAllowance = await this.checkTokenAllowance(
params.tokenIn.address,
params.spender
);
if (currentAllowance >= params.amountIn) {
return null; // 不需要审批
}
// 智能审批额度
const approvalAmount = preferences.infiniteApproval ?
ethers.constants.MaxUint256 :
params.amountIn * BigInt(preferences.approvalMultiplier || 2);
return {
type: 'approval',
title: `Approve ${params.tokenIn.symbol}`,
description: preferences.infiniteApproval ?
'Approve unlimited spending (one-time setup)' :
`Approve ${formatUnits(approvalAmount)} ${params.tokenIn.symbol}`,
autoConfirm: preferences.autoApprove && approvalAmount <= preferences.autoApprovalLimit,
estimatedTime: 10,
gasEstimate: await this.gasOptimizer.estimateApprovalGas(params.tokenIn.address),
params: { token: params.tokenIn, amount: approvalAmount, spender: params.spender }
};
}
// 批量操作识别
async identifyBatchingOpportunities(interaction: UserInteraction): Promise<BatchingOpportunity[]> {
const opportunities: BatchingOpportunity[] = [];
const pendingOperations = this.stateManager.getPendingOperations();
// 检查相同类型操作
const similarOps = pendingOperations.filter(op =>
op.type === interaction.type &&
op.targetContract === interaction.targetContract
);
if (similarOps.length > 0) {
opportunities.push({
type: 'same_contract',
operations: [interaction, ...similarOps],
gasSavings: await this.calculateBatchGasSavings(similarOps),
description: `Batch ${similarOps.length + 1} operations to save gas`
});
}
// 检查相关操作(如 approve + swap)
const relatedOps = await this.findRelatedOperations(interaction, pendingOperations);
if (relatedOps.length > 0) {
opportunities.push({
type: 'related_operations',
operations: [interaction, ...relatedOps],
gasSavings: await this.calculateBatchGasSavings(relatedOps),
description: 'Combine related operations in one transaction'
});
}
return opportunities;
}
// Gas使用优化
async optimizeGasUsage(interaction: UserInteraction): Promise<GasOptimization> {
const gasEstimate = await this.gasOptimizer.estimateGas(interaction);
const gasPrice = await this.gasOptimizer.getOptimalGasPrice();
const alternativeRoutes = await this.findAlternativeRoutes(interaction);
return {
standard: gasEstimate,
optimized: await this.gasOptimizer.optimizeGasUsage(interaction),
alternatives: alternativeRoutes,
recommendations: await this.generateGasRecommendations(interaction, gasEstimate)
};
}
// 预批准建议
async suggestPreApprovals(
interaction: UserInteraction,
preferences: UserPreferences
): Promise<PreApprovalSuggestion[]> {
if (!preferences.enablePreApprovals) return [];
const suggestions: PreApprovalSuggestion[] = [];
const userHistory = await this.preferencesManager.getUserInteractionHistory();
// 分析常用合约
const frequentContracts = this.analyzeFrequentContracts(userHistory);
for (const contract of frequentContracts) {
const tokens = await this.getTokensUsedWithContract(contract, userHistory);
for (const token of tokens) {
const currentAllowance = await this.checkTokenAllowance(token.address, contract.address);
if (currentAllowance === BigInt(0)) {
suggestions.push({
token,
spender: contract,
reason: `You frequently use ${token.symbol} with ${contract.name}`,
estimatedGasSavings: await this.calculateApprovalGasSavings(token, contract),
riskLevel: this.assessApprovalRisk(token, contract)
});
}
}
}
return suggestions;
}
}
// 交互流程简化器
class InteractionFlowSimplifier {
// 一键操作封装
async createOneClickOperation(params: OneClickParams): Promise<OneClickOperation> {
const steps = await this.analyzeRequiredSteps(params);
const optimizedSteps = await this.optimizeStepOrder(steps);
const batchedSteps = await this.batchCompatibleSteps(optimizedSteps);
return {
id: this.generateOperationId(),
title: params.title,
description: params.description,
steps: batchedSteps,
totalEstimatedTime: batchedSteps.reduce((sum, step) => sum + step.estimatedTime, 0),
totalGasEstimate: batchedSteps.reduce((sum, step) => sum + step.gasEstimate.total, BigInt(0)),
riskAssessment: await this.assessOperationRisk(batchedSteps)
};
}
// 智能默认值填充
async fillSmartDefaults(params: Partial<TransactionParams>): Promise<TransactionParams> {
const userPrefs = await this.getUserPreferences();
const marketData = await this.getMarketData();
return {
...params,
gasLimit: params.gasLimit || await this.estimateGasLimit(params),
gasPrice: params.gasPrice || await this.getOptimalGasPrice(userPrefs.gasPriceStrategy),
slippage: params.slippage || this.calculateDefaultSlippage(params, marketData),
deadline: params.deadline || (Date.now() + userPrefs.defaultDeadline),
maxFeePerGas: params.maxFeePerGas || await this.calculateMaxFee(userPrefs),
maxPriorityFeePerGas: params.maxPriorityFeePerGas || await this.calculatePriorityFee(userPrefs)
};
}
// 渐进式披露
createProgressiveDisclosure(operation: OneClickOperation): ProgressiveDisclosure {
return {
summary: {
title: operation.title,
description: operation.description,
estimatedTime: operation.totalEstimatedTime,
estimatedCost: operation.totalGasEstimate
},
basic: {
steps: operation.steps.map(step => ({
title: step.title,
description: step.description,
status: 'pending'
}))
},
advanced: {
gasDetails: operation.steps.map(step => step.gasEstimate),
riskAnalysis: operation.riskAssessment,
technicalDetails: operation.steps.map(step => step.technicalDetails)
},
expert: {
rawTransactionData: operation.steps.map(step => step.rawData),
contractInteractions: operation.steps.map(step => step.contractDetails),
securityAudit: operation.riskAssessment.detailedAnalysis
}
};
}
}
// 状态和进度管理
class UXStateManager {
private operationStates = new Map<string, OperationState>();
private progressCallbacks = new Map<string, Function[]>();
// 操作状态跟踪
trackOperation(operation: OneClickOperation): OperationTracker {
const tracker: OperationTracker = {
id: operation.id,
startTime: Date.now(),
currentStep: 0,
totalSteps: operation.steps.length,
status: 'pending',
onProgress: (callback: ProgressCallback) => {
const callbacks = this.progressCallbacks.get(operation.id) || [];
callbacks.push(callback);
this.progressCallbacks.set(operation.id, callbacks);
},
updateProgress: (stepIndex: number, stepStatus: StepStatus, data?: any) => {
const state = this.operationStates.get(operation.id) || {
operation,
currentStep: 0,
stepStates: new Map(),
startTime: Date.now()
};
state.currentStep = stepIndex;
state.stepStates.set(stepIndex, { status: stepStatus, data, timestamp: Date.now() });
this.operationStates.set(operation.id, state);
// 通知进度回调
const callbacks = this.progressCallbacks.get(operation.id) || [];
callbacks.forEach(callback => {
try {
callback({
operationId: operation.id,
currentStep: stepIndex,
totalSteps: operation.steps.length,
stepStatus,
progress: (stepIndex + 1) / operation.steps.length,
data
});
} catch (error) {
console.error('Progress callback error:', error);
}
});
},
complete: (success: boolean, result?: any) => {
const state = this.operationStates.get(operation.id);
if (state) {
state.status = success ? 'completed' : 'failed';
state.endTime = Date.now();
state.result = result;
}
// 清理回调
this.progressCallbacks.delete(operation.id);
}
};
return tracker;
}
// 获取待处理操作
getPendingOperations(): UserInteraction[] {
const pending: UserInteraction[] = [];
this.operationStates.forEach((state, operationId) => {
if (state.status === 'pending' || state.status === 'in_progress') {
pending.push(state.operation as any); // 类型转换简化
}
});
return pending;
}
}
// 用户偏好管理
class UserPreferencesManager {
private preferences: UserPreferences;
constructor() {
this.loadPreferences();
}
async getUserPreferences(): Promise<UserPreferences> {
return this.preferences;
}
async updatePreferences(updates: Partial<UserPreferences>): Promise<void> {
this.preferences = { ...this.preferences, ...updates };
await this.savePreferences();
}
// 学习用户行为
async learnFromUserBehavior(interaction: CompletedInteraction): Promise<void> {
// 分析用户选择模式
const patterns = this.analyzeUserPatterns([interaction]);
// 更新自动化偏好
if (patterns.consistentGasChoice) {
this.preferences.gasPriceStrategy = patterns.preferredGasStrategy;
}
if (patterns.consistentSlippage) {
this.preferences.defaultSlippage = patterns.averageSlippage;
}
// 更新风险偏好
if (patterns.riskTolerance) {
this.preferences.riskTolerance = patterns.riskTolerance;
}
await this.savePreferences();
}
private loadPreferences(): void {
const stored = localStorage.getItem('wallet-ux-preferences');
this.preferences = stored ? JSON.parse(stored) : this.getDefaultPreferences();
}
private async savePreferences(): Promise<void> {
localStorage.setItem('wallet-ux-preferences', JSON.stringify(this.preferences));
}
private getDefaultPreferences(): UserPreferences {
return {
autoSlippage: true,
autoConfirmSwaps: false,
autoConfirmLimit: 100, // USD
autoApprove: false,
autoApprovalLimit: BigInt(1000),
infiniteApproval: false,
approvalMultiplier: 2,
gasPriceStrategy: 'standard',
defaultDeadline: 20 * 60 * 1000, // 20 minutes
enablePreApprovals: true,
riskTolerance: 'medium',
defaultSlippage: 0.5 // 0.5%
};
}
async getUserInteractionHistory(): Promise<InteractionHistory[]> {
// 从本地存储或服务器获取交互历史
return [];
}
}
用户体验最佳实践:
How to handle network delays and timeouts in wallet integration?
How to handle network delays and timeouts in wallet integration?
考察点:网络异常处理。
答案:
钱包集成的网络延迟和超时处理通过多层次的策略实现:超时配置、重试机制、降级策略、用户反馈等。关键在于平衡响应速度与可靠性,为用户提供稳定的Web3交互体验。
网络异常处理架构:
// 网络延迟和超时处理管理器
class NetworkResilienceManager {
private config: NetworkConfig;
private retryPolicy: RetryPolicy;
private timeoutManager: TimeoutManager;
private networkMonitor: NetworkMonitor;
private fallbackProviders: Map<string, FallbackProvider>;
constructor(config: NetworkConfig) {
this.config = config;
this.retryPolicy = new RetryPolicy(config.retry);
this.timeoutManager = new TimeoutManager(config.timeouts);
this.networkMonitor = new NetworkMonitor();
this.fallbackProviders = new Map();
this.setupNetworkMonitoring();
}
// 带超时的请求处理
async executeWithTimeout<T>(
operation: () => Promise<T>,
timeout: number,
operationType: string
): Promise<T> {
const operationId = this.generateOperationId();
try {
// 设置超时控制
const timeoutPromise = this.timeoutManager.createTimeoutPromise(timeout, operationType);
// 执行操作
const result = await Promise.race([
operation(),
timeoutPromise
]);
// 记录成功
this.networkMonitor.recordSuccess(operationType, Date.now());
return result;
} catch (error) {
// 记录失败
this.networkMonitor.recordFailure(operationType, error);
// 判断是否为超时错误
if (this.isTimeoutError(error)) {
throw new WalletTimeoutError(
`Operation ${operationType} timed out after ${timeout}ms`,
operationType,
timeout
);
}
throw error;
}
}
// 带重试的网络请求
async requestWithRetry<T>(
requestFn: () => Promise<T>,
options: RequestOptions
): Promise<T> {
const { operationType, timeout, retryConfig } = options;
const effectiveRetryConfig = retryConfig || this.retryPolicy.getConfig(operationType);
let lastError: Error;
let attempt = 0;
while (attempt <= effectiveRetryConfig.maxRetries) {
try {
// 应用退避延迟
if (attempt > 0) {
const delay = this.calculateBackoffDelay(attempt, effectiveRetryConfig);
await this.delay(delay);
}
// 执行请求
const result = await this.executeWithTimeout(
requestFn,
timeout || this.config.timeouts.default,
operationType
);
return result;
} catch (error) {
lastError = error;
attempt++;
// 检查是否应该重试
if (!this.shouldRetry(error, attempt, effectiveRetryConfig)) {
break;
}
console.warn(`Request ${operationType} failed (attempt ${attempt}/${effectiveRetryConfig.maxRetries}):`, error.message);
}
}
throw new WalletRetryExhaustedError(
`Request ${operationType} failed after ${attempt} attempts`,
lastError,
attempt
);
}
// 智能Provider选择
async executeWithFallback<T>(
primaryOperation: () => Promise<T>,
operationType: string,
options: FallbackOptions = {}
): Promise<T> {
const providers = await this.getAvailableProviders(operationType);
for (let i = 0; i < providers.length; i++) {
const provider = providers[i];
try {
// 使用当前Provider执行操作
const result = await this.executeWithProvider(
primaryOperation,
provider,
operationType,
options
);
// 记录成功的Provider
this.networkMonitor.recordProviderSuccess(provider.url, operationType);
return result;
} catch (error) {
// 记录Provider失败
this.networkMonitor.recordProviderFailure(provider.url, operationType, error);
// 如果是最后一个Provider,抛出错误
if (i === providers.length - 1) {
throw new WalletFallbackExhaustedError(
`All providers failed for operation ${operationType}`,
error,
providers.length
);
}
console.warn(`Provider ${provider.url} failed, trying next provider:`, error.message);
}
}
}
// 网络状态监控
private setupNetworkMonitoring(): void {
// 监听网络状态变化
if (typeof window !== 'undefined') {
window.addEventListener('online', this.handleNetworkOnline.bind(this));
window.addEventListener('offline', this.handleNetworkOffline.bind(this));
}
// 定期健康检查
setInterval(() => {
this.performHealthCheck();
}, this.config.healthCheckInterval || 30000);
}
// 网络恢复处理
private async handleNetworkOnline(): Promise<void> {
console.log('Network restored, performing recovery operations');
// 重新连接钱包
await this.attemptWalletReconnection();
// 恢复待处理操作
await this.resumePendingOperations();
// 触发网络恢复事件
this.emitNetworkEvent('network-restored');
}
// 网络断开处理
private handleNetworkOffline(): void {
console.warn('Network connection lost');
// 暂停所有网络操作
this.pauseNetworkOperations();
// 显示离线提示
this.showOfflineNotification();
// 触发网络断开事件
this.emitNetworkEvent('network-lost');
}
// 健康检查
private async performHealthCheck(): Promise<void> {
const providers = Array.from(this.fallbackProviders.values());
const healthChecks = providers.map(async (provider) => {
try {
const startTime = Date.now();
await this.pingProvider(provider);
const responseTime = Date.now() - startTime;
this.networkMonitor.recordProviderHealth(provider.url, {
status: 'healthy',
responseTime,
timestamp: Date.now()
});
} catch (error) {
this.networkMonitor.recordProviderHealth(provider.url, {
status: 'unhealthy',
error: error.message,
timestamp: Date.now()
});
}
});
await Promise.allSettled(healthChecks);
}
// 超时管理器
createTimeoutPromise(timeout: number, operationType: string): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new WalletTimeoutError(
`Operation ${operationType} timed out after ${timeout}ms`,
operationType,
timeout
));
}, timeout);
});
}
// 重试策略判断
private shouldRetry(error: Error, attempt: number, config: RetryConfig): boolean {
if (attempt > config.maxRetries) {
return false;
}
// 不重试的错误类型
const nonRetryableErrors = [
'UserRejectedRequestError',
'UnauthorizedError',
'InvalidParameterError'
];
if (nonRetryableErrors.includes(error.constructor.name)) {
return false;
}
// 网络相关错误可以重试
const retryableErrors = [
'NetworkError',
'TimeoutError',
'ConnectionError',
'ServerError'
];
return retryableErrors.some(errorType =>
error.message.includes(errorType) ||
error.constructor.name.includes(errorType)
);
}
// 退避延迟计算
private calculateBackoffDelay(attempt: number, config: RetryConfig): number {
const baseDelay = config.baseDelay || 1000;
const maxDelay = config.maxDelay || 30000;
switch (config.strategy) {
case 'exponential':
return Math.min(baseDelay * Math.pow(2, attempt - 1), maxDelay);
case 'linear':
return Math.min(baseDelay * attempt, maxDelay);
case 'fixed':
return baseDelay;
default:
// 带抖动的指数退避
const exponentialDelay = baseDelay * Math.pow(2, attempt - 1);
const jitter = Math.random() * 0.1 * exponentialDelay;
return Math.min(exponentialDelay + jitter, maxDelay);
}
}
// Provider排序
private async getAvailableProviders(operationType: string): Promise<FallbackProvider[]> {
const allProviders = Array.from(this.fallbackProviders.values());
// 根据健康状态和性能排序
const providersWithScore = await Promise.all(
allProviders.map(async (provider) => {
const health = this.networkMonitor.getProviderHealth(provider.url);
const performance = this.networkMonitor.getProviderPerformance(provider.url, operationType);
let score = 0;
// 健康状态评分
if (health.status === 'healthy') score += 50;
if (health.responseTime && health.responseTime < 1000) score += 20;
// 性能评分
if (performance.successRate > 0.9) score += 20;
if (performance.avgResponseTime < 2000) score += 10;
return { provider, score };
})
);
// 按评分排序
providersWithScore.sort((a, b) => b.score - a.score);
return providersWithScore.map(item => item.provider);
}
// 延迟工具函数
private delay(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 错误类型判断
private isTimeoutError(error: Error): boolean {
return error instanceof WalletTimeoutError ||
error.name === 'TimeoutError' ||
error.message.includes('timeout') ||
error.message.includes('timed out');
}
// 生成操作ID
private generateOperationId(): string {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 占位实现
private async executeWithProvider<T>(operation: () => Promise<T>, provider: FallbackProvider, operationType: string, options: FallbackOptions): Promise<T> {
// 切换到指定Provider并执行操作
return operation();
}
private async pingProvider(provider: FallbackProvider): Promise<void> {
// 对Provider进行健康检查
}
private async attemptWalletReconnection(): Promise<void> {
// 尝试重新连接钱包
}
private async resumePendingOperations(): Promise<void> {
// 恢复待处理的操作
}
private pauseNetworkOperations(): void {
// 暂停所有网络操作
}
private showOfflineNotification(): void {
// 显示离线通知
}
private emitNetworkEvent(eventType: string): void {
// 触发网络事件
}
}
// 网络监控器
class NetworkMonitor {
private operationStats = new Map<string, OperationStats>();
private providerStats = new Map<string, ProviderStats>();
private networkHealth = new Map<string, ProviderHealth>();
recordSuccess(operationType: string, responseTime: number): void {
const stats = this.getOrCreateOperationStats(operationType);
stats.totalRequests++;
stats.successCount++;
stats.totalResponseTime += responseTime;
stats.lastSuccess = Date.now();
}
recordFailure(operationType: string, error: Error): void {
const stats = this.getOrCreateOperationStats(operationType);
stats.totalRequests++;
stats.failureCount++;
stats.lastFailure = Date.now();
stats.recentErrors.push({
error: error.message,
timestamp: Date.now()
});
// 保持错误历史在合理范围内
if (stats.recentErrors.length > 10) {
stats.recentErrors = stats.recentErrors.slice(-10);
}
}
recordProviderSuccess(providerUrl: string, operationType: string): void {
const stats = this.getOrCreateProviderStats(providerUrl);
const opStats = stats.operationStats.get(operationType) || { success: 0, failure: 0, totalTime: 0 };
opStats.success++;
stats.operationStats.set(operationType, opStats);
stats.lastSuccess = Date.now();
}
recordProviderFailure(providerUrl: string, operationType: string, error: Error): void {
const stats = this.getOrCreateProviderStats(providerUrl);
const opStats = stats.operationStats.get(operationType) || { success: 0, failure: 0, totalTime: 0 };
opStats.failure++;
stats.operationStats.set(operationType, opStats);
stats.lastFailure = Date.now();
}
recordProviderHealth(providerUrl: string, health: ProviderHealth): void {
this.networkHealth.set(providerUrl, health);
}
getProviderHealth(providerUrl: string): ProviderHealth {
return this.networkHealth.get(providerUrl) || {
status: 'unknown',
timestamp: 0
};
}
getProviderPerformance(providerUrl: string, operationType: string): ProviderPerformance {
const stats = this.providerStats.get(providerUrl);
if (!stats) {
return { successRate: 0, avgResponseTime: Infinity, totalRequests: 0 };
}
const opStats = stats.operationStats.get(operationType);
if (!opStats) {
return { successRate: 0, avgResponseTime: Infinity, totalRequests: 0 };
}
const totalRequests = opStats.success + opStats.failure;
const successRate = totalRequests > 0 ? opStats.success / totalRequests : 0;
const avgResponseTime = opStats.success > 0 ? opStats.totalTime / opStats.success : Infinity;
return { successRate, avgResponseTime, totalRequests };
}
private getOrCreateOperationStats(operationType: string): OperationStats {
let stats = this.operationStats.get(operationType);
if (!stats) {
stats = {
totalRequests: 0,
successCount: 0,
failureCount: 0,
totalResponseTime: 0,
recentErrors: [],
lastSuccess: 0,
lastFailure: 0
};
this.operationStats.set(operationType, stats);
}
return stats;
}
private getOrCreateProviderStats(providerUrl: string): ProviderStats {
let stats = this.providerStats.get(providerUrl);
if (!stats) {
stats = {
operationStats: new Map(),
lastSuccess: 0,
lastFailure: 0
};
this.providerStats.set(providerUrl, stats);
}
return stats;
}
}
// 超时管理器
class TimeoutManager {
private timeouts: TimeoutConfig;
constructor(timeouts: TimeoutConfig) {
this.timeouts = timeouts;
}
createTimeoutPromise(timeout: number, operationType: string): Promise<never> {
return new Promise((_, reject) => {
setTimeout(() => {
reject(new WalletTimeoutError(
`Operation ${operationType} timed out after ${timeout}ms`,
operationType,
timeout
));
}, timeout);
});
}
getTimeoutForOperation(operationType: string): number {
const operationTimeouts: Record<string, number> = {
'wallet_connect': 30000,
'transaction_send': 120000,
'balance_query': 10000,
'token_approval': 60000,
'network_switch': 15000,
'sign_message': 60000
};
return operationTimeouts[operationType] || this.timeouts.default;
}
}
// 自定义错误类
class WalletTimeoutError extends Error {
constructor(message: string, public operationType: string, public timeout: number) {
super(message);
this.name = 'WalletTimeoutError';
}
}
class WalletRetryExhaustedError extends Error {
constructor(message: string, public lastError: Error, public attempts: number) {
super(message);
this.name = 'WalletRetryExhaustedError';
}
}
class WalletFallbackExhaustedError extends Error {
constructor(message: string, public lastError: Error, public providersCount: number) {
super(message);
this.name = 'WalletFallbackExhaustedError';
}
}
// 类型定义
interface NetworkConfig {
timeouts: TimeoutConfig;
retry: RetryConfig;
healthCheckInterval: number;
maxConcurrentRequests: number;
}
interface TimeoutConfig {
default: number;
connect: number;
transaction: number;
query: number;
}
interface RetryConfig {
maxRetries: number;
baseDelay: number;
maxDelay: number;
strategy: 'exponential' | 'linear' | 'fixed';
}
interface RequestOptions {
operationType: string;
timeout?: number;
retryConfig?: RetryConfig;
}
interface FallbackOptions {
maxProviders?: number;
requireHealthy?: boolean;
}
interface FallbackProvider {
url: string;
weight: number;
timeout: number;
}
interface OperationStats {
totalRequests: number;
successCount: number;
failureCount: number;
totalResponseTime: number;
recentErrors: Array<{ error: string; timestamp: number }>;
lastSuccess: number;
lastFailure: number;
}
interface ProviderStats {
operationStats: Map<string, { success: number; failure: number; totalTime: number }>;
lastSuccess: number;
lastFailure: number;
}
interface ProviderHealth {
status: 'healthy' | 'unhealthy' | 'unknown';
responseTime?: number;
error?: string;
timestamp: number;
}
interface ProviderPerformance {
successRate: number;
avgResponseTime: number;
totalRequests: number;
}
网络异常处理最佳实践:
What are social recovery wallets? How do they differ from traditional wallets?
What are social recovery wallets? How do they differ from traditional wallets?
考察点:新型钱包机制。
答案:
社交恢复钱包是一种基于智能合约的新型钱包机制,通过多个受信任联系人(监护人)的集体确认来实现账户恢复。它解决了传统钱包助记词丢失无法找回的痛点,在安全性和可用性之间提供了更好的平衡。
社交恢复钱包架构:
// 社交恢复钱包合约接口
interface SocialRecoveryWallet {
// 基础钱包功能
execute(to: string, value: bigint, data: string): Promise<string>;
executeBatch(transactions: Transaction[]): Promise<string>;
// 监护人管理
addGuardian(guardian: string): Promise<void>;
removeGuardian(guardian: string): Promise<void>;
getGuardians(): Promise<string[]>;
// 恢复流程
initiateRecovery(newOwner: string): Promise<void>;
supportRecovery(recoveryId: string): Promise<void>;
executeRecovery(recoveryId: string): Promise<void>;
cancelRecovery(recoveryId: string): Promise<void>;
// 配置管理
setRecoveryThreshold(threshold: number): Promise<void>;
setRecoveryPeriod(period: number): Promise<void>;
}
// 社交恢复钱包实现
class SocialRecoveryWalletImpl implements SocialRecoveryWallet {
private contract: Contract;
private provider: Provider;
private owner: string;
private guardians: Set<string> = new Set();
private recoveryThreshold: number = 3;
private recoveryPeriod: number = 7 * 24 * 60 * 60; // 7天
constructor(contractAddress: string, provider: Provider, owner: string) {
this.contract = new Contract(contractAddress, SOCIAL_RECOVERY_ABI, provider);
this.provider = provider;
this.owner = owner;
this.loadConfiguration();
}
// 执行交易
async execute(to: string, value: bigint, data: string): Promise<string> {
const tx = await this.contract.execute(to, value, data);
return tx.hash;
}
// 批量执行交易
async executeBatch(transactions: Transaction[]): Promise<string> {
const targets = transactions.map(tx => tx.to);
const values = transactions.map(tx => tx.value || 0n);
const calldatas = transactions.map(tx => tx.data || '0x');
const tx = await this.contract.executeBatch(targets, values, calldatas);
return tx.hash;
}
// 添加监护人
async addGuardian(guardian: string): Promise<void> {
if (this.guardians.has(guardian)) {
throw new Error('Guardian already exists');
}
if (!this.isValidAddress(guardian)) {
throw new Error('Invalid guardian address');
}
const tx = await this.contract.addGuardian(guardian);
await tx.wait();
this.guardians.add(guardian);
this.emitEvent('GuardianAdded', { guardian });
}
// 移除监护人
async removeGuardian(guardian: string): Promise<void> {
if (!this.guardians.has(guardian)) {
throw new Error('Guardian not found');
}
if (this.guardians.size <= this.recoveryThreshold) {
throw new Error('Cannot remove guardian: would fall below threshold');
}
const tx = await this.contract.removeGuardian(guardian);
await tx.wait();
this.guardians.delete(guardian);
this.emitEvent('GuardianRemoved', { guardian });
}
// 获取监护人列表
async getGuardians(): Promise<string[]> {
const guardians = await this.contract.getGuardians();
return guardians;
}
// 发起恢复流程
async initiateRecovery(newOwner: string): Promise<void> {
if (!this.isValidAddress(newOwner)) {
throw new Error('Invalid new owner address');
}
// 验证恢复请求的合法性
await this.validateRecoveryRequest(newOwner);
const tx = await this.contract.initiateRecovery(newOwner);
await tx.wait();
const recoveryId = await this.getRecoveryId(newOwner);
this.emitEvent('RecoveryInitiated', { newOwner, recoveryId });
}
// 支持恢复
async supportRecovery(recoveryId: string): Promise<void> {
const recovery = await this.getRecoveryDetails(recoveryId);
if (!recovery) {
throw new Error('Recovery not found');
}
if (recovery.executed) {
throw new Error('Recovery already executed');
}
const tx = await this.contract.supportRecovery(recoveryId);
await tx.wait();
this.emitEvent('RecoverySupported', { recoveryId });
}
// 执行恢复
async executeRecovery(recoveryId: string): Promise<void> {
const recovery = await this.getRecoveryDetails(recoveryId);
if (!recovery) {
throw new Error('Recovery not found');
}
// 检查是否满足执行条件
if (recovery.supportCount < this.recoveryThreshold) {
throw new Error('Insufficient guardian support');
}
if (Date.now() < recovery.executeAfter) {
throw new Error('Recovery period not yet elapsed');
}
const tx = await this.contract.executeRecovery(recoveryId);
await tx.wait();
this.owner = recovery.newOwner;
this.emitEvent('RecoveryExecuted', { recoveryId, newOwner: recovery.newOwner });
}
// 取消恢复
async cancelRecovery(recoveryId: string): Promise<void> {
const tx = await this.contract.cancelRecovery(recoveryId);
await tx.wait();
this.emitEvent('RecoveryCancelled', { recoveryId });
}
// 设置恢复阈值
async setRecoveryThreshold(threshold: number): Promise<void> {
if (threshold <= 0 || threshold > this.guardians.size) {
throw new Error('Invalid threshold');
}
const tx = await this.contract.setRecoveryThreshold(threshold);
await tx.wait();
this.recoveryThreshold = threshold;
this.emitEvent('ThresholdChanged', { threshold });
}
// 设置恢复期间
async setRecoveryPeriod(period: number): Promise<void> {
if (period < 24 * 60 * 60) { // 最少24小时
throw new Error('Recovery period too short');
}
const tx = await this.contract.setRecoveryPeriod(period);
await tx.wait();
this.recoveryPeriod = period;
this.emitEvent('PeriodChanged', { period });
}
// 监护人推荐系统
async recommendGuardians(userProfile: UserProfile): Promise<GuardianRecommendation[]> {
const recommendations: GuardianRecommendation[] = [];
// 基于社交网络的推荐
if (userProfile.socialAccounts) {
const socialContacts = await this.getSocialContacts(userProfile.socialAccounts);
for (const contact of socialContacts) {
if (await this.hasWallet(contact.address)) {
recommendations.push({
address: contact.address,
name: contact.name,
relationship: contact.relationship,
trustScore: contact.trustScore,
source: 'social'
});
}
}
}
// 基于交易历史的推荐
const frequentCounterparties = await this.getFrequentCounterparties(this.owner);
for (const counterparty of frequentCounterparties) {
recommendations.push({
address: counterparty.address,
relationship: 'frequent_contact',
trustScore: counterparty.interactionCount / 100,
source: 'onchain'
});
}
// 专业监护服务推荐
const professionalGuardians = await this.getProfessionalGuardians();
recommendations.push(...professionalGuardians);
// 按信任度排序
recommendations.sort((a, b) => b.trustScore - a.trustScore);
return recommendations.slice(0, 10); // 返回前10个推荐
}
// 恢复流程管理
async manageRecoveryProcess(): Promise<RecoveryManager> {
return new RecoveryManager(this);
}
// 私有方法
private async loadConfiguration(): Promise<void> {
const [guardians, threshold, period] = await Promise.all([
this.contract.getGuardians(),
this.contract.getRecoveryThreshold(),
this.contract.getRecoveryPeriod()
]);
this.guardians = new Set(guardians);
this.recoveryThreshold = threshold;
this.recoveryPeriod = period;
}
private async validateRecoveryRequest(newOwner: string): Promise<void> {
// 验证新所有者不是当前所有者
if (newOwner.toLowerCase() === this.owner.toLowerCase()) {
throw new Error('New owner cannot be the same as current owner');
}
// 验证新所有者不是监护人
if (this.guardians.has(newOwner.toLowerCase())) {
throw new Error('New owner cannot be a guardian');
}
}
private async getRecoveryId(newOwner: string): Promise<string> {
const filter = this.contract.filters.RecoveryInitiated(null, newOwner);
const events = await this.contract.queryFilter(filter, -100);
return events[events.length - 1].args.recoveryId;
}
private async getRecoveryDetails(recoveryId: string): Promise<RecoveryDetails | null> {
try {
const details = await this.contract.getRecovery(recoveryId);
return {
newOwner: details.newOwner,
supportCount: details.supportCount,
executeAfter: details.executeAfter * 1000,
executed: details.executed
};
} catch {
return null;
}
}
private isValidAddress(address: string): boolean {
return /^0x[a-fA-F0-9]{40}$/.test(address);
}
private emitEvent(eventName: string, data: any): void {
// 触发事件通知
console.log(`Event: ${eventName}`, data);
}
// 占位实现
private async getSocialContacts(accounts: SocialAccount[]): Promise<SocialContact[]> { return []; }
private async hasWallet(address: string): Promise<boolean> { return false; }
private async getFrequentCounterparties(owner: string): Promise<Counterparty[]> { return []; }
private async getProfessionalGuardians(): Promise<GuardianRecommendation[]> { return []; }
}
// 恢复流程管理器
class RecoveryManager {
private wallet: SocialRecoveryWalletImpl;
private activeRecoveries = new Map<string, RecoveryProcess>();
constructor(wallet: SocialRecoveryWalletImpl) {
this.wallet = wallet;
this.setupEventListeners();
}
// 启动恢复向导
async startRecoveryWizard(): Promise<RecoveryWizard> {
return new RecoveryWizard(this.wallet);
}
// 监控恢复进度
async trackRecoveryProgress(recoveryId: string): Promise<RecoveryStatus> {
const process = this.activeRecoveries.get(recoveryId);
if (!process) {
throw new Error('Recovery process not found');
}
const details = await this.wallet.getRecoveryDetails(recoveryId);
const guardians = await this.wallet.getGuardians();
const threshold = await this.wallet.recoveryThreshold;
return {
recoveryId,
progress: details.supportCount / threshold,
supportCount: details.supportCount,
requiredSupport: threshold,
timeRemaining: Math.max(0, details.executeAfter - Date.now()),
canExecute: details.supportCount >= threshold && Date.now() >= details.executeAfter,
supportingGuardians: await this.getSupportingGuardians(recoveryId)
};
}
// 通知监护人
async notifyGuardians(recoveryId: string, message: string): Promise<void> {
const guardians = await this.wallet.getGuardians();
const notifications = guardians.map(async (guardian) => {
return await this.sendRecoveryNotification(guardian, recoveryId, message);
});
await Promise.allSettled(notifications);
}
private setupEventListeners(): void {
// 监听恢复相关事件
}
private async getSupportingGuardians(recoveryId: string): Promise<string[]> {
// 获取已支持恢复的监护人列表
return [];
}
private async sendRecoveryNotification(guardian: string, recoveryId: string, message: string): Promise<void> {
// 发送恢复通知到监护人
}
}
// 恢复向导
class RecoveryWizard {
private wallet: SocialRecoveryWalletImpl;
private steps: RecoveryStep[];
private currentStep = 0;
constructor(wallet: SocialRecoveryWalletImpl) {
this.wallet = wallet;
this.initializeSteps();
}
async nextStep(): Promise<RecoveryStep | null> {
if (this.currentStep >= this.steps.length) {
return null;
}
const step = this.steps[this.currentStep];
this.currentStep++;
return step;
}
async executeStep(stepData: any): Promise<StepResult> {
const step = this.steps[this.currentStep - 1];
return await step.execute(stepData);
}
private initializeSteps(): void {
this.steps = [
{
id: 'verify_identity',
title: '验证身份',
description: '确认你是钱包的合法所有者',
execute: async (data) => this.verifyIdentity(data)
},
{
id: 'select_new_owner',
title: '设置新密钥',
description: '生成或导入新的钱包密钥',
execute: async (data) => this.setupNewOwner(data)
},
{
id: 'contact_guardians',
title: '联系监护人',
description: '通知监护人支持恢复请求',
execute: async (data) => this.contactGuardians(data)
},
{
id: 'wait_for_support',
title: '等待支持',
description: '等待足够多的监护人确认',
execute: async (data) => this.waitForSupport(data)
},
{
id: 'execute_recovery',
title: '执行恢复',
description: '完成钱包恢复流程',
execute: async (data) => this.executeRecovery(data)
}
];
}
private async verifyIdentity(data: any): Promise<StepResult> {
// 身份验证逻辑
return { success: true, data: {} };
}
private async setupNewOwner(data: any): Promise<StepResult> {
// 设置新所有者逻辑
return { success: true, data: {} };
}
private async contactGuardians(data: any): Promise<StepResult> {
// 联系监护人逻辑
return { success: true, data: {} };
}
private async waitForSupport(data: any): Promise<StepResult> {
// 等待支持逻辑
return { success: true, data: {} };
}
private async executeRecovery(data: any): Promise<StepResult> {
// 执行恢复逻辑
return { success: true, data: {} };
}
}
与传统钱包的核心区别:
How to implement security auditing for wallet connections? Detect malicious behavior.
How to implement security auditing for wallet connections? Detect malicious behavior.
考察点:安全监控机制。
答案:
钱包连接安全审计通过多层次监控体系实现恶意行为检测,包括实时行为分析、异常模式识别、威胁情报集成等。核心目标是在保护用户资产安全的同时,最小化对正常操作的影响。
安全审计架构设计:
// 钱包安全审计系统
class WalletSecurityAuditor {
private behaviorAnalyzer: BehaviorAnalyzer;
private threatDetector: ThreatDetector;
private riskAssessment: RiskAssessment;
private alertSystem: AlertSystem;
private auditLogger: AuditLogger;
constructor(config: SecurityConfig) {
this.behaviorAnalyzer = new BehaviorAnalyzer(config.behavior);
this.threatDetector = new ThreatDetector(config.threats);
this.riskAssessment = new RiskAssessment(config.risk);
this.alertSystem = new AlertSystem(config.alerts);
this.auditLogger = new AuditLogger(config.logging);
}
// 连接安全审计
async auditConnection(connectionAttempt: ConnectionAttempt): Promise<SecurityVerdict> {
const auditSession = this.auditLogger.startAuditSession('wallet_connection');
try {
// 1. 基础安全检查
const basicChecks = await this.performBasicSecurityChecks(connectionAttempt);
auditSession.log('basic_checks', basicChecks);
if (basicChecks.criticalIssues.length > 0) {
return this.createSecurityVerdict('BLOCKED', basicChecks.criticalIssues);
}
// 2. 行为分析
const behaviorAnalysis = await this.behaviorAnalyzer.analyze(connectionAttempt);
auditSession.log('behavior_analysis', behaviorAnalysis);
// 3. 威胁检测
const threatAnalysis = await this.threatDetector.detectThreats(connectionAttempt);
auditSession.log('threat_detection', threatAnalysis);
// 4. 风险评估
const riskScore = await this.riskAssessment.calculateRisk({
connection: connectionAttempt,
behavior: behaviorAnalysis,
threats: threatAnalysis
});
auditSession.log('risk_assessment', { score: riskScore });
// 5. 生成审计结果
const verdict = await this.generateVerdict(riskScore, threatAnalysis, behaviorAnalysis);
auditSession.log('verdict', verdict);
return verdict;
} catch (error) {
auditSession.logError('audit_error', error);
// 在错误情况下,采用保守策略
return this.createSecurityVerdict('BLOCKED', ['Audit system error']);
} finally {
auditSession.end();
}
}
// 实时交易监控
async monitorTransaction(transaction: TransactionAttempt): Promise<TransactionSecurityResult> {
const monitor = this.auditLogger.startTransactionMonitor(transaction.hash);
try {
// 交易内容分析
const contentAnalysis = await this.analyzeTransactionContent(transaction);
monitor.log('content_analysis', contentAnalysis);
// gas异常检测
const gasAnalysis = await this.analyzeGasPatterns(transaction);
monitor.log('gas_analysis', gasAnalysis);
// 合约安全检查
const contractSecurity = await this.checkContractSecurity(transaction.to);
monitor.log('contract_security', contractSecurity);
// MEV攻击检测
const mevAnalysis = await this.detectMEVAttacks(transaction);
monitor.log('mev_analysis', mevAnalysis);
// 综合风险评分
const riskScore = this.calculateTransactionRisk({
content: contentAnalysis,
gas: gasAnalysis,
contract: contractSecurity,
mev: mevAnalysis
});
const result: TransactionSecurityResult = {
allowed: riskScore < 0.7,
riskScore,
warnings: this.generateWarnings(contentAnalysis, gasAnalysis, contractSecurity),
recommendations: this.generateRecommendations(riskScore, mevAnalysis)
};
monitor.log('final_result', result);
return result;
} finally {
monitor.end();
}
}
// 基础安全检查
private async performBasicSecurityChecks(attempt: ConnectionAttempt): Promise<BasicSecurityChecks> {
const checks: BasicSecurityChecks = {
criticalIssues: [],
warnings: [],
info: []
};
// 检查DApp域名
const domainCheck = await this.checkDomainSecurity(attempt.origin);
if (domainCheck.isPhishing) {
checks.criticalIssues.push(`Phishing domain detected: ${attempt.origin}`);
}
if (domainCheck.isSuspicious) {
checks.warnings.push(`Suspicious domain: ${domainCheck.reason}`);
}
// 检查SSL证书
const sslCheck = await this.checkSSLSecurity(attempt.origin);
if (!sslCheck.valid) {
checks.criticalIssues.push('Invalid SSL certificate');
}
if (sslCheck.expiringSoon) {
checks.warnings.push('SSL certificate expiring soon');
}
// 检查请求频率
const rateCheck = this.checkRequestRate(attempt.origin, attempt.userAgent);
if (rateCheck.isAbusive) {
checks.criticalIssues.push('Abusive request rate detected');
}
// 检查用户代理
const uaCheck = this.checkUserAgent(attempt.userAgent);
if (uaCheck.isSuspicious) {
checks.warnings.push(`Suspicious user agent: ${uaCheck.reason}`);
}
return checks;
}
// 交易内容分析
private async analyzeTransactionContent(tx: TransactionAttempt): Promise<ContentAnalysis> {
const analysis: ContentAnalysis = {
riskLevel: 'low',
issues: [],
patterns: []
};
// 检查交易值异常
if (tx.value && this.isUnusualValue(tx.value)) {
analysis.issues.push({
type: 'unusual_value',
severity: 'medium',
description: `Unusual transaction value: ${tx.value}`
});
}
// 检查gas设置异常
if (this.isUnusualGasSettings(tx.gas, tx.gasPrice)) {
analysis.issues.push({
type: 'unusual_gas',
severity: 'low',
description: 'Unusual gas settings detected'
});
}
// 检查调用数据
if (tx.data && tx.data !== '0x') {
const dataAnalysis = await this.analyzeCallData(tx.data, tx.to);
if (dataAnalysis.hasRiskyPatterns) {
analysis.issues.push({
type: 'risky_calldata',
severity: 'high',
description: dataAnalysis.description
});
}
}
// 检查批量转账模式
const batchPattern = this.detectBatchTransferPattern(tx);
if (batchPattern.detected) {
analysis.patterns.push({
type: 'batch_transfer',
confidence: batchPattern.confidence,
description: 'Batch transfer pattern detected'
});
}
// 设置整体风险级别
const highSeverityIssues = analysis.issues.filter(issue => issue.severity === 'high');
if (highSeverityIssues.length > 0) {
analysis.riskLevel = 'high';
} else if (analysis.issues.length > 2) {
analysis.riskLevel = 'medium';
}
return analysis;
}
// 恶意行为检测
async detectMaliciousBehavior(sessionData: SessionData): Promise<MaliciousBehaviorReport> {
const detectors: MaliciousBehaviorDetector[] = [
new PhishingDetector(),
new DrainerDetector(),
new MEVBotDetector(),
new SandwichAttackDetector(),
new FlashLoanAttackDetector(),
new RugPullDetector()
];
const results = await Promise.all(
detectors.map(detector => detector.analyze(sessionData))
);
const report: MaliciousBehaviorReport = {
overallRisk: 0,
detectedAttacks: [],
recommendations: [],
immediateActions: []
};
// 聚合检测结果
for (const result of results) {
report.overallRisk = Math.max(report.overallRisk, result.riskScore);
if (result.detected) {
report.detectedAttacks.push({
type: result.attackType,
confidence: result.confidence,
description: result.description,
evidence: result.evidence
});
}
report.recommendations.push(...result.recommendations);
report.immediateActions.push(...result.immediateActions);
}
return report;
}
}
// 行为分析器
class BehaviorAnalyzer {
private userProfiles = new Map<string, UserBehaviorProfile>();
private sessionPatterns = new Map<string, SessionPattern>();
async analyze(connectionAttempt: ConnectionAttempt): Promise<BehaviorAnalysis> {
const userProfile = await this.getUserBehaviorProfile(connectionAttempt.userAddress);
const sessionPattern = this.analyzeSessionPattern(connectionAttempt);
return {
userRiskScore: this.calculateUserRiskScore(userProfile),
sessionRiskScore: this.calculateSessionRiskScore(sessionPattern),
anomalies: this.detectAnomalies(userProfile, sessionPattern),
recommendations: this.generateBehaviorRecommendations(userProfile, sessionPattern)
};
}
private async getUserBehaviorProfile(userAddress: string): Promise<UserBehaviorProfile> {
let profile = this.userProfiles.get(userAddress);
if (!profile) {
profile = await this.buildUserProfile(userAddress);
this.userProfiles.set(userAddress, profile);
}
return profile;
}
private async buildUserProfile(userAddress: string): Promise<UserBehaviorProfile> {
// 从区块链数据构建用户行为档案
const transactionHistory = await this.getTransactionHistory(userAddress);
const interactionPatterns = this.analyzeInteractionPatterns(transactionHistory);
return {
address: userAddress,
firstSeen: transactionHistory[0]?.timestamp || Date.now(),
totalTransactions: transactionHistory.length,
averageValue: this.calculateAverageValue(transactionHistory),
preferredDApps: this.extractPreferredDApps(transactionHistory),
riskIndicators: this.identifyRiskIndicators(transactionHistory),
interactionPatterns
};
}
private calculateUserRiskScore(profile: UserBehaviorProfile): number {
let risk = 0;
// 新用户风险
const accountAge = Date.now() - profile.firstSeen;
if (accountAge < 7 * 24 * 60 * 60 * 1000) { // 7天
risk += 0.2;
}
// 交易历史风险
if (profile.totalTransactions < 10) {
risk += 0.15;
}
// 风险指标检查
risk += profile.riskIndicators.length * 0.1;
// 异常模式检查
if (profile.interactionPatterns.hasAnomalous) {
risk += 0.3;
}
return Math.min(risk, 1.0);
}
// 占位实现
private analyzeSessionPattern(attempt: ConnectionAttempt): SessionPattern { return {} as SessionPattern; }
private calculateSessionRiskScore(pattern: SessionPattern): number { return 0; }
private detectAnomalies(profile: UserBehaviorProfile, pattern: SessionPattern): Anomaly[] { return []; }
private generateBehaviorRecommendations(profile: UserBehaviorProfile, pattern: SessionPattern): string[] { return []; }
private async getTransactionHistory(address: string): Promise<any[]> { return []; }
private analyzeInteractionPatterns(history: any[]): any { return {}; }
private calculateAverageValue(history: any[]): number { return 0; }
private extractPreferredDApps(history: any[]): string[] { return []; }
private identifyRiskIndicators(history: any[]): string[] { return []; }
}
// 威胁检测器
class ThreatDetector {
private threatIntelligence: ThreatIntelligence;
private maliciousContracts: Set<string>;
private phishingDomains: Set<string>;
constructor(config: ThreatConfig) {
this.threatIntelligence = new ThreatIntelligence(config.intelligence);
this.maliciousContracts = new Set(config.maliciousContracts);
this.phishingDomains = new Set(config.phishingDomains);
}
async detectThreats(attempt: ConnectionAttempt): Promise<ThreatAnalysis> {
const threats: DetectedThreat[] = [];
// 检查恶意域名
if (this.phishingDomains.has(attempt.origin)) {
threats.push({
type: 'phishing_domain',
severity: 'critical',
confidence: 0.95,
description: `Known phishing domain: ${attempt.origin}`
});
}
// 检查威胁情报
const intelThreats = await this.threatIntelligence.checkThreats(attempt);
threats.push(...intelThreats);
// 检查DApp信誉
const reputationCheck = await this.checkDAppReputation(attempt.origin);
if (reputationCheck.isMalicious) {
threats.push({
type: 'malicious_dapp',
severity: 'high',
confidence: reputationCheck.confidence,
description: `Malicious DApp detected: ${reputationCheck.reason}`
});
}
return {
threatCount: threats.length,
maxSeverity: this.getMaxSeverity(threats),
threats,
overallRisk: this.calculateThreatRisk(threats)
};
}
private getMaxSeverity(threats: DetectedThreat[]): 'low' | 'medium' | 'high' | 'critical' {
if (threats.some(t => t.severity === 'critical')) return 'critical';
if (threats.some(t => t.severity === 'high')) return 'high';
if (threats.some(t => t.severity === 'medium')) return 'medium';
return 'low';
}
private calculateThreatRisk(threats: DetectedThreat[]): number {
return threats.reduce((risk, threat) => {
const severityWeight = {
'low': 0.1,
'medium': 0.3,
'high': 0.7,
'critical': 1.0
};
return risk + severityWeight[threat.severity] * threat.confidence;
}, 0) / threats.length;
}
private async checkDAppReputation(origin: string): Promise<ReputationCheck> {
// 检查DApp信誉
return {
isMalicious: false,
confidence: 0,
reason: ''
};
}
}
// 专用恶意行为检测器
class PhishingDetector implements MaliciousBehaviorDetector {
async analyze(sessionData: SessionData): Promise<DetectionResult> {
const indicators = [];
// 检查域名相似性
if (this.checkDomainSimilarity(sessionData.origin)) {
indicators.push('domain_similarity');
}
// 检查SSL证书
if (await this.checkSuspiciousSSL(sessionData.origin)) {
indicators.push('suspicious_ssl');
}
// 检查页面内容
if (await this.checkPhishingContent(sessionData.pageContent)) {
indicators.push('phishing_content');
}
const riskScore = indicators.length / 3;
const detected = riskScore > 0.5;
return {
detected,
attackType: 'phishing',
riskScore,
confidence: detected ? Math.min(riskScore + 0.2, 1.0) : 0,
description: detected ? 'Potential phishing attack detected' : 'No phishing indicators found',
evidence: indicators,
recommendations: detected ? ['Verify website authenticity', 'Check URL carefully'] : [],
immediateActions: detected ? ['Block connection', 'Show phishing warning'] : []
};
}
private checkDomainSimilarity(origin: string): boolean {
// 检查域名是否与知名DApp相似
return false;
}
private async checkSuspiciousSSL(origin: string): Promise<boolean> {
// 检查SSL证书是否可疑
return false;
}
private async checkPhishingContent(content: string): Promise<boolean> {
// 检查页面内容是否包含钓鱼特征
return false;
}
}
// 更多占位实现和类型定义
class DrainerDetector implements MaliciousBehaviorDetector {
async analyze(sessionData: SessionData): Promise<DetectionResult> {
// Drainer检测逻辑
return {
detected: false,
attackType: 'drainer',
riskScore: 0,
confidence: 0,
description: '',
evidence: [],
recommendations: [],
immediateActions: []
};
}
}
// 类型定义
interface SecurityConfig {
behavior: BehaviorConfig;
threats: ThreatConfig;
risk: RiskConfig;
alerts: AlertConfig;
logging: LoggingConfig;
}
interface ConnectionAttempt {
origin: string;
userAddress: string;
userAgent: string;
timestamp: number;
requestedPermissions: string[];
}
interface SecurityVerdict {
decision: 'ALLOWED' | 'BLOCKED' | 'WARNING';
riskScore: number;
reasons: string[];
recommendations: string[];
}
interface MaliciousBehaviorDetector {
analyze(sessionData: SessionData): Promise<DetectionResult>;
}
interface DetectionResult {
detected: boolean;
attackType: string;
riskScore: number;
confidence: number;
description: string;
evidence: string[];
recommendations: string[];
immediateActions: string[];
}
// 更多类型定义省略...
interface BehaviorConfig {}
interface ThreatConfig { intelligence: any; maliciousContracts: string[]; phishingDomains: string[]; }
interface RiskConfig {}
interface AlertConfig {}
interface LoggingConfig {}
interface TransactionAttempt { hash: string; to: string; value?: bigint; data?: string; gas?: bigint; gasPrice?: bigint; }
interface TransactionSecurityResult { allowed: boolean; riskScore: number; warnings: string[]; recommendations: string[]; }
interface BasicSecurityChecks { criticalIssues: string[]; warnings: string[]; info: string[]; }
interface ContentAnalysis { riskLevel: string; issues: any[]; patterns: any[]; }
interface MaliciousBehaviorReport { overallRisk: number; detectedAttacks: any[]; recommendations: string[]; immediateActions: string[]; }
interface BehaviorAnalysis { userRiskScore: number; sessionRiskScore: number; anomalies: Anomaly[]; recommendations: string[]; }
interface UserBehaviorProfile { address: string; firstSeen: number; totalTransactions: number; averageValue: number; preferredDApps: string[]; riskIndicators: string[]; interactionPatterns: any; }
interface SessionPattern {}
interface Anomaly {}
interface ThreatAnalysis { threatCount: number; maxSeverity: string; threats: DetectedThreat[]; overallRisk: number; }
interface DetectedThreat { type: string; severity: string; confidence: number; description: string; }
interface ReputationCheck { isMalicious: boolean; confidence: number; reason: string; }
interface SessionData { origin: string; pageContent: string; }
interface ThreatIntelligence { checkThreats(attempt: ConnectionAttempt): Promise<DetectedThreat[]>; }
interface AuditLogger { startAuditSession(type: string): any; startTransactionMonitor(hash: string): any; }
interface AlertSystem {}
interface RiskAssessment { calculateRisk(data: any): Promise<number>; }
// 占位类实现
class MEVBotDetector implements MaliciousBehaviorDetector {
async analyze(sessionData: SessionData): Promise<DetectionResult> { return {} as DetectionResult; }
}
class SandwichAttackDetector implements MaliciousBehaviorDetector {
async analyze(sessionData: SessionData): Promise<DetectionResult> { return {} as DetectionResult; }
}
class FlashLoanAttackDetector implements MaliciousBehaviorDetector {
async analyze(sessionData: SessionData): Promise<DetectionResult> { return {} as DetectionResult; }
}
class RugPullDetector implements MaliciousBehaviorDetector {
async analyze(sessionData: SessionData): Promise<DetectionResult> { return {} as DetectionResult; }
}
安全监控最佳实践:
What challenges does cross-chain wallet integration face? How to design unified cross-chain interfaces?
What challenges does cross-chain wallet integration face? How to design unified cross-chain interfaces?
考察点:跨链技术挑战。
答案:
跨链钱包集成面临网络差异性、状态同步、安全性验证等多重挑战。统一跨链接口需要设计抽象层来屏蔽不同区块链的差异,提供一致的用户体验和开发接口。
跨链集成主要挑战:
统一跨链接口架构:
// 跨链钱包管理器
class CrossChainWalletManager {
private chainAdapters = new Map<string, ChainAdapter>();
private bridgeManager: CrossChainBridgeManager;
private stateManager: CrossChainStateManager;
private assetManager: CrossChainAssetManager;
private eventBus: CrossChainEventBus;
constructor(config: CrossChainConfig) {
this.bridgeManager = new CrossChainBridgeManager(config.bridges);
this.stateManager = new CrossChainStateManager(config.state);
this.assetManager = new CrossChainAssetManager(config.assets);
this.eventBus = new CrossChainEventBus();
this.initializeChainAdapters(config.chains);
}
// 注册链适配器
registerChain(chainId: string, adapter: ChainAdapter): void {
this.chainAdapters.set(chainId, adapter);
this.eventBus.emit('chainRegistered', { chainId, adapter });
}
// 统一连接接口
async connectWallet(chainId: string, options?: ConnectOptions): Promise<CrossChainWalletConnection> {
const adapter = this.getChainAdapter(chainId);
try {
const connection = await adapter.connect(options);
// 创建跨链连接包装器
const crossChainConnection = new CrossChainWalletConnection({
chainId,
adapter,
connection,
stateManager: this.stateManager,
assetManager: this.assetManager,
bridgeManager: this.bridgeManager
});
// 同步账户状态
await this.syncAccountState(crossChainConnection);
// 注册状态监听
this.registerConnectionEventListeners(crossChainConnection);
return crossChainConnection;
} catch (error) {
throw new CrossChainError(`Failed to connect to ${chainId}: ${error.message}`);
}
}
// 统一资产查询
async getAssetBalance(address: string, chainId?: string): Promise<CrossChainAssetBalance> {
if (chainId) {
return await this.getSingleChainBalance(address, chainId);
}
// 获取所有链上的资产
const balancePromises = Array.from(this.chainAdapters.keys()).map(async (id) => {
try {
const balance = await this.getSingleChainBalance(address, id);
return { chainId: id, balance };
} catch (error) {
console.warn(`Failed to get balance for ${id}:`, error);
return { chainId: id, balance: null, error: error.message };
}
});
const results = await Promise.all(balancePromises);
return this.aggregateAssetBalances(results);
}
// 跨链转账
async crossChainTransfer(params: CrossChainTransferParams): Promise<CrossChainTransaction> {
const { from, to, asset, amount, fromChain, toChain } = params;
// 验证跨链转账参数
await this.validateCrossChainTransfer(params);
// 选择最优跨链路径
const bridgeRoute = await this.bridgeManager.findOptimalRoute(fromChain, toChain, asset);
if (!bridgeRoute) {
throw new CrossChainError(`No bridge route found from ${fromChain} to ${toChain}`);
}
// 执行跨链转账
const transaction = await this.executeCrossChainTransfer(params, bridgeRoute);
// 启动状态监控
this.startTransactionMonitoring(transaction);
return transaction;
}
// 网络状态监控
async monitorNetworkStatus(): Promise<CrossChainNetworkStatus> {
const networkPromises = Array.from(this.chainAdapters.entries()).map(async ([chainId, adapter]) => {
try {
const status = await adapter.getNetworkStatus();
return { chainId, status, healthy: true };
} catch (error) {
return {
chainId,
status: null,
healthy: false,
error: error.message
};
}
});
const results = await Promise.all(networkPromises);
return {
timestamp: Date.now(),
networks: results,
overallHealth: results.every(r => r.healthy)
};
}
private getChainAdapter(chainId: string): ChainAdapter {
const adapter = this.chainAdapters.get(chainId);
if (!adapter) {
throw new CrossChainError(`Chain adapter not found for ${chainId}`);
}
return adapter;
}
private async syncAccountState(connection: CrossChainWalletConnection): Promise<void> {
const accountData = await connection.getAccountData();
await this.stateManager.updateAccountState(connection.chainId, accountData);
}
private registerConnectionEventListeners(connection: CrossChainWalletConnection): void {
connection.on('accountChanged', (newAccount) => {
this.eventBus.emit('accountChanged', {
chainId: connection.chainId,
newAccount
});
});
connection.on('chainChanged', (newChainId) => {
this.eventBus.emit('chainChanged', {
oldChainId: connection.chainId,
newChainId
});
});
connection.on('disconnect', () => {
this.eventBus.emit('disconnect', {
chainId: connection.chainId
});
});
}
}
// 链适配器接口
interface ChainAdapter {
readonly chainId: string;
readonly chainName: string;
readonly nativeCurrency: Currency;
connect(options?: ConnectOptions): Promise<WalletConnection>;
disconnect(): Promise<void>;
getNetworkStatus(): Promise<NetworkStatus>;
getBalance(address: string, tokenAddress?: string): Promise<AssetBalance>;
sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse>;
estimateGas(transaction: TransactionRequest): Promise<GasEstimate>;
getTransactionReceipt(txHash: string): Promise<TransactionReceipt>;
subscribeToBlocks(callback: (block: Block) => void): () => void;
subscribeToLogs(filter: LogFilter, callback: (log: Log) => void): () => void;
}
// 以太坊适配器实现
class EthereumChainAdapter implements ChainAdapter {
readonly chainId = '1';
readonly chainName = 'Ethereum Mainnet';
readonly nativeCurrency: Currency = {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18
};
private provider: ethers.providers.Web3Provider | null = null;
private signer: ethers.Signer | null = null;
async connect(options?: ConnectOptions): Promise<WalletConnection> {
if (typeof window.ethereum === 'undefined') {
throw new Error('Ethereum provider not found');
}
try {
// 请求连接
await window.ethereum.request({ method: 'eth_requestAccounts' });
// 切换到正确网络
await this.switchToNetwork();
this.provider = new ethers.providers.Web3Provider(window.ethereum);
this.signer = this.provider.getSigner();
const address = await this.signer.getAddress();
return new EthereumWalletConnection({
address,
provider: this.provider,
signer: this.signer,
chainId: this.chainId
});
} catch (error) {
throw new Error(`Failed to connect to Ethereum: ${error.message}`);
}
}
async getBalance(address: string, tokenAddress?: string): Promise<AssetBalance> {
if (!this.provider) {
throw new Error('Provider not initialized');
}
if (!tokenAddress) {
// 获取ETH余额
const balance = await this.provider.getBalance(address);
return {
address,
symbol: 'ETH',
decimals: 18,
balance: balance.toString(),
formattedBalance: ethers.utils.formatEther(balance)
};
} else {
// 获取ERC-20代币余额
const contract = new ethers.Contract(
tokenAddress,
['function balanceOf(address) view returns (uint256)', 'function decimals() view returns (uint8)', 'function symbol() view returns (string)'],
this.provider
);
const [balance, decimals, symbol] = await Promise.all([
contract.balanceOf(address),
contract.decimals(),
contract.symbol()
]);
return {
address,
symbol,
decimals,
balance: balance.toString(),
formattedBalance: ethers.utils.formatUnits(balance, decimals),
tokenAddress
};
}
}
async sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> {
if (!this.signer) {
throw new Error('Signer not initialized');
}
try {
const tx = await this.signer.sendTransaction({
to: transaction.to,
value: transaction.value,
data: transaction.data,
gasLimit: transaction.gasLimit,
gasPrice: transaction.gasPrice
});
return {
hash: tx.hash,
from: tx.from,
to: tx.to,
value: tx.value?.toString(),
gasLimit: tx.gasLimit?.toString(),
gasPrice: tx.gasPrice?.toString(),
nonce: tx.nonce,
chainId: this.chainId,
wait: () => tx.wait()
};
} catch (error) {
throw new Error(`Transaction failed: ${error.message}`);
}
}
private async switchToNetwork(): Promise<void> {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x1' }] // Ethereum mainnet
});
} catch (error) {
// 如果网络不存在,尝试添加
if (error.code === 4902) {
await this.addNetwork();
} else {
throw error;
}
}
}
private async addNetwork(): Promise<void> {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x1',
chainName: 'Ethereum Mainnet',
nativeCurrency: {
name: 'Ethereum',
symbol: 'ETH',
decimals: 18
},
rpcUrls: ['https://mainnet.infura.io/v3/YOUR_API_KEY'],
blockExplorerUrls: ['https://etherscan.io']
}]
});
}
// 其他方法的占位实现
async disconnect(): Promise<void> { /* 实现断开连接逻辑 */ }
async getNetworkStatus(): Promise<NetworkStatus> { return {} as NetworkStatus; }
async estimateGas(transaction: TransactionRequest): Promise<GasEstimate> { return {} as GasEstimate; }
async getTransactionReceipt(txHash: string): Promise<TransactionReceipt> { return {} as TransactionReceipt; }
subscribeToBlocks(callback: (block: Block) => void): () => void { return () => {}; }
subscribeToLogs(filter: LogFilter, callback: (log: Log) => void): () => void { return () => {}; }
}
// BSC适配器实现
class BSCChainAdapter implements ChainAdapter {
readonly chainId = '56';
readonly chainName = 'Binance Smart Chain';
readonly nativeCurrency: Currency = {
name: 'Binance Coin',
symbol: 'BNB',
decimals: 18
};
async connect(options?: ConnectOptions): Promise<WalletConnection> {
// BSC特定的连接逻辑
if (typeof window.ethereum === 'undefined') {
throw new Error('Ethereum provider not found');
}
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
await this.switchToBSC();
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
const address = await signer.getAddress();
return new BSCWalletConnection({
address,
provider,
signer,
chainId: this.chainId
});
} catch (error) {
throw new Error(`Failed to connect to BSC: ${error.message}`);
}
}
private async switchToBSC(): Promise<void> {
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x38' }] // BSC mainnet
});
} catch (error) {
if (error.code === 4902) {
await this.addBSCNetwork();
} else {
throw error;
}
}
}
private async addBSCNetwork(): Promise<void> {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x38',
chainName: 'Binance Smart Chain Mainnet',
nativeCurrency: {
name: 'Binance Coin',
symbol: 'BNB',
decimals: 18
},
rpcUrls: ['https://bsc-dataseed1.binance.org/'],
blockExplorerUrls: ['https://bscscan.com']
}]
});
}
// 其他方法的占位实现
async disconnect(): Promise<void> {}
async getNetworkStatus(): Promise<NetworkStatus> { return {} as NetworkStatus; }
async getBalance(address: string, tokenAddress?: string): Promise<AssetBalance> { return {} as AssetBalance; }
async sendTransaction(transaction: TransactionRequest): Promise<TransactionResponse> { return {} as TransactionResponse; }
async estimateGas(transaction: TransactionRequest): Promise<GasEstimate> { return {} as GasEstimate; }
async getTransactionReceipt(txHash: string): Promise<TransactionReceipt> { return {} as TransactionReceipt; }
subscribeToBlocks(callback: (block: Block) => void): () => void { return () => {}; }
subscribeToLogs(filter: LogFilter, callback: (log: Log) => void): () => void { return () => {}; }
}
// 跨链状态管理器
class CrossChainStateManager {
private accountStates = new Map<string, Map<string, AccountState>>();
private transactionCache = new Map<string, CrossChainTransaction>();
private syncWorkers = new Map<string, StateSync>();
constructor(private config: StateConfig) {
this.initializeSyncWorkers();
}
async updateAccountState(chainId: string, accountData: AccountData): Promise<void> {
if (!this.accountStates.has(chainId)) {
this.accountStates.set(chainId, new Map());
}
const chainStates = this.accountStates.get(chainId)!;
chainStates.set(accountData.address, {
address: accountData.address,
balance: accountData.balance,
nonce: accountData.nonce,
lastUpdated: Date.now(),
chainId
});
// 触发状态同步事件
this.emit('stateUpdated', { chainId, address: accountData.address });
}
async getAccountState(chainId: string, address: string): Promise<AccountState | null> {
const chainStates = this.accountStates.get(chainId);
return chainStates?.get(address) || null;
}
async getAllAccountStates(address: string): Promise<Map<string, AccountState>> {
const result = new Map<string, AccountState>();
for (const [chainId, chainStates] of this.accountStates) {
const accountState = chainStates.get(address);
if (accountState) {
result.set(chainId, accountState);
}
}
return result;
}
private initializeSyncWorkers(): void {
for (const chainId of this.config.supportedChains) {
const syncWorker = new StateSync(chainId, this.config.sync);
this.syncWorkers.set(chainId, syncWorker);
syncWorker.on('stateChange', (change) => {
this.handleStateChange(chainId, change);
});
}
}
private handleStateChange(chainId: string, change: StateChange): void {
// 处理链状态变化
this.emit('chainStateChange', { chainId, change });
}
private emit(event: string, data: any): void {
// 事件发射实现
}
}
// 跨链资产管理器
class CrossChainAssetManager {
private assetRegistry = new Map<string, AssetInfo>();
private bridgeRegistry = new Map<string, BridgeInfo>();
constructor(private config: AssetConfig) {
this.loadAssetRegistry();
this.loadBridgeRegistry();
}
async getAssetInfo(chainId: string, tokenAddress: string): Promise<AssetInfo | null> {
const key = `${chainId}:${tokenAddress}`;
return this.assetRegistry.get(key) || null;
}
async aggregateAssetBalances(balances: ChainBalance[]): Promise<AggregatedBalance[]> {
const aggregated = new Map<string, AggregatedBalance>();
for (const chainBalance of balances) {
if (!chainBalance.balance) continue;
for (const asset of chainBalance.balance.assets) {
const assetId = this.getAssetId(asset);
if (!aggregated.has(assetId)) {
aggregated.set(assetId, {
assetId,
symbol: asset.symbol,
totalBalance: '0',
chainBalances: []
});
}
const agg = aggregated.get(assetId)!;
agg.chainBalances.push({
chainId: chainBalance.chainId,
balance: asset.balance,
formattedBalance: asset.formattedBalance
});
// 累加总余额(需要处理精度)
agg.totalBalance = this.addBalances(agg.totalBalance, asset.balance, asset.decimals);
}
}
return Array.from(aggregated.values());
}
private getAssetId(asset: AssetBalance): string {
// 生成资产唯一标识
if (asset.tokenAddress) {
return `token:${asset.symbol}`;
}
return `native:${asset.symbol}`;
}
private addBalances(balance1: string, balance2: string, decimals: number): string {
// 安全的大数加法实现
const bn1 = ethers.BigNumber.from(balance1 || '0');
const bn2 = ethers.BigNumber.from(balance2 || '0');
return bn1.add(bn2).toString();
}
private loadAssetRegistry(): void {
// 加载资产注册表
}
private loadBridgeRegistry(): void {
// 加载跨链桥注册表
}
}
// 使用示例
async function initializeCrossChainWallet() {
const config: CrossChainConfig = {
chains: {
ethereum: { rpc: 'https://mainnet.infura.io/v3/YOUR_API_KEY' },
bsc: { rpc: 'https://bsc-dataseed1.binance.org/' },
polygon: { rpc: 'https://polygon-rpc.com/' }
},
bridges: {
// 跨链桥配置
},
state: {
supportedChains: ['1', '56', '137'],
sync: { interval: 30000 }
},
assets: {
// 资产配置
}
};
const walletManager = new CrossChainWalletManager(config);
// 注册链适配器
walletManager.registerChain('1', new EthereumChainAdapter());
walletManager.registerChain('56', new BSCChainAdapter());
// 连接到多个链
const ethConnection = await walletManager.connectWallet('1');
const bscConnection = await walletManager.connectWallet('56');
// 获取跨链资产余额
const address = '0x742d35Cc6635C0532925a3b8D8fEAB4D7E7C3278';
const crossChainBalance = await walletManager.getAssetBalance(address);
console.log('Cross-chain balance:', crossChainBalance);
// 执行跨链转账
const transferResult = await walletManager.crossChainTransfer({
from: address,
to: '0xRecipientAddress',
asset: 'USDT',
amount: '100',
fromChain: '1',
toChain: '56'
});
console.log('Cross-chain transfer:', transferResult);
}
// 类型定义
interface CrossChainConfig {
chains: Record<string, ChainConfig>;
bridges: Record<string, BridgeConfig>;
state: StateConfig;
assets: AssetConfig;
}
interface ChainConfig {
rpc: string;
}
interface StateConfig {
supportedChains: string[];
sync: { interval: number };
}
interface AssetConfig {}
interface BridgeConfig {}
interface ConnectOptions {}
interface Currency { name: string; symbol: string; decimals: number; }
interface WalletConnection {}
interface NetworkStatus {}
interface AssetBalance {
address: string;
symbol: string;
decimals: number;
balance: string;
formattedBalance: string;
tokenAddress?: string;
}
interface TransactionRequest {
to?: string;
value?: any;
data?: string;
gasLimit?: any;
gasPrice?: any;
}
interface TransactionResponse {
hash: string;
from?: string;
to?: string;
value?: string;
gasLimit?: string;
gasPrice?: string;
nonce?: number;
chainId: string;
wait: () => Promise<any>;
}
interface GasEstimate {}
interface TransactionReceipt {}
interface Block {}
interface LogFilter {}
interface Log {}
interface CrossChainWalletConnection {}
interface CrossChainAssetBalance {}
interface CrossChainTransferParams {
from: string;
to: string;
asset: string;
amount: string;
fromChain: string;
toChain: string;
}
interface CrossChainTransaction {}
interface CrossChainNetworkStatus {
timestamp: number;
networks: any[];
overallHealth: boolean;
}
interface CrossChainError extends Error {}
interface AccountData { address: string; balance: any; nonce: number; }
interface AccountState {
address: string;
balance: any;
nonce: number;
lastUpdated: number;
chainId: string;
}
interface StateChange {}
interface AssetInfo {}
interface BridgeInfo {}
interface ChainBalance { chainId: string; balance?: { assets: AssetBalance[] }; }
interface AggregatedBalance {
assetId: string;
symbol: string;
totalBalance: string;
chainBalances: { chainId: string; balance: string; formattedBalance: string; }[];
}
// 占位类实现
class CrossChainBridgeManager {
constructor(config: any) {}
async findOptimalRoute(fromChain: string, toChain: string, asset: string): Promise<any> { return null; }
}
class CrossChainEventBus {
emit(event: string, data: any): void {}
}
class EthereumWalletConnection {
constructor(config: any) {}
}
class BSCWalletConnection {
constructor(config: any) {}
}
class StateSync {
constructor(chainId: string, config: any) {}
on(event: string, handler: (data: any) => void): void {}
}
跨链集成关键技术:
最佳实践建议: