DApp与传统Web应用有什么主要区别?
What are the main differences between DApp and traditional web applications?
*考察点:DApp核心概念理解。*
共 30 道题目
What are the main differences between DApp and traditional web applications?
What are the main differences between DApp and traditional web applications?
考察点:DApp核心概念理解。
答案:
DApp(去中心化应用)与传统Web应用的主要区别体现在架构设计、数据存储、身份验证和用户交互等多个层面。传统Web应用通常采用中心化的服务器-客户端架构,而DApp基于区块链技术构建,具有去中心化的特性。
主要区别:
技术实现差异:
// 传统Web应用 - API调用
const response = await fetch('/api/user/profile', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify(userData)
});
// DApp - 智能合约交互
const contract = new ethers.Contract(contractAddress, abi, signer);
const tx = await contract.updateProfile(userData);
await tx.wait(); // 等待区块确认
What core components are typically included in DApp frontend architecture?
What core components are typically included in DApp frontend architecture?
考察点:DApp架构设计基础。
答案:
DApp前端架构需要处理复杂的区块链交互、状态管理和用户体验优化。其核心组件设计需要考虑去中心化特性、异步交互和多种区块链网络的兼容性。
核心组件:
架构示例:
// DApp核心架构层次
DApp Frontend
├── Presentation Layer (React/Vue Components)
├── State Management (Redux/Zustand + Web3 State)
├── Web3 Integration Layer
│ ├── Wallet Connection Manager
│ ├── Contract Interaction Service
│ ├── Transaction Manager
│ └── Network Manager
├── Utility Layer
│ ├── Error Handler
│ ├── Cache Manager
│ └── BigNumber Formatter
└── External Services
├── IPFS Client
├── Subgraph Client
└── Price Oracle API
How to manage user authentication state in DApp?
How to manage user authentication state in DApp?
考察点:身份验证机制。
答案:
DApp的身份验证基于区块链地址和数字签名,无需传统的用户名密码系统。用户通过数字钱包连接DApp,钱包地址即为用户身份标识。身份验证状态管理需要处理钱包连接、地址变化、网络切换等多种场景。
身份验证流程:
实现示例:
// 身份验证状态管理
class AuthManager {
constructor() {
this.user = null;
this.isConnected = false;
this.listeners = [];
}
// 连接钱包
async connectWallet() {
try {
if (typeof window.ethereum !== 'undefined') {
const accounts = await window.ethereum.request({
method: 'eth_requestAccounts'
});
if (accounts.length > 0) {
this.user = {
address: accounts[0],
chainId: await window.ethereum.request({ method: 'eth_chainId' })
};
this.isConnected = true;
this.saveToStorage();
this.notifyListeners();
}
}
} catch (error) {
console.error('钱包连接失败:', error);
}
}
// 消息签名验证
async verifySignature(message) {
try {
const signature = await window.ethereum.request({
method: 'personal_sign',
params: [message, this.user.address]
});
return signature;
} catch (error) {
console.error('签名验证失败:', error);
return null;
}
}
// 监听账户变化
setupEventListeners() {
if (window.ethereum) {
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
this.logout();
} else {
this.user.address = accounts[0];
this.notifyListeners();
}
});
window.ethereum.on('chainChanged', (chainId) => {
this.user.chainId = chainId;
this.notifyListeners();
});
}
}
}
状态管理策略:
What is a Web3 Provider? What is its role in DApp?
What is a Web3 Provider? What is its role in DApp?
考察点:Provider概念理解。
答案:
Web3 Provider是DApp与区块链网络之间的通信桥梁,它提供了一套标准化的API接口,使前端应用能够与以太坊或其他兼容区块链进行交互。Provider负责处理网络连接、账户管理、交易签名等核心功能。
Provider的核心作用:
常见Provider类型:
// 1. 浏览器内置Provider (MetaMask)
if (typeof window.ethereum !== 'undefined') {
const provider = window.ethereum;
// 请求连接账户
await provider.request({ method: 'eth_requestAccounts' });
}
// 2. ethers.js Provider
import { ethers } from 'ethers';
// 使用浏览器Provider
const provider = new ethers.providers.Web3Provider(window.ethereum);
// 使用RPC Provider
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR-PROJECT-ID');
// 3. WalletConnect Provider
import WalletConnectProvider from '@walletconnect/web3-provider';
const provider = new WalletConnectProvider({
infuraId: 'YOUR-INFURA-ID',
qrcode: true
});
await provider.enable();
Provider使用示例:
// 获取账户信息
const accounts = await provider.request({ method: 'eth_accounts' });
const balance = await provider.request({
method: 'eth_getBalance',
params: [accounts[0], 'latest']
});
// 发送交易
const transactionParameters = {
to: '0x...',
value: ethers.utils.parseEther('1.0'),
gasLimit: '21000',
};
const txHash = await provider.request({
method: 'eth_sendTransaction',
params: [transactionParameters]
});
Provider管理最佳实践:
How does state management in DApp differ from traditional applications?
How does state management in DApp differ from traditional applications?
考察点:状态管理差异。
答案:
DApp的状态管理比传统应用更加复杂,需要同时处理链上状态和链下状态,并应对区块链的异步特性、不可逆性和网络延迟等挑战。状态管理策略必须考虑数据一致性、用户体验和性能优化的平衡。
主要差异:
状态管理架构:
// DApp状态管理结构
const dappState = {
// 用户相关状态
user: {
address: null,
balance: null,
isConnected: false,
chainId: null
},
// 链上状态 (只读,通过合约查询)
onChain: {
contractData: {},
tokenBalances: {},
transactionHistory: []
},
// 链下状态 (本地管理)
offChain: {
uiState: {},
cache: {},
pendingTransactions: []
},
// 交易状态管理
transactions: {
pending: [],
confirmed: [],
failed: []
}
};
// 状态更新策略
class DAppStateManager {
constructor() {
this.state = dappState;
this.listeners = [];
}
// 乐观更新 - 立即更新UI,等待链上确认
async optimisticUpdate(action, txPromise) {
// 1. 立即更新本地状态
this.updateLocalState(action);
// 2. 等待交易确认
try {
const receipt = await txPromise;
this.confirmTransaction(action, receipt);
} catch (error) {
// 3. 交易失败时回滚状态
this.revertState(action);
throw error;
}
}
// 监听链上事件更新状态
subscribeToChainEvents() {
contract.on('Transfer', (from, to, amount) => {
this.syncChainState();
});
}
}
状态同步策略:
// 实时状态同步
class StateSync {
// 定期同步链上状态
async syncChainState() {
const [balance, contractData] = await Promise.all([
provider.getBalance(userAddress),
contract.getData()
]);
this.updateState({
user: { balance },
onChain: { contractData }
});
}
// 处理pending交易状态
trackPendingTransaction(txHash) {
const interval = setInterval(async () => {
const receipt = await provider.getTransactionReceipt(txHash);
if (receipt) {
clearInterval(interval);
this.confirmTransaction(txHash, receipt);
}
}, 3000);
}
}
最佳实践:
How to handle asynchronous blockchain interactions in DApp?
How to handle asynchronous blockchain interactions in DApp?
考察点:异步处理基础。
答案:
区块链交互本质上是异步的,包括网络请求延迟、交易确认等待、区块生成时间等。DApp需要妥善处理这些异步操作,提供良好的用户体验,同时确保数据的准确性和一致性。
异步处理策略:
基本异步处理模式:
// 1. 基础异步交互模式
class BlockchainService {
async executeTransaction(contractMethod, ...args) {
try {
// 设置loading状态
this.setLoading(true);
// 估算Gas费用
const gasEstimate = await contractMethod.estimateGas(...args);
// 发送交易
const tx = await contractMethod(...args, {
gasLimit: gasEstimate.mul(120).div(100) // 增加20%缓冲
});
// 返回交易哈希,开始监听确认
this.trackTransaction(tx.hash);
return tx;
} catch (error) {
this.handleError(error);
throw error;
} finally {
this.setLoading(false);
}
}
// 交易确认监听
async trackTransaction(txHash) {
return new Promise((resolve, reject) => {
const checkConfirmation = async () => {
try {
const receipt = await provider.getTransactionReceipt(txHash);
if (receipt) {
if (receipt.status === 1) {
resolve(receipt);
} else {
reject(new Error('Transaction failed'));
}
} else {
// 继续等待确认
setTimeout(checkConfirmation, 3000);
}
} catch (error) {
reject(error);
}
};
checkConfirmation();
});
}
}
React Hook异步处理:
// 自定义Hook处理区块链交互
function useContractCall(contract, method, args = []) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const execute = useCallback(async (...newArgs) => {
try {
setLoading(true);
setError(null);
const result = await contract[method](...(newArgs.length ? newArgs : args));
setData(result);
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, [contract, method, args]);
return { data, loading, error, execute };
}
// 使用示例
function TokenBalance() {
const { data: balance, loading, execute: fetchBalance } = useContractCall(
tokenContract,
'balanceOf',
[userAddress]
);
useEffect(() => {
fetchBalance();
}, [fetchBalance]);
if (loading) return <Spinner />;
return <div>余额: {ethers.utils.formatEther(balance || 0)} ETH</div>;
}
并发处理和批量操作:
// 批量异步调用优化
class BatchProcessor {
async batchCall(calls) {
try {
// 使用Promise.all并发执行
const results = await Promise.all(
calls.map(async (call) => {
try {
return await call();
} catch (error) {
return { error };
}
})
);
return results;
} catch (error) {
throw new Error('Batch operation failed');
}
}
// 错误重试机制
async retryCall(fn, maxRetries = 3, delay = 1000) {
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, i)));
}
}
}
}
用户体验优化:
What is transaction confirmation? How to display transaction status in UI?
What is transaction confirmation? How to display transaction status in UI?
考察点:交易状态管理。
答案:
交易确认是指交易被包含在区块中并得到网络认可的过程。每个新区块的产生都会为之前的交易增加一次确认,确认数越多,交易被回滚的概率越低。通常1-3个确认即可认为交易安全,重要交易建议等待6个或更多确认。
交易生命周期:
UI状态设计:
// 交易状态枚举
const TransactionStatus = {
IDLE: 'idle', // 初始状态
PREPARING: 'preparing', // 准备交易
PENDING: 'pending', // 等待确认
CONFIRMING: 'confirming', // 确认中
SUCCESS: 'success', // 成功
FAILED: 'failed' // 失败
};
// 交易状态组件
function TransactionStatus({ tx }) {
const [status, setStatus] = useState(TransactionStatus.IDLE);
const [confirmations, setConfirmations] = useState(0);
const [error, setError] = useState(null);
const handleTransaction = async () => {
try {
setStatus(TransactionStatus.PREPARING);
// 发送交易
const transaction = await contract.transfer(to, amount);
setStatus(TransactionStatus.PENDING);
// 监听确认
const receipt = await transaction.wait(1); // 等待1个确认
setStatus(TransactionStatus.CONFIRMING);
// 监听更多确认
watchConfirmations(transaction.hash);
} catch (err) {
setStatus(TransactionStatus.FAILED);
setError(err.message);
}
};
const watchConfirmations = (txHash) => {
const interval = setInterval(async () => {
try {
const currentBlock = await provider.getBlockNumber();
const receipt = await provider.getTransactionReceipt(txHash);
if (receipt) {
const confirmationCount = currentBlock - receipt.blockNumber + 1;
setConfirmations(confirmationCount);
if (confirmationCount >= 3) {
setStatus(TransactionStatus.SUCCESS);
clearInterval(interval);
}
}
} catch (error) {
clearInterval(interval);
}
}, 15000); // 每15秒检查一次
};
return (
<div className="transaction-status">
{status === TransactionStatus.PREPARING && (
<div className="status preparing">
<Spinner /> 准备交易中...
</div>
)}
{status === TransactionStatus.PENDING && (
<div className="status pending">
<Spinner /> 交易已提交,等待打包...
<small>交易哈希: {tx?.hash}</small>
</div>
)}
{status === TransactionStatus.CONFIRMING && (
<div className="status confirming">
<ProgressBar value={confirmations} max={3} />
确认中 ({confirmations}/3)
</div>
)}
{status === TransactionStatus.SUCCESS && (
<div className="status success">
<CheckIcon /> 交易成功!({confirmations} 个确认)
</div>
)}
{status === TransactionStatus.FAILED && (
<div className="status failed">
<ErrorIcon /> 交易失败: {error}
</div>
)}
</div>
);
}
状态展示最佳实践:
// 交易状态管理Hook
function useTransactionTracker(txHash) {
const [state, setState] = useState({
status: 'pending',
confirmations: 0,
blockNumber: null,
gasUsed: null
});
useEffect(() => {
if (!txHash) return;
const trackTransaction = async () => {
try {
// 获取交易receipt
const receipt = await provider.waitForTransaction(txHash, 1);
if (receipt.status === 0) {
setState(prev => ({ ...prev, status: 'failed' }));
return;
}
setState(prev => ({
...prev,
status: 'confirmed',
blockNumber: receipt.blockNumber,
gasUsed: receipt.gasUsed
}));
// 继续监听更多确认
const subscription = provider.on('block', (blockNumber) => {
const confirmations = blockNumber - receipt.blockNumber + 1;
setState(prev => ({
...prev,
confirmations,
status: confirmations >= 3 ? 'finalized' : 'confirmed'
}));
});
return () => provider.removeAllListeners('block');
} catch (error) {
setState(prev => ({ ...prev, status: 'failed', error }));
}
};
trackTransaction();
}, [txHash]);
return state;
}
用户体验增强:
How to handle network errors and timeouts in DApp?
How to handle network errors and timeouts in DApp?
考察点:错误处理基础。
答案:
DApp面临的网络错误比传统Web应用更复杂,包括RPC节点故障、网络拥堵、Gas费不足、交易被拒绝等。有效的错误处理策略需要识别错误类型、提供用户友好的提示,并实现自动恢复机制。
常见错误类型:
错误处理实现:
// 错误分类和处理
class DAppErrorHandler {
static handleError(error) {
// 用户拒绝错误
if (error.code === 4001) {
return {
type: 'USER_REJECTED',
message: '用户取消了操作',
recoverable: true
};
}
// Gas相关错误
if (error.message.includes('insufficient funds')) {
return {
type: 'INSUFFICIENT_FUNDS',
message: '余额不足,请检查ETH余额',
recoverable: true,
action: 'CHECK_BALANCE'
};
}
// 网络错误
if (error.message.includes('network') || error.code === 'NETWORK_ERROR') {
return {
type: 'NETWORK_ERROR',
message: '网络连接失败,请检查网络设置',
recoverable: true,
action: 'RETRY'
};
}
// 合约执行错误
if (error.message.includes('revert')) {
return {
type: 'CONTRACT_REVERT',
message: '交易被合约拒绝,请检查操作条件',
recoverable: false
};
}
return {
type: 'UNKNOWN_ERROR',
message: error.message || '发生未知错误',
recoverable: false
};
}
}
// 重试机制
class RetryManager {
async executeWithRetry(fn, options = {}) {
const {
maxRetries = 3,
baseDelay = 1000,
maxDelay = 10000,
backoffFactor = 2
} = options;
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
const errorInfo = DAppErrorHandler.handleError(error);
// 不可恢复的错误直接抛出
if (!errorInfo.recoverable || attempt === maxRetries - 1) {
throw error;
}
// 计算延迟时间
const delay = Math.min(
baseDelay * Math.pow(backoffFactor, attempt),
maxDelay
);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
}
}
超时控制实现:
// 超时处理工具
class TimeoutManager {
// 带超时的Promise包装
static withTimeout(promise, timeoutMs = 30000) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => {
reject(new Error(`操作超时 (${timeoutMs}ms)`));
}, timeoutMs);
})
]);
}
// 可取消的网络请求
static createCancellableRequest(requestFn, timeoutMs) {
let timeoutId;
let cancelled = false;
const promise = new Promise(async (resolve, reject) => {
timeoutId = setTimeout(() => {
cancelled = true;
reject(new Error('Request timeout'));
}, timeoutMs);
try {
if (!cancelled) {
const result = await requestFn();
if (!cancelled) {
clearTimeout(timeoutId);
resolve(result);
}
}
} catch (error) {
if (!cancelled) {
clearTimeout(timeoutId);
reject(error);
}
}
});
return {
promise,
cancel: () => {
cancelled = true;
clearTimeout(timeoutId);
}
};
}
}
React错误边界组件:
// DApp专用错误边界
class DAppErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
const errorDetails = DAppErrorHandler.handleError(error);
// 记录错误日志
console.error('DApp Error:', {
error,
errorInfo,
errorDetails
});
// 可选:发送错误报告到监控服务
this.reportError(error, errorInfo, errorDetails);
}
reportError = (error, errorInfo, errorDetails) => {
// 发送到错误监控服务
if (typeof window !== 'undefined' && window.analytics) {
window.analytics.track('DApp Error', {
errorType: errorDetails.type,
errorMessage: errorDetails.message,
stack: error.stack
});
}
};
render() {
if (this.state.hasError) {
const errorDetails = DAppErrorHandler.handleError(this.state.error);
return (
<div className="error-fallback">
<h2>出现错误</h2>
<p>{errorDetails.message}</p>
{errorDetails.action === 'RETRY' && (
<button onClick={() => window.location.reload()}>
重试
</button>
)}
{errorDetails.action === 'CHECK_BALANCE' && (
<button onClick={() => {/* 跳转到余额页面 */}}>
查看余额
</button>
)}
</div>
);
}
return this.props.children;
}
}
最佳实践:
What is Gas fee estimation? How to implement Gas fee estimation in frontend?
What is Gas fee estimation? How to implement Gas fee estimation in frontend?
考察点:Gas费用处理。
答案:
Gas费用是以太坊网络中执行交易或智能合约所需支付的费用,用于激励矿工打包交易。Gas费用估算帮助用户了解交易成本,选择合适的Gas价格以平衡交易速度和成本。前端需要实现准确的费用预估和用户友好的费用选择界面。
Gas费用组成:
EIP-1559费用结构(以太坊2.0后):
Gas费用估算实现:
// Gas费用估算服务
class GasEstimationService {
constructor(provider) {
this.provider = provider;
}
// 估算交易Gas Limit
async estimateGasLimit(transaction) {
try {
const gasEstimate = await this.provider.estimateGas(transaction);
// 增加15%的安全边距
return gasEstimate.mul(115).div(100);
} catch (error) {
console.error('Gas估算失败:', error);
// 返回默认值
return ethers.BigNumber.from('21000');
}
}
// 获取当前网络Gas价格
async getGasPrices() {
try {
// 获取网络建议的Gas价格
const feeData = await this.provider.getFeeData();
return {
slow: {
maxFeePerGas: feeData.maxFeePerGas?.mul(90).div(100), // 90%
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.mul(90).div(100)
},
standard: {
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas
},
fast: {
maxFeePerGas: feeData.maxFeePerGas?.mul(110).div(100), // 110%
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas?.mul(110).div(100)
}
};
} catch (error) {
console.error('获取Gas价格失败:', error);
throw error;
}
}
// 计算预估费用
async calculateTransactionCost(transaction) {
const [gasLimit, gasPrices] = await Promise.all([
this.estimateGasLimit(transaction),
this.getGasPrices()
]);
return {
gasLimit,
slow: {
...gasPrices.slow,
totalCost: gasLimit.mul(gasPrices.slow.maxFeePerGas)
},
standard: {
...gasPrices.standard,
totalCost: gasLimit.mul(gasPrices.standard.maxFeePerGas)
},
fast: {
...gasPrices.fast,
totalCost: gasLimit.mul(gasPrices.fast.maxFeePerGas)
}
};
}
}
Gas费用选择组件:
// Gas费用选择器组件
function GasFeeSelector({ transaction, onFeeSelect }) {
const [feeOptions, setFeeOptions] = useState(null);
const [selectedOption, setSelectedOption] = useState('standard');
const [loading, setLoading] = useState(false);
const [customFee, setCustomFee] = useState(false);
useEffect(() => {
estimateFees();
}, [transaction]);
const estimateFees = async () => {
setLoading(true);
try {
const gasService = new GasEstimationService(provider);
const fees = await gasService.calculateTransactionCost(transaction);
setFeeOptions(fees);
// 默认选择标准费用
onFeeSelect(fees.standard);
} catch (error) {
console.error('费用估算失败:', error);
} finally {
setLoading(false);
}
};
const handleOptionSelect = (option) => {
setSelectedOption(option);
setCustomFee(false);
onFeeSelect(feeOptions[option]);
};
if (loading) {
return <div className="gas-fee-loading">估算Gas费用中...</div>;
}
return (
<div className="gas-fee-selector">
<h3>选择交易费用</h3>
{feeOptions && (
<div className="fee-options">
{['slow', 'standard', 'fast'].map((option) => {
const fee = feeOptions[option];
const isSelected = selectedOption === option && !customFee;
return (
<div
key={option}
className={`fee-option ${isSelected ? 'selected' : ''}`}
onClick={() => handleOptionSelect(option)}
>
<div className="option-label">
{option === 'slow' && '慢速 (~5分钟)'}
{option === 'standard' && '标准 (~2分钟)'}
{option === 'fast' && '快速 (~30秒)'}
</div>
<div className="option-price">
{ethers.utils.formatEther(fee.totalCost)} ETH
</div>
<div className="option-gwei">
{ethers.utils.formatUnits(fee.maxFeePerGas, 'gwei')} Gwei
</div>
</div>
);
})}
</div>
)}
<button
className="custom-fee-toggle"
onClick={() => setCustomFee(!customFee)}
>
{customFee ? '使用预设费用' : '自定义费用'}
</button>
{customFee && (
<CustomGasFeeInput
defaultValues={feeOptions?.standard}
onChange={onFeeSelect}
/>
)}
</div>
);
}
// 自定义Gas费用输入组件
function CustomGasFeeInput({ defaultValues, onChange }) {
const [maxFee, setMaxFee] = useState(
ethers.utils.formatUnits(defaultValues?.maxFeePerGas || 0, 'gwei')
);
const [priorityFee, setPriorityFee] = useState(
ethers.utils.formatUnits(defaultValues?.maxPriorityFeePerGas || 0, 'gwei')
);
const handleSubmit = () => {
onChange({
maxFeePerGas: ethers.utils.parseUnits(maxFee, 'gwei'),
maxPriorityFeePerGas: ethers.utils.parseUnits(priorityFee, 'gwei')
});
};
return (
<div className="custom-gas-input">
<div className="input-group">
<label>最大费用 (Gwei)</label>
<input
type="number"
value={maxFee}
onChange={(e) => setMaxFee(e.target.value)}
step="0.1"
/>
</div>
<div className="input-group">
<label>优先费用 (Gwei)</label>
<input
type="number"
value={priorityFee}
onChange={(e) => setPriorityFee(e.target.value)}
step="0.1"
/>
</div>
<button onClick={handleSubmit}>应用自定义费用</button>
</div>
);
}
实用工具函数:
// Gas费用工具函数
const GasUtils = {
// 格式化Gas价格显示
formatGasPrice(gasPrice) {
const gwei = ethers.utils.formatUnits(gasPrice, 'gwei');
return `${parseFloat(gwei).toFixed(1)} Gwei`;
},
// 格式化总费用显示
formatTotalFee(totalFee) {
const eth = ethers.utils.formatEther(totalFee);
return `${parseFloat(eth).toFixed(6)} ETH`;
},
// 预估确认时间
estimateConfirmationTime(gasPrice, networkCondition) {
// 基于Gas价格和网络状况的简单预估
const basePrice = ethers.utils.parseUnits('20', 'gwei');
const ratio = gasPrice.div(basePrice);
if (ratio.gte(2)) return '< 1分钟';
if (ratio.gte(1)) return '1-3分钟';
return '3-10分钟';
}
};
最佳实践:
How to implement user-friendly loading states in DApp?
How to implement user-friendly loading states in DApp?
考察点:用户体验基础。
答案:
DApp中的加载状态比传统应用更复杂,需要处理钱包连接、交易确认、区块链查询等多种异步操作。良好的加载状态设计能显著提升用户体验,减少用户焦虑,并提供清晰的操作反馈。
加载状态分类:
基础加载组件实现:
// 通用加载状态Hook
function useLoadingState() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [progress, setProgress] = useState(0);
const withLoading = async (asyncFn, progressCallback) => {
try {
setLoading(true);
setError(null);
setProgress(0);
const result = await asyncFn(setProgress);
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
setProgress(0);
}
};
return { loading, error, progress, withLoading };
}
// 交易加载状态组件
function TransactionLoader({ transaction, onComplete }) {
const [stage, setStage] = useState('preparing');
const [progress, setProgress] = useState(0);
const [error, setError] = useState(null);
useEffect(() => {
if (transaction) {
processTransaction();
}
}, [transaction]);
const processTransaction = async () => {
try {
// 阶段1: 准备交易
setStage('preparing');
setProgress(20);
await new Promise(resolve => setTimeout(resolve, 500));
// 阶段2: 等待用户签名
setStage('signing');
setProgress(40);
const signedTx = await transaction.wait(0);
// 阶段3: 提交到网络
setStage('submitting');
setProgress(60);
// 阶段4: 等待确认
setStage('confirming');
setProgress(80);
const receipt = await signedTx.wait(1);
// 阶段5: 完成
setStage('completed');
setProgress(100);
onComplete(receipt);
} catch (err) {
setError(err);
setStage('failed');
}
};
const stageLabels = {
preparing: '准备交易...',
signing: '等待签名确认...',
submitting: '提交到区块链网络...',
confirming: '等待交易确认...',
completed: '交易成功完成!',
failed: '交易失败'
};
return (
<div className="transaction-loader">
<div className="loader-header">
<h3>{stageLabels[stage]}</h3>
{stage !== 'failed' && stage !== 'completed' && (
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
)}
</div>
<div className="stage-indicators">
{['preparing', 'signing', 'submitting', 'confirming', 'completed'].map((s, index) => (
<div
key={s}
className={`stage-indicator ${
s === stage ? 'active' :
['preparing', 'signing', 'submitting', 'confirming'].indexOf(stage) > index ? 'completed' : ''
}`}
>
{index + 1}
</div>
))}
</div>
{error && (
<div className="error-message">
错误: {error.message}
</div>
)}
</div>
);
}
骨架屏加载效果:
// 数据加载骨架屏组件
function DataSkeleton({ type = 'card', count = 1 }) {
const renderSkeleton = () => {
switch (type) {
case 'balance':
return (
<div className="skeleton-balance">
<div className="skeleton-line short" />
<div className="skeleton-line long" />
</div>
);
case 'transaction':
return (
<div className="skeleton-transaction">
<div className="skeleton-avatar" />
<div className="skeleton-content">
<div className="skeleton-line medium" />
<div className="skeleton-line short" />
</div>
<div className="skeleton-amount" />
</div>
);
case 'nft':
return (
<div className="skeleton-nft">
<div className="skeleton-image" />
<div className="skeleton-line short" />
<div className="skeleton-line medium" />
</div>
);
default:
return (
<div className="skeleton-card">
<div className="skeleton-line long" />
<div className="skeleton-line medium" />
<div className="skeleton-line short" />
</div>
);
}
};
return (
<div className="skeleton-container">
{Array.from({ length: count }, (_, i) => (
<div key={i} className="skeleton-item">
{renderSkeleton()}
</div>
))}
</div>
);
}
// 使用示例
function TokenList() {
const [tokens, setTokens] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadTokens();
}, []);
const loadTokens = async () => {
setLoading(true);
try {
const tokenData = await fetchUserTokens();
setTokens(tokenData);
} finally {
setLoading(false);
}
};
if (loading) {
return <DataSkeleton type="balance" count={5} />;
}
return (
<div className="token-list">
{tokens.map(token => (
<TokenCard key={token.address} token={token} />
))}
</div>
);
}
智能加载状态管理:
// 全局加载状态管理
class LoadingManager {
constructor() {
this.loadingStates = new Map();
this.listeners = [];
}
// 设置加载状态
setLoading(key, loading, message = '') {
this.loadingStates.set(key, { loading, message });
this.notifyListeners();
}
// 获取加载状态
isLoading(key) {
return this.loadingStates.get(key)?.loading || false;
}
// 获取所有加载状态
getAllLoadingStates() {
return Array.from(this.loadingStates.entries())
.filter(([_, state]) => state.loading)
.map(([key, state]) => ({ key, ...state }));
}
// 添加监听器
addListener(callback) {
this.listeners.push(callback);
}
// 通知监听器
notifyListeners() {
this.listeners.forEach(callback => callback(this.getAllLoadingStates()));
}
}
// React Hook封装
function useGlobalLoading() {
const [loadingStates, setLoadingStates] = useState([]);
useEffect(() => {
const loadingManager = LoadingManager.getInstance();
const handleLoadingChange = (states) => {
setLoadingStates(states);
};
loadingManager.addListener(handleLoadingChange);
return () => {
// 清理监听器
};
}, []);
return {
loadingStates,
isAnyLoading: loadingStates.length > 0,
setLoading: (key, loading, message) => {
LoadingManager.getInstance().setLoading(key, loading, message);
}
};
}
用户体验优化技巧:
How should local storage strategy be designed for DApp?
How should local storage strategy be designed for DApp?
考察点:数据存储策略。
答案:
DApp的本地存储策略需要平衡用户体验、数据安全和隐私保护。由于区块链数据的特殊性,需要区分不同类型的数据并采用相应的存储策略,同时考虑跨设备同步和数据一致性问题。
数据分类策略:
存储方案实现:
// 分层存储管理器
class DAppStorageManager {
constructor() {
this.storageTypes = {
SECURE: 'secure', // 加密存储
LOCAL: 'local', // 本地存储
SESSION: 'session', // 会话存储
MEMORY: 'memory', // 内存存储
CACHE: 'cache' // 缓存存储
};
this.memoryStorage = new Map();
this.encryptionKey = this.generateEncryptionKey();
}
// 根据数据类型选择存储方式
set(key, value, options = {}) {
const { type = this.storageTypes.LOCAL, ttl, encrypt = false } = options;
const data = {
value,
timestamp: Date.now(),
ttl: ttl ? Date.now() + ttl : null
};
if (encrypt) {
data.value = this.encrypt(JSON.stringify(value));
}
switch (type) {
case this.storageTypes.SECURE:
return this.setSecure(key, data);
case this.storageTypes.LOCAL:
return localStorage.setItem(key, JSON.stringify(data));
case this.storageTypes.SESSION:
return sessionStorage.setItem(key, JSON.stringify(data));
case this.storageTypes.MEMORY:
return this.memoryStorage.set(key, data);
case this.storageTypes.CACHE:
return this.setCache(key, data);
default:
throw new Error(`Unknown storage type: ${type}`);
}
}
// 获取数据
get(key, type = this.storageTypes.LOCAL) {
let data;
switch (type) {
case this.storageTypes.SECURE:
data = this.getSecure(key);
break;
case this.storageTypes.LOCAL:
data = localStorage.getItem(key);
break;
case this.storageTypes.SESSION:
data = sessionStorage.getItem(key);
break;
case this.storageTypes.MEMORY:
data = this.memoryStorage.get(key);
break;
case this.storageTypes.CACHE:
data = this.getCache(key);
break;
default:
return null;
}
if (!data) return null;
// 解析JSON数据
if (typeof data === 'string') {
try {
data = JSON.parse(data);
} catch {
return null;
}
}
// 检查TTL过期
if (data.ttl && Date.now() > data.ttl) {
this.remove(key, type);
return null;
}
return data.value;
}
// 安全存储实现(使用Web Crypto API)
async setSecure(key, data) {
try {
const encrypted = await this.encryptData(JSON.stringify(data));
localStorage.setItem(`secure_${key}`, encrypted);
} catch (error) {
console.error('Secure storage failed:', error);
}
}
async getSecure(key) {
try {
const encrypted = localStorage.getItem(`secure_${key}`);
if (!encrypted) return null;
const decrypted = await this.decryptData(encrypted);
return JSON.parse(decrypted);
} catch (error) {
console.error('Secure retrieval failed:', error);
return null;
}
}
}
DApp专用存储配置:
// DApp存储配置
const DAppStorage = {
// 用户偏好设置
userPreferences: {
theme: { type: 'LOCAL', ttl: null },
language: { type: 'LOCAL', ttl: null },
currency: { type: 'LOCAL', ttl: null },
gasPreferences: { type: 'LOCAL', ttl: null }
},
// 连接状态
connectionState: {
lastConnectedWallet: { type: 'LOCAL', ttl: null },
connectedAddress: { type: 'SESSION', ttl: null },
networkId: { type: 'SESSION', ttl: null }
},
// 缓存数据
cache: {
tokenBalances: { type: 'CACHE', ttl: 300000 }, // 5分钟
tokenPrices: { type: 'CACHE', ttl: 60000 }, // 1分钟
transactionHistory: { type: 'CACHE', ttl: 600000 }, // 10分钟
contractAbi: { type: 'LOCAL', ttl: null } // 永久缓存
},
// 临时数据
temporary: {
formDrafts: { type: 'SESSION', ttl: null },
pendingTransactions: { type: 'MEMORY', ttl: 3600000 } // 1小时
}
};
// 存储操作封装
class DAppDataManager {
constructor() {
this.storage = new DAppStorageManager();
}
// 保存用户偏好
saveUserPreference(key, value) {
const config = DAppStorage.userPreferences[key];
if (config) {
this.storage.set(`pref_${key}`, value, config);
}
}
// 获取用户偏好
getUserPreference(key, defaultValue = null) {
const config = DAppStorage.userPreferences[key];
if (config) {
return this.storage.get(`pref_${key}`, config.type) || defaultValue;
}
return defaultValue;
}
// 缓存区块链数据
cacheBlockchainData(key, data) {
const config = DAppStorage.cache[key];
if (config) {
this.storage.set(`cache_${key}`, data, config);
}
}
// 获取缓存数据
getCachedData(key) {
const config = DAppStorage.cache[key];
if (config) {
return this.storage.get(`cache_${key}`, config.type);
}
return null;
}
// 清理过期数据
cleanup() {
// 清理过期缓存
Object.keys(DAppStorage.cache).forEach(key => {
this.storage.get(`cache_${key}`, DAppStorage.cache[key].type);
});
// 清理临时数据
Object.keys(DAppStorage.temporary).forEach(key => {
this.storage.get(`temp_${key}`, DAppStorage.temporary[key].type);
});
}
}
React Hook集成:
// DApp存储Hook
function useDAppStorage() {
const dataManager = useMemo(() => new DAppDataManager(), []);
// 用户偏好Hook
const useUserPreference = (key, defaultValue) => {
const [value, setValue] = useState(
() => dataManager.getUserPreference(key, defaultValue)
);
const updateValue = useCallback((newValue) => {
setValue(newValue);
dataManager.saveUserPreference(key, newValue);
}, [key]);
return [value, updateValue];
};
// 缓存数据Hook
const useCachedData = (key, fetchFn, dependencies = []) => {
const [data, setData] = useState(() => dataManager.getCachedData(key));
const [loading, setLoading] = useState(!data);
useEffect(() => {
if (!data) {
setLoading(true);
fetchFn().then(result => {
dataManager.cacheBlockchainData(key, result);
setData(result);
}).finally(() => {
setLoading(false);
});
}
}, dependencies);
const refresh = useCallback(() => {
setLoading(true);
fetchFn().then(result => {
dataManager.cacheBlockchainData(key, result);
setData(result);
}).finally(() => {
setLoading(false);
});
}, [key, fetchFn]);
return { data, loading, refresh };
};
return {
dataManager,
useUserPreference,
useCachedData
};
}
安全和隐私考虑:
How to handle display and calculation of large numbers (BigNumber) in DApp?
How to handle display and calculation of large numbers (BigNumber) in DApp?
考察点:数值处理基础。
答案:
区块链中的数值通常非常大,超出JavaScript原生Number类型的安全范围。例如以太坊使用wei作为最小单位,1 ETH = 10^18 wei。DApp必须使用BigNumber库进行精确的数值计算和显示,避免精度丢失和计算错误。
BigNumber使用场景:
基础BigNumber操作:
import { ethers } from 'ethers';
// BigNumber创建和转换
class BigNumberUtils {
// 创建BigNumber
static create(value, unit = 'wei') {
if (unit === 'ether' || unit === 'eth') {
return ethers.utils.parseEther(value.toString());
}
return ethers.BigNumber.from(value);
}
// 格式化显示
static format(bigNumber, decimals = 18, displayDecimals = 4) {
if (!bigNumber || bigNumber.isZero()) return '0';
// 转换为可读格式
const formatted = ethers.utils.formatUnits(bigNumber, decimals);
// 处理小数位数
const number = parseFloat(formatted);
if (number === 0) return '0';
if (number < 0.0001) return '< 0.0001';
return number.toLocaleString('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: displayDecimals
});
}
// 安全的数学运算
static safeAdd(a, b) {
try {
return ethers.BigNumber.from(a).add(ethers.BigNumber.from(b));
} catch (error) {
console.error('BigNumber addition error:', error);
return ethers.BigNumber.from(0);
}
}
static safeSub(a, b) {
try {
const bigA = ethers.BigNumber.from(a);
const bigB = ethers.BigNumber.from(b);
if (bigA.lt(bigB)) {
throw new Error('Subtraction would result in negative number');
}
return bigA.sub(bigB);
} catch (error) {
console.error('BigNumber subtraction error:', error);
return ethers.BigNumber.from(0);
}
}
static safeMul(a, b) {
try {
return ethers.BigNumber.from(a).mul(ethers.BigNumber.from(b));
} catch (error) {
console.error('BigNumber multiplication error:', error);
return ethers.BigNumber.from(0);
}
}
static safeDiv(a, b) {
try {
const bigB = ethers.BigNumber.from(b);
if (bigB.isZero()) {
throw new Error('Division by zero');
}
return ethers.BigNumber.from(a).div(bigB);
} catch (error) {
console.error('BigNumber division error:', error);
return ethers.BigNumber.from(0);
}
}
}
代币数量格式化组件:
// 代币数量显示组件
function TokenAmount({
amount,
decimals = 18,
symbol = '',
showFullPrecision = false,
prefix = '',
className = ''
}) {
const [showFull, setShowFull] = useState(showFullPrecision);
const formatAmount = (bigNumberAmount, showAll = false) => {
if (!bigNumberAmount) return '0';
try {
const formatted = ethers.utils.formatUnits(bigNumberAmount, decimals);
const number = parseFloat(formatted);
if (number === 0) return '0';
// 显示完整精度
if (showAll) {
return formatted;
}
// 智能显示策略
if (number >= 1000000) {
return (number / 1000000).toFixed(2) + 'M';
} else if (number >= 1000) {
return (number / 1000).toFixed(2) + 'K';
} else if (number >= 1) {
return number.toFixed(4);
} else if (number >= 0.0001) {
return number.toFixed(6);
} else {
return '< 0.0001';
}
} catch (error) {
console.error('Format amount error:', error);
return 'Error';
}
};
const displayAmount = formatAmount(amount, showFull);
const hasMorePrecision = amount && !ethers.BigNumber.from(amount).isZero() &&
displayAmount !== formatAmount(amount, true);
return (
<span className={`token-amount ${className}`}>
{prefix && <span className="prefix">{prefix}</span>}
<span
className={`amount ${hasMorePrecision ? 'clickable' : ''}`}
onClick={hasMorePrecision ? () => setShowFull(!showFull) : undefined}
title={showFull ? '点击简化显示' : '点击显示完整精度'}
>
{displayAmount}
</span>
{symbol && <span className="symbol">{symbol}</span>}
{hasMorePrecision && (
<span className="precision-toggle">
{showFull ? '⬆️' : '⬇️'}
</span>
)}
</span>
);
}
// 价格计算组件
function PriceCalculator({ tokenAmount, tokenPrice, tokenDecimals = 18 }) {
const calculateValue = () => {
try {
if (!tokenAmount || !tokenPrice) return ethers.BigNumber.from(0);
// 将代币数量转换为标准单位
const tokenInEther = ethers.utils.formatUnits(tokenAmount, tokenDecimals);
// 计算总价值 (使用字符串避免精度问题)
const totalValue = parseFloat(tokenInEther) * parseFloat(tokenPrice);
// 转换回BigNumber (以美分为单位保持精度)
return ethers.utils.parseUnits(totalValue.toFixed(2), 2);
} catch (error) {
console.error('Price calculation error:', error);
return ethers.BigNumber.from(0);
}
};
const totalValue = calculateValue();
return (
<div className="price-calculator">
<div className="token-amount">
<TokenAmount
amount={tokenAmount}
decimals={tokenDecimals}
symbol="TOKENS"
/>
</div>
<div className="multiplication">×</div>
<div className="token-price">
${tokenPrice}
</div>
<div className="equals">=</div>
<div className="total-value">
$<TokenAmount
amount={totalValue}
decimals={2}
showFullPrecision={true}
/>
</div>
</div>
);
}
输入验证和处理:
// BigNumber输入处理Hook
function useBigNumberInput(decimals = 18, maxValue = null) {
const [inputValue, setInputValue] = useState('');
const [bigNumberValue, setBigNumberValue] = useState(ethers.BigNumber.from(0));
const [error, setError] = useState('');
const handleInputChange = (value) => {
setInputValue(value);
setError('');
// 基础验证
if (!value || value === '') {
setBigNumberValue(ethers.BigNumber.from(0));
return;
}
// 验证数字格式
const numberRegex = /^\d*\.?\d*$/;
if (!numberRegex.test(value)) {
setError('请输入有效的数字');
return;
}
// 验证小数位数
const decimalPlaces = (value.split('.')[1] || '').length;
if (decimalPlaces > decimals) {
setError(`小数位数不能超过${decimals}位`);
return;
}
try {
// 转换为BigNumber
const bigNum = ethers.utils.parseUnits(value, decimals);
// 检查最大值限制
if (maxValue && bigNum.gt(maxValue)) {
setError('输入值超过最大限制');
return;
}
setBigNumberValue(bigNum);
} catch (err) {
setError('数值转换失败');
}
};
const setMaxValue = () => {
if (maxValue) {
const maxValueFormatted = ethers.utils.formatUnits(maxValue, decimals);
handleInputChange(maxValueFormatted);
}
};
return {
inputValue,
bigNumberValue,
error,
handleInputChange,
setMaxValue,
isValid: !error && !bigNumberValue.isZero()
};
}
// 使用示例
function TokenTransferForm() {
const {
inputValue,
bigNumberValue,
error,
handleInputChange,
setMaxValue,
isValid
} = useBigNumberInput(18, userBalance);
return (
<div className="transfer-form">
<div className="input-group">
<label>转账数量</label>
<input
type="text"
value={inputValue}
onChange={(e) => handleInputChange(e.target.value)}
placeholder="0.0"
className={error ? 'error' : ''}
/>
<button onClick={setMaxValue}>最大</button>
</div>
{error && <div className="error-message">{error}</div>}
<div className="balance-info">
余额: <TokenAmount amount={userBalance} decimals={18} />
</div>
<button disabled={!isValid}>
转账
</button>
</div>
);
}
最佳实践:
What is IPFS? How to integrate IPFS storage in DApp?
What is IPFS? How to integrate IPFS storage in DApp?
考察点:去中心化存储基础。
答案:
IPFS(InterPlanetary File System)是一个分布式的点对点文件系统,旨在创建一个全球统一的文件存储网络。IPFS使用内容寻址而非位置寻址,通过文件内容的哈希值来唯一标识文件,确保数据的不可篡改性和去中心化存储。
IPFS核心特性:
IPFS集成实现:
// IPFS客户端封装
import { create } from 'ipfs-http-client';
class IPFSManager {
constructor() {
// 连接到IPFS节点 (可以是本地节点或Infura等服务)
this.client = create({
host: 'ipfs.infura.io',
port: 5001,
protocol: 'https',
headers: {
authorization: `Basic ${Buffer.from(
`${projectId}:${projectSecret}`
).toString('base64')}`
}
});
}
// 上传文件到IPFS
async uploadFile(file) {
try {
const result = await this.client.add(file, {
progress: (prog) => console.log(`上传进度: ${prog}`)
});
return {
hash: result.cid.toString(),
path: result.path,
size: result.size
};
} catch (error) {
console.error('IPFS上传失败:', error);
throw error;
}
}
// 上传JSON数据
async uploadJSON(data) {
try {
const jsonString = JSON.stringify(data, null, 2);
const result = await this.client.add(jsonString);
return result.cid.toString();
} catch (error) {
console.error('JSON上传失败:', error);
throw error;
}
}
// 获取文件内容
async getFile(hash) {
try {
const stream = this.client.cat(hash);
let data = '';
for await (const chunk of stream) {
data += new TextDecoder().decode(chunk);
}
return data;
} catch (error) {
console.error('IPFS获取失败:', error);
throw error;
}
}
// 获取JSON数据
async getJSON(hash) {
try {
const data = await this.getFile(hash);
return JSON.parse(data);
} catch (error) {
console.error('JSON解析失败:', error);
throw error;
}
}
// 固定文件到节点
async pinFile(hash) {
try {
await this.client.pin.add(hash);
return true;
} catch (error) {
console.error('文件固定失败:', error);
return false;
}
}
// 生成IPFS网关URL
getGatewayUrl(hash, gateway = 'https://ipfs.io/ipfs/') {
return `${gateway}${hash}`;
}
}
React组件中的IPFS使用:
// IPFS文件上传组件
function IPFSFileUpload({ onUploadComplete }) {
const [uploading, setUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [uploadedFiles, setUploadedFiles] = useState([]);
const ipfsManager = useMemo(() => new IPFSManager(), []);
const handleFileUpload = async (files) => {
setUploading(true);
setProgress(0);
try {
const uploadPromises = Array.from(files).map(async (file, index) => {
const result = await ipfsManager.uploadFile(file);
setProgress(((index + 1) / files.length) * 100);
return {
name: file.name,
hash: result.hash,
size: result.size,
url: ipfsManager.getGatewayUrl(result.hash)
};
});
const results = await Promise.all(uploadPromises);
setUploadedFiles(prev => [...prev, ...results]);
if (onUploadComplete) {
onUploadComplete(results);
}
} catch (error) {
console.error('上传失败:', error);
} finally {
setUploading(false);
setProgress(0);
}
};
return (
<div className="ipfs-upload">
<div
className="upload-zone"
onDrop={(e) => {
e.preventDefault();
handleFileUpload(e.dataTransfer.files);
}}
onDragOver={(e) => e.preventDefault()}
>
{uploading ? (
<div className="upload-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
<span>上传中... {Math.round(progress)}%</span>
</div>
) : (
<div className="upload-prompt">
<p>拖拽文件到此处或点击选择</p>
<input
type="file"
multiple
onChange={(e) => handleFileUpload(e.target.files)}
style={{ display: 'none' }}
/>
</div>
)}
</div>
{uploadedFiles.length > 0 && (
<div className="uploaded-files">
<h4>已上传文件</h4>
{uploadedFiles.map((file, index) => (
<div key={index} className="file-item">
<span className="file-name">{file.name}</span>
<span className="file-hash">{file.hash}</span>
<a
href={file.url}
target="_blank"
rel="noopener noreferrer"
>
查看
</a>
</div>
))}
</div>
)}
</div>
);
}
// NFT元数据上传组件
function NFTMetadataUpload({ onMetadataUploaded }) {
const [metadata, setMetadata] = useState({
name: '',
description: '',
image: '',
attributes: []
});
const [uploading, setUploading] = useState(false);
const ipfsManager = useMemo(() => new IPFSManager(), []);
const handleImageUpload = async (file) => {
try {
const result = await ipfsManager.uploadFile(file);
setMetadata(prev => ({
...prev,
image: ipfsManager.getGatewayUrl(result.hash)
}));
} catch (error) {
console.error('图片上传失败:', error);
}
};
const handleMetadataUpload = async () => {
setUploading(true);
try {
// 验证元数据
if (!metadata.name || !metadata.image) {
throw new Error('名称和图片是必需的');
}
// 上传元数据JSON
const hash = await ipfsManager.uploadJSON(metadata);
const metadataUrl = ipfsManager.getGatewayUrl(hash);
if (onMetadataUploaded) {
onMetadataUploaded({
hash,
url: metadataUrl,
metadata
});
}
} catch (error) {
console.error('元数据上传失败:', error);
} finally {
setUploading(false);
}
};
return (
<div className="nft-metadata-upload">
<div className="metadata-form">
<input
type="text"
placeholder="NFT名称"
value={metadata.name}
onChange={(e) => setMetadata(prev => ({
...prev,
name: e.target.value
}))}
/>
<textarea
placeholder="NFT描述"
value={metadata.description}
onChange={(e) => setMetadata(prev => ({
...prev,
description: e.target.value
}))}
/>
<div className="image-upload">
<input
type="file"
accept="image/*"
onChange={(e) => {
if (e.target.files[0]) {
handleImageUpload(e.target.files[0]);
}
}}
/>
{metadata.image && (
<img src={metadata.image} alt="Preview" className="image-preview" />
)}
</div>
<button
onClick={handleMetadataUpload}
disabled={uploading || !metadata.name || !metadata.image}
>
{uploading ? '上传中...' : '上传元数据'}
</button>
</div>
</div>
);
}
IPFS数据获取Hook:
// IPFS数据获取Hook
function useIPFSData(hash) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const ipfsManager = useMemo(() => new IPFSManager(), []);
useEffect(() => {
if (!hash) return;
const fetchData = async () => {
setLoading(true);
setError(null);
try {
// 尝试解析为JSON,失败则返回原始文本
let result;
try {
result = await ipfsManager.getJSON(hash);
} catch {
result = await ipfsManager.getFile(hash);
}
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, [hash]);
return { data, loading, error };
}
// 使用示例
function IPFSDataViewer({ hash }) {
const { data, loading, error } = useIPFSData(hash);
if (loading) return <div>加载IPFS数据中...</div>;
if (error) return <div>错误: {error}</div>;
if (!data) return <div>无数据</div>;
return (
<div className="ipfs-data-viewer">
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
IPFS应用场景:
最佳实践:
What special considerations are there for form validation in DApp compared to traditional applications?
What special considerations are there for form validation in DApp compared to traditional applications?
考察点:表单处理差异。
答案:
DApp表单验证除了传统的客户端验证外,还需要考虑区块链特有的约束条件,如Gas费用、网络状态、智能合约限制等。验证策略需要在用户体验和交易成本之间找到平衡,避免用户因验证不充分而产生失败交易的Gas费损失。
DApp表单验证特殊考虑:
DApp专用验证实现:
// DApp表单验证器
class DAppFormValidator {
constructor(provider, userAddress) {
this.provider = provider;
this.userAddress = userAddress;
}
// 验证用户余额
async validateBalance(requiredAmount, tokenContract = null) {
try {
let balance;
if (tokenContract) {
// ERC20代币余额
balance = await tokenContract.balanceOf(this.userAddress);
} else {
// ETH余额
balance = await this.provider.getBalance(this.userAddress);
}
if (balance.lt(requiredAmount)) {
return {
valid: false,
error: '余额不足',
current: balance,
required: requiredAmount
};
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: '余额查询失败'
};
}
}
// 验证Gas费用
async validateGasFee(transaction) {
try {
// 估算Gas
const gasEstimate = await this.provider.estimateGas(transaction);
const feeData = await this.provider.getFeeData();
const gasCost = gasEstimate.mul(feeData.gasPrice || feeData.maxFeePerGas);
// 检查ETH余额是否足够支付Gas
const ethBalance = await this.provider.getBalance(this.userAddress);
if (ethBalance.lt(gasCost)) {
return {
valid: false,
error: 'ETH余额不足以支付Gas费用',
gasCost,
ethBalance
};
}
return {
valid: true,
gasCost,
gasEstimate
};
} catch (error) {
return {
valid: false,
error: 'Gas费用估算失败'
};
}
}
// 验证网络
async validateNetwork(requiredChainId) {
try {
const network = await this.provider.getNetwork();
if (network.chainId !== requiredChainId) {
return {
valid: false,
error: '请切换到正确的网络',
current: network.chainId,
required: requiredChainId
};
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: '网络检查失败'
};
}
}
// 验证地址格式
validateAddress(address) {
if (!ethers.utils.isAddress(address)) {
return {
valid: false,
error: '无效的地址格式'
};
}
if (address === ethers.constants.AddressZero) {
return {
valid: false,
error: '不能使用零地址'
};
}
return { valid: true };
}
// 验证代币数量
validateTokenAmount(amount, decimals = 18, maxAmount = null) {
try {
const parsedAmount = ethers.utils.parseUnits(amount.toString(), decimals);
if (parsedAmount.isZero()) {
return {
valid: false,
error: '数量必须大于0'
};
}
if (maxAmount && parsedAmount.gt(maxAmount)) {
return {
valid: false,
error: '数量超过最大限制'
};
}
return {
valid: true,
parsedAmount
};
} catch (error) {
return {
valid: false,
error: '无效的数量格式'
};
}
}
// 智能合约状态验证
async validateContractState(contract, validationRules) {
const results = {};
for (const [key, rule] of Object.entries(validationRules)) {
try {
const value = await contract[rule.method](...(rule.params || []));
if (rule.validator) {
const validation = rule.validator(value);
results[key] = validation;
} else {
results[key] = { valid: true, value };
}
} catch (error) {
results[key] = {
valid: false,
error: `合约状态检查失败: ${key}`
};
}
}
return results;
}
}
React表单验证Hook:
// DApp表单验证Hook
function useDAppFormValidation(provider, userAddress, chainId) {
const [errors, setErrors] = useState({});
const [validating, setValidating] = useState(false);
const validator = useMemo(() =>
new DAppFormValidator(provider, userAddress),
[provider, userAddress]
);
const validateField = async (fieldName, value, validationConfig) => {
setValidating(true);
try {
let result = { valid: true };
switch (validationConfig.type) {
case 'address':
result = validator.validateAddress(value);
break;
case 'tokenAmount':
result = validator.validateTokenAmount(
value,
validationConfig.decimals,
validationConfig.maxAmount
);
break;
case 'balance':
result = await validator.validateBalance(
value,
validationConfig.tokenContract
);
break;
case 'network':
result = await validator.validateNetwork(chainId);
break;
default:
// 自定义验证函数
if (validationConfig.customValidator) {
result = await validationConfig.customValidator(value);
}
}
setErrors(prev => ({
...prev,
[fieldName]: result.valid ? null : result.error
}));
return result;
} catch (error) {
const errorMessage = `验证失败: ${error.message}`;
setErrors(prev => ({
...prev,
[fieldName]: errorMessage
}));
return { valid: false, error: errorMessage };
} finally {
setValidating(false);
}
};
const validateForm = async (formData, validationSchema) => {
setValidating(true);
const results = {};
try {
// 并行执行所有验证
const validationPromises = Object.entries(validationSchema).map(
async ([fieldName, config]) => {
const value = formData[fieldName];
const result = await validateField(fieldName, value, config);
return [fieldName, result];
}
);
const validationResults = await Promise.all(validationPromises);
validationResults.forEach(([fieldName, result]) => {
results[fieldName] = result;
});
const isValid = Object.values(results).every(r => r.valid);
return {
isValid,
results,
errors: Object.fromEntries(
Object.entries(results)
.filter(([_, result]) => !result.valid)
.map(([fieldName, result]) => [fieldName, result.error])
)
};
} finally {
setValidating(false);
}
};
return {
errors,
validating,
validateField,
validateForm,
clearErrors: () => setErrors({})
};
}
实际应用示例:
// 代币转账表单
function TokenTransferForm({ tokenContract }) {
const { provider, userAddress, chainId } = useWeb3();
const [formData, setFormData] = useState({
recipient: '',
amount: '',
gasPrice: ''
});
const { errors, validating, validateForm } = useDAppFormValidation(
provider,
userAddress,
chainId
);
const validationSchema = {
recipient: {
type: 'address'
},
amount: {
type: 'tokenAmount',
decimals: 18,
maxAmount: userTokenBalance
},
gasPrice: {
type: 'tokenAmount',
decimals: 9, // Gwei
customValidator: async (value) => {
const minGasPrice = ethers.utils.parseUnits('1', 'gwei');
const maxGasPrice = ethers.utils.parseUnits('100', 'gwei');
const gasPrice = ethers.utils.parseUnits(value, 'gwei');
if (gasPrice.lt(minGasPrice)) {
return { valid: false, error: 'Gas价格过低' };
}
if (gasPrice.gt(maxGasPrice)) {
return { valid: false, error: 'Gas价格过高' };
}
return { valid: true };
}
}
};
const handleSubmit = async (e) => {
e.preventDefault();
// 执行表单验证
const validation = await validateForm(formData, validationSchema);
if (!validation.isValid) {
console.log('表单验证失败:', validation.errors);
return;
}
// 额外的Gas费用验证
const transaction = {
to: tokenContract.address,
data: tokenContract.interface.encodeFunctionData('transfer', [
formData.recipient,
ethers.utils.parseEther(formData.amount)
])
};
const gasValidation = await validator.validateGasFee(transaction);
if (!gasValidation.valid) {
console.log('Gas费用验证失败:', gasValidation.error);
return;
}
// 提交交易
try {
const tx = await tokenContract.transfer(
formData.recipient,
ethers.utils.parseEther(formData.amount)
);
console.log('交易提交成功:', tx.hash);
} catch (error) {
console.error('交易失败:', error);
}
};
return (
<form onSubmit={handleSubmit} className="token-transfer-form">
<div className="field-group">
<label>接收地址</label>
<input
type="text"
value={formData.recipient}
onChange={(e) => setFormData(prev => ({
...prev,
recipient: e.target.value
}))}
className={errors.recipient ? 'error' : ''}
/>
{errors.recipient && (
<div className="error-message">{errors.recipient}</div>
)}
</div>
<div className="field-group">
<label>转账数量</label>
<input
type="text"
value={formData.amount}
onChange={(e) => setFormData(prev => ({
...prev,
amount: e.target.value
}))}
className={errors.amount ? 'error' : ''}
/>
{errors.amount && (
<div className="error-message">{errors.amount}</div>
)}
</div>
<button type="submit" disabled={validating}>
{validating ? '验证中...' : '发送转账'}
</button>
</form>
);
}
最佳实践:
How to design responsive layout for DApp to adapt to different devices?
How to design responsive layout for DApp to adapt to different devices?
考察点:响应式设计基础。
答案:
DApp的响应式设计需要特别考虑钱包交互、复杂数据展示和移动端的操作便利性。与传统Web应用相比,DApp需要处理更多的状态信息、交易确认界面和数值显示,这对响应式设计提出了更高的要求。
DApp响应式设计特殊考虑:
响应式布局实现:
/* DApp响应式基础样式 */
.dapp-container {
max-width: 1200px;
margin: 0 auto;
padding: 0 16px;
/* 移动优先的设计 */
@media (min-width: 768px) {
padding: 0 24px;
}
@media (min-width: 1024px) {
padding: 0 32px;
}
}
/* 钱包连接按钮响应式 */
.wallet-connect {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
@media (min-width: 768px) {
flex-direction: row;
width: auto;
gap: 16px;
}
}
.wallet-option {
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
border-radius: 12px;
background: var(--card-background);
border: 1px solid var(--border-color);
cursor: pointer;
transition: all 0.2s ease;
/* 移动端增大触摸区域 */
min-height: 48px;
@media (min-width: 768px) {
min-height: auto;
padding: 12px 20px;
}
}
/* 数据表格响应式 */
.transaction-table {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
table {
width: 100%;
min-width: 600px;
border-collapse: collapse;
}
/* 移动端卡片式布局 */
@media (max-width: 767px) {
table, thead, tbody, th, td, tr {
display: block;
}
thead tr {
position: absolute;
top: -9999px;
left: -9999px;
}
tr {
border: 1px solid var(--border-color);
border-radius: 8px;
margin-bottom: 12px;
padding: 16px;
background: var(--card-background);
}
td {
border: none;
position: relative;
padding: 8px 0;
&:before {
content: attr(data-label) ": ";
font-weight: bold;
color: var(--text-secondary);
}
}
}
}
/* 表单响应式布局 */
.form-layout {
display: grid;
gap: 20px;
/* 移动端单列布局 */
grid-template-columns: 1fr;
/* 平板端双列布局 */
@media (min-width: 768px) {
grid-template-columns: 1fr 1fr;
gap: 24px;
}
/* 桌面端更复杂的网格 */
@media (min-width: 1024px) {
grid-template-columns: 2fr 1fr;
gap: 32px;
}
}
.form-full-width {
grid-column: 1 / -1;
}
/* 数值显示响应式 */
.balance-display {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
@media (min-width: 768px) {
flex-direction: row;
align-items: center;
gap: 12px;
}
}
.balance-amount {
font-size: 24px;
font-weight: bold;
@media (min-width: 768px) {
font-size: 32px;
}
}
.balance-usd {
font-size: 14px;
color: var(--text-secondary);
@media (min-width: 768px) {
font-size: 16px;
}
}
React响应式组件:
// 响应式Hook
function useResponsive() {
const [breakpoint, setBreakpoint] = useState('mobile');
useEffect(() => {
const getBreakpoint = () => {
const width = window.innerWidth;
if (width >= 1024) return 'desktop';
if (width >= 768) return 'tablet';
return 'mobile';
};
const handleResize = () => {
setBreakpoint(getBreakpoint());
};
handleResize();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return {
breakpoint,
isMobile: breakpoint === 'mobile',
isTablet: breakpoint === 'tablet',
isDesktop: breakpoint === 'desktop'
};
}
// 响应式钱包连接组件
function ResponsiveWalletConnect({ onConnect }) {
const { isMobile } = useResponsive();
const [showModal, setShowModal] = useState(false);
const walletOptions = [
{ name: 'MetaMask', icon: '🦊', id: 'metamask' },
{ name: 'WalletConnect', icon: '🔗', id: 'walletconnect' },
{ name: 'Coinbase', icon: '🔷', id: 'coinbase' }
];
if (isMobile) {
return (
<>
<button
className="wallet-connect-button mobile"
onClick={() => setShowModal(true)}
>
连接钱包
</button>
{showModal && (
<div className="wallet-modal mobile">
<div className="modal-content">
<div className="modal-header">
<h3>选择钱包</h3>
<button
className="close-button"
onClick={() => setShowModal(false)}
>
×
</button>
</div>
<div className="wallet-options">
{walletOptions.map(wallet => (
<button
key={wallet.id}
className="wallet-option mobile"
onClick={() => {
onConnect(wallet.id);
setShowModal(false);
}}
>
<span className="wallet-icon">{wallet.icon}</span>
<span className="wallet-name">{wallet.name}</span>
</button>
))}
</div>
</div>
</div>
)}
</>
);
}
return (
<div className="wallet-connect desktop">
{walletOptions.map(wallet => (
<button
key={wallet.id}
className="wallet-option"
onClick={() => onConnect(wallet.id)}
>
<span className="wallet-icon">{wallet.icon}</span>
<span className="wallet-name">{wallet.name}</span>
</button>
))}
</div>
);
}
// 响应式交易列表
function ResponsiveTransactionList({ transactions }) {
const { isMobile } = useResponsive();
if (isMobile) {
return (
<div className="transaction-list mobile">
{transactions.map(tx => (
<div key={tx.hash} className="transaction-card">
<div className="tx-header">
<span className="tx-type">{tx.type}</span>
<span className="tx-status">{tx.status}</span>
</div>
<div className="tx-details">
<div className="tx-amount">
{tx.amount} {tx.symbol}
</div>
<div className="tx-time">
{formatTime(tx.timestamp)}
</div>
<div className="tx-hash">
{shortenAddress(tx.hash)}
</div>
</div>
</div>
))}
</div>
);
}
return (
<div className="transaction-table desktop">
<table>
<thead>
<tr>
<th>类型</th>
<th>数量</th>
<th>状态</th>
<th>时间</th>
<th>交易哈希</th>
</tr>
</thead>
<tbody>
{transactions.map(tx => (
<tr key={tx.hash}>
<td>{tx.type}</td>
<td>{tx.amount} {tx.symbol}</td>
<td>{tx.status}</td>
<td>{formatTime(tx.timestamp)}</td>
<td>{shortenAddress(tx.hash)}</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
// 响应式数值输入
function ResponsiveNumberInput({
value,
onChange,
symbol,
maxValue,
placeholder = "0.0"
}) {
const { isMobile } = useResponsive();
return (
<div className={`number-input ${isMobile ? 'mobile' : 'desktop'}`}>
<input
type="text"
inputMode="decimal"
pattern="[0-9]*\.?[0-9]*"
value={value}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
className="amount-input"
/>
<div className="input-accessories">
{symbol && <span className="symbol">{symbol}</span>}
{maxValue && (
<button
type="button"
className="max-button"
onClick={() => onChange(maxValue)}
>
MAX
</button>
)}
</div>
</div>
);
}
移动端优化技巧:
// 移动端特殊处理
function MobileOptimizations() {
useEffect(() => {
// 防止双击缩放
document.addEventListener('touchstart', (e) => {
if (e.touches.length > 1) {
e.preventDefault();
}
});
// 防止长按选择文本
document.addEventListener('selectstart', (e) => {
if (e.target.classList.contains('no-select')) {
e.preventDefault();
}
});
// 优化输入框在iOS上的行为
const inputs = document.querySelectorAll('input[inputmode="decimal"]');
inputs.forEach(input => {
input.addEventListener('focus', () => {
// 滚动到视图中
setTimeout(() => {
input.scrollIntoView({ behavior: 'smooth', block: 'center' });
}, 300);
});
});
}, []);
return null;
}
// PWA支持
function usePWA() {
const [installPrompt, setInstallPrompt] = useState(null);
const [isInstallable, setIsInstallable] = useState(false);
useEffect(() => {
const handler = (e) => {
e.preventDefault();
setInstallPrompt(e);
setIsInstallable(true);
};
window.addEventListener('beforeinstallprompt', handler);
return () => window.removeEventListener('beforeinstallprompt', handler);
}, []);
const installApp = async () => {
if (!installPrompt) return;
const result = await installPrompt.prompt();
console.log('PWA安装结果:', result);
setInstallPrompt(null);
setIsInstallable(false);
};
return { isInstallable, installApp };
}
最佳实践:
How to design a scalable DApp state management architecture?
How to design a scalable DApp state management architecture?
考察点:架构设计能力。
答案:
可扩展的DApp状态管理架构需要处理多链环境、复杂的异步交互和大量的区块链数据。架构设计应该遵循模块化、可测试性和可维护性原则,同时考虑性能优化和用户体验。
架构设计原则:
核心架构实现:
// 状态管理架构基础
class DAppStateManager {
constructor() {
this.stores = new Map();
this.eventBus = new EventBus();
this.middleware = [];
this.subscriptions = new Map();
}
// 注册状态存储
registerStore(name, store) {
this.stores.set(name, store);
// 连接事件系统
store.setEventBus(this.eventBus);
return store;
}
// 获取状态存储
getStore(name) {
return this.stores.get(name);
}
// 注册中间件
use(middleware) {
this.middleware.push(middleware);
}
// 分发action
async dispatch(action) {
let result = action;
// 执行中间件
for (const middleware of this.middleware) {
result = await middleware(result, this);
}
// 分发到相关stores
const promises = [];
this.stores.forEach(store => {
if (store.canHandle(result)) {
promises.push(store.dispatch(result));
}
});
return Promise.all(promises);
}
}
// 基础Store类
class BaseStore {
constructor(initialState = {}) {
this.state = { ...initialState };
this.listeners = new Set();
this.eventBus = null;
}
setEventBus(eventBus) {
this.eventBus = eventBus;
}
// 更新状态
setState(partialState) {
const prevState = { ...this.state };
this.state = { ...this.state, ...partialState };
// 通知监听者
this.listeners.forEach(listener => {
listener(this.state, prevState);
});
// 发出状态变化事件
if (this.eventBus) {
this.eventBus.emit('stateChange', {
store: this.constructor.name,
state: this.state,
prevState
});
}
}
// 获取状态
getState() {
return { ...this.state };
}
// 订阅状态变化
subscribe(listener) {
this.listeners.add(listener);
return () => {
this.listeners.delete(listener);
};
}
// 判断是否能处理action
canHandle(action) {
return false; // 子类实现
}
// 分发action
async dispatch(action) {
// 子类实现
}
}
专用Store实现:
// Web3连接状态管理
class Web3Store extends BaseStore {
constructor() {
super({
isConnected: false,
address: null,
chainId: null,
provider: null,
signer: null,
network: null,
balance: null
});
}
canHandle(action) {
return action.type && action.type.startsWith('WEB3_');
}
async dispatch(action) {
switch (action.type) {
case 'WEB3_CONNECT':
return this.handleConnect(action.payload);
case 'WEB3_DISCONNECT':
return this.handleDisconnect();
case 'WEB3_ACCOUNT_CHANGED':
return this.handleAccountChanged(action.payload);
case 'WEB3_CHAIN_CHANGED':
return this.handleChainChanged(action.payload);
default:
return;
}
}
async handleConnect(provider) {
try {
const signer = provider.getSigner();
const address = await signer.getAddress();
const network = await provider.getNetwork();
const balance = await provider.getBalance(address);
this.setState({
isConnected: true,
address,
chainId: network.chainId,
provider,
signer,
network,
balance
});
// 监听账户和网络变化
this.setupEventListeners(provider);
} catch (error) {
console.error('Web3连接失败:', error);
throw error;
}
}
setupEventListeners(provider) {
if (provider.provider?.on) {
provider.provider.on('accountsChanged', (accounts) => {
this.dispatch({
type: 'WEB3_ACCOUNT_CHANGED',
payload: accounts[0] || null
});
});
provider.provider.on('chainChanged', (chainId) => {
this.dispatch({
type: 'WEB3_CHAIN_CHANGED',
payload: parseInt(chainId, 16)
});
});
}
}
}
// 合约状态管理
class ContractStore extends BaseStore {
constructor() {
super({
contracts: new Map(),
contractData: new Map(),
loading: new Set(),
errors: new Map()
});
}
canHandle(action) {
return action.type && action.type.startsWith('CONTRACT_');
}
async dispatch(action) {
switch (action.type) {
case 'CONTRACT_REGISTER':
return this.registerContract(action.payload);
case 'CONTRACT_CALL':
return this.callContract(action.payload);
case 'CONTRACT_SEND':
return this.sendTransaction(action.payload);
default:
return;
}
}
registerContract(contractConfig) {
const { name, address, abi, provider } = contractConfig;
const contract = new ethers.Contract(address, abi, provider);
const state = this.getState();
const contracts = new Map(state.contracts);
contracts.set(name, contract);
this.setState({ contracts });
}
async callContract({ contractName, method, params = [], cacheKey }) {
const contracts = this.getState().contracts;
const contract = contracts.get(contractName);
if (!contract) {
throw new Error(`Contract ${contractName} not found`);
}
// 检查缓存
if (cacheKey) {
const cachedData = this.getState().contractData.get(cacheKey);
if (cachedData && !this.isCacheExpired(cachedData)) {
return cachedData.data;
}
}
// 设置loading状态
const loading = new Set(this.getState().loading);
loading.add(cacheKey || `${contractName}.${method}`);
this.setState({ loading });
try {
const result = await contract[method](...params);
// 缓存结果
if (cacheKey) {
const contractData = new Map(this.getState().contractData);
contractData.set(cacheKey, {
data: result,
timestamp: Date.now(),
ttl: 300000 // 5分钟缓存
});
this.setState({ contractData });
}
return result;
} catch (error) {
const errors = new Map(this.getState().errors);
errors.set(cacheKey || `${contractName}.${method}`, error);
this.setState({ errors });
throw error;
} finally {
const loading = new Set(this.getState().loading);
loading.delete(cacheKey || `${contractName}.${method}`);
this.setState({ loading });
}
}
isCacheExpired(cacheItem) {
return Date.now() > (cacheItem.timestamp + cacheItem.ttl);
}
}
// 交易状态管理
class TransactionStore extends BaseStore {
constructor() {
super({
transactions: new Map(),
pending: new Set(),
confirmed: new Set(),
failed: new Set()
});
}
canHandle(action) {
return action.type && action.type.startsWith('TX_');
}
async dispatch(action) {
switch (action.type) {
case 'TX_SUBMIT':
return this.submitTransaction(action.payload);
case 'TX_UPDATE':
return this.updateTransaction(action.payload);
case 'TX_CONFIRM':
return this.confirmTransaction(action.payload);
default:
return;
}
}
submitTransaction(txData) {
const { hash, ...transaction } = txData;
const transactions = new Map(this.getState().transactions);
const pending = new Set(this.getState().pending);
transactions.set(hash, {
...transaction,
hash,
status: 'pending',
timestamp: Date.now()
});
pending.add(hash);
this.setState({ transactions, pending });
// 开始监听交易状态
this.watchTransaction(hash);
}
async watchTransaction(hash) {
const web3Store = this.eventBus.stateManager.getStore('web3');
const provider = web3Store.getState().provider;
if (!provider) return;
try {
const receipt = await provider.waitForTransaction(hash);
this.dispatch({
type: 'TX_CONFIRM',
payload: { hash, receipt }
});
} catch (error) {
this.dispatch({
type: 'TX_UPDATE',
payload: {
hash,
status: 'failed',
error: error.message
}
});
}
}
}
React集成Hook:
// 状态管理Hook
function useDAppState() {
const [stateManager] = useState(() => {
const manager = new DAppStateManager();
// 注册各种stores
manager.registerStore('web3', new Web3Store());
manager.registerStore('contract', new ContractStore());
manager.registerStore('transaction', new TransactionStore());
// 注册中间件
manager.use(async (action, stateManager) => {
console.log('Action dispatched:', action);
return action;
});
return manager;
});
return stateManager;
}
// 具体Store的Hook
function useWeb3Store() {
const stateManager = useDAppState();
const web3Store = stateManager.getStore('web3');
const [state, setState] = useState(web3Store.getState());
useEffect(() => {
const unsubscribe = web3Store.subscribe((newState) => {
setState(newState);
});
return unsubscribe;
}, [web3Store]);
const actions = {
connect: (provider) => stateManager.dispatch({
type: 'WEB3_CONNECT',
payload: provider
}),
disconnect: () => stateManager.dispatch({
type: 'WEB3_DISCONNECT'
})
};
return { state, actions };
}
// 使用示例
function DAppComponent() {
const { state: web3State, actions: web3Actions } = useWeb3Store();
const handleConnect = async () => {
try {
const provider = new ethers.providers.Web3Provider(window.ethereum);
await web3Actions.connect(provider);
} catch (error) {
console.error('连接失败:', error);
}
};
return (
<div>
{web3State.isConnected ? (
<div>已连接: {web3State.address}</div>
) : (
<button onClick={handleConnect}>连接钱包</button>
)}
</div>
);
}
架构优势:
How to implement offline functionality and data synchronization in DApp?
How to implement offline functionality and data synchronization in DApp?
考察点:离线功能设计。
答案:
DApp的离线功能设计需要在去中心化特性和用户体验之间找到平衡。由于区块链交互本质上需要网络连接,离线功能主要集中在数据缓存、交易队列和基础功能的本地实现上。
离线功能策略:
离线存储架构:
// 离线数据管理器
class OfflineDataManager {
constructor() {
this.dbName = 'dapp-offline-db';
this.version = 1;
this.db = null;
this.syncQueue = [];
this.isOnline = navigator.onLine;
this.setupEventListeners();
}
async initialize() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve();
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 创建对象存储
if (!db.objectStoreNames.contains('transactions')) {
const txStore = db.createObjectStore('transactions', {
keyPath: 'id',
autoIncrement: true
});
txStore.createIndex('hash', 'hash', { unique: false });
txStore.createIndex('status', 'status', { unique: false });
txStore.createIndex('timestamp', 'timestamp', { unique: false });
}
if (!db.objectStoreNames.contains('contractData')) {
const contractStore = db.createObjectStore('contractData', {
keyPath: 'key'
});
contractStore.createIndex('contractAddress', 'contractAddress', { unique: false });
contractStore.createIndex('timestamp', 'timestamp', { unique: false });
}
if (!db.objectStoreNames.contains('userPreferences')) {
db.createObjectStore('userPreferences', {
keyPath: 'key'
});
}
if (!db.objectStoreNames.contains('pendingActions')) {
const actionStore = db.createObjectStore('pendingActions', {
keyPath: 'id',
autoIncrement: true
});
actionStore.createIndex('type', 'type', { unique: false });
actionStore.createIndex('priority', 'priority', { unique: false });
}
};
});
}
setupEventListeners() {
window.addEventListener('online', () => {
this.isOnline = true;
this.onNetworkRestore();
});
window.addEventListener('offline', () => {
this.isOnline = false;
this.onNetworkLost();
});
}
// 缓存合约数据
async cacheContractData(key, data, contractAddress, ttl = 3600000) {
if (!this.db) return;
const transaction = this.db.transaction(['contractData'], 'readwrite');
const store = transaction.objectStore('contractData');
const cacheItem = {
key,
data,
contractAddress,
timestamp: Date.now(),
ttl,
expiresAt: Date.now() + ttl
};
await store.put(cacheItem);
}
// 获取缓存的合约数据
async getCachedContractData(key) {
if (!this.db) return null;
const transaction = this.db.transaction(['contractData'], 'readonly');
const store = transaction.objectStore('contractData');
return new Promise((resolve) => {
const request = store.get(key);
request.onsuccess = () => {
const result = request.result;
if (!result) {
resolve(null);
return;
}
// 检查是否过期
if (Date.now() > result.expiresAt) {
this.deleteCachedData('contractData', key);
resolve(null);
return;
}
resolve(result.data);
};
request.onerror = () => resolve(null);
});
}
// 添加离线交易到队列
async addPendingTransaction(transactionData) {
if (!this.db) return;
const transaction = this.db.transaction(['pendingActions'], 'readwrite');
const store = transaction.objectStore('pendingActions');
const pendingAction = {
type: 'TRANSACTION',
data: transactionData,
priority: transactionData.priority || 1,
timestamp: Date.now(),
retryCount: 0,
maxRetries: 3
};
const result = await store.add(pendingAction);
return result;
}
// 获取待处理的操作
async getPendingActions() {
if (!this.db) return [];
const transaction = this.db.transaction(['pendingActions'], 'readonly');
const store = transaction.objectStore('pendingActions');
const index = store.index('priority');
return new Promise((resolve) => {
const actions = [];
const request = index.openCursor(null, 'prev'); // 按优先级降序
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
actions.push(cursor.value);
cursor.continue();
} else {
resolve(actions);
}
};
request.onerror = () => resolve([]);
});
}
// 网络恢复时的处理
async onNetworkRestore() {
console.log('网络已恢复,开始同步数据...');
try {
// 处理待处理的操作
const pendingActions = await this.getPendingActions();
for (const action of pendingActions) {
try {
await this.processPendingAction(action);
await this.removePendingAction(action.id);
} catch (error) {
console.error('处理待处理操作失败:', error);
await this.incrementRetryCount(action.id);
}
}
// 触发数据同步事件
window.dispatchEvent(new CustomEvent('dapp-sync-start'));
} catch (error) {
console.error('数据同步失败:', error);
}
}
async processPendingAction(action) {
switch (action.type) {
case 'TRANSACTION':
return await this.submitPendingTransaction(action.data);
case 'CONTRACT_CALL':
return await this.executePendingContractCall(action.data);
default:
console.warn('未知的待处理操作类型:', action.type);
}
}
}
离线交易管理:
// 离线交易管理器
class OfflineTransactionManager {
constructor(offlineDataManager, web3Provider) {
this.offlineData = offlineDataManager;
this.provider = web3Provider;
this.queuedTransactions = new Map();
}
// 离线提交交易
async submitOfflineTransaction(transactionData) {
const {
to,
value,
data,
gasLimit,
gasPrice,
description,
priority = 1
} = transactionData;
// 生成本地交易ID
const localId = this.generateLocalTransactionId();
const offlineTx = {
localId,
to,
value: value?.toString(),
data,
gasLimit: gasLimit?.toString(),
gasPrice: gasPrice?.toString(),
description,
priority,
status: 'queued',
timestamp: Date.now(),
estimatedGasCost: gasLimit && gasPrice ?
gasLimit.mul(gasPrice).toString() : null
};
// 保存到离线存储
await this.offlineData.addPendingTransaction(offlineTx);
// 更新本地状态
this.queuedTransactions.set(localId, offlineTx);
// 触发UI更新事件
window.dispatchEvent(new CustomEvent('offline-transaction-queued', {
detail: offlineTx
}));
return localId;
}
// 网络恢复后提交交易
async submitPendingTransaction(txData) {
try {
// 重新获取最新的nonce和gas价格
const signer = this.provider.getSigner();
const nonce = await signer.getTransactionCount('pending');
const feeData = await this.provider.getFeeData();
const transaction = {
to: txData.to,
value: txData.value,
data: txData.data,
nonce,
gasLimit: txData.gasLimit,
maxFeePerGas: feeData.maxFeePerGas,
maxPriorityFeePerGas: feeData.maxPriorityFeePerGas
};
const tx = await signer.sendTransaction(transaction);
// 更新交易状态
this.updateTransactionStatus(txData.localId, 'submitted', tx.hash);
return tx;
} catch (error) {
this.updateTransactionStatus(txData.localId, 'failed', null, error.message);
throw error;
}
}
generateLocalTransactionId() {
return `local_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
updateTransactionStatus(localId, status, hash = null, error = null) {
const tx = this.queuedTransactions.get(localId);
if (tx) {
tx.status = status;
tx.hash = hash;
tx.error = error;
tx.updatedAt = Date.now();
window.dispatchEvent(new CustomEvent('transaction-status-updated', {
detail: tx
}));
}
}
}
React离线功能Hook:
// 离线状态管理Hook
function useOfflineCapability() {
const [isOnline, setIsOnline] = useState(navigator.onLine);
const [syncStatus, setSyncStatus] = useState('idle');
const [pendingActions, setPendingActions] = useState([]);
const [offlineDataManager] = useState(() => new OfflineDataManager());
useEffect(() => {
const handleOnline = () => {
setIsOnline(true);
setSyncStatus('syncing');
};
const handleOffline = () => {
setIsOnline(false);
setSyncStatus('offline');
};
const handleSyncStart = () => {
setSyncStatus('syncing');
};
const handleSyncComplete = () => {
setSyncStatus('idle');
};
window.addEventListener('online', handleOnline);
window.addEventListener('offline', handleOffline);
window.addEventListener('dapp-sync-start', handleSyncStart);
window.addEventListener('dapp-sync-complete', handleSyncComplete);
// 初始化离线数据管理器
offlineDataManager.initialize();
return () => {
window.removeEventListener('online', handleOnline);
window.removeEventListener('offline', handleOffline);
window.removeEventListener('dapp-sync-start', handleSyncStart);
window.removeEventListener('dapp-sync-complete', handleSyncComplete);
};
}, [offlineDataManager]);
// 获取缓存数据
const getCachedData = useCallback(async (key) => {
return await offlineDataManager.getCachedContractData(key);
}, [offlineDataManager]);
// 缓存数据
const cacheData = useCallback(async (key, data, contractAddress, ttl) => {
return await offlineDataManager.cacheContractData(key, data, contractAddress, ttl);
}, [offlineDataManager]);
return {
isOnline,
syncStatus,
pendingActions,
getCachedData,
cacheData,
offlineDataManager
};
}
// 离线感知的数据获取Hook
function useOfflineAwareData(key, fetchFn, cacheOptions = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [fromCache, setFromCache] = useState(false);
const { isOnline, getCachedData, cacheData } = useOfflineCapability();
useEffect(() => {
const loadData = async () => {
setLoading(true);
setError(null);
try {
// 先尝试获取缓存数据
const cachedData = await getCachedData(key);
if (cachedData) {
setData(cachedData);
setFromCache(true);
setLoading(false);
// 如果在线,仍然尝试获取最新数据
if (!isOnline) {
return;
}
}
// 如果在线,获取最新数据
if (isOnline && fetchFn) {
const freshData = await fetchFn();
// 缓存新数据
if (freshData && cacheOptions.cache !== false) {
await cacheData(
key,
freshData,
cacheOptions.contractAddress,
cacheOptions.ttl
);
}
setData(freshData);
setFromCache(false);
}
} catch (err) {
setError(err);
// 如果获取失败但有缓存数据,使用缓存
const cachedData = await getCachedData(key);
if (cachedData) {
setData(cachedData);
setFromCache(true);
}
} finally {
setLoading(false);
}
};
loadData();
}, [key, isOnline, fetchFn, getCachedData, cacheData, cacheOptions]);
return { data, loading, error, fromCache, isOnline };
}
// 使用示例
function TokenBalance({ tokenAddress, userAddress }) {
const { data: balance, loading, fromCache, isOnline } = useOfflineAwareData(
`balance_${tokenAddress}_${userAddress}`,
async () => {
const contract = new ethers.Contract(tokenAddress, tokenABI, provider);
return await contract.balanceOf(userAddress);
},
{
contractAddress: tokenAddress,
ttl: 300000, // 5分钟缓存
cache: true
}
);
return (
<div className="token-balance">
{loading ? (
<span>加载中...</span>
) : (
<div>
<span>余额: {ethers.utils.formatEther(balance || 0)}</span>
{fromCache && (
<span className="cache-indicator">
📱 {isOnline ? '缓存数据' : '离线数据'}
</span>
)}
</div>
)}
</div>
);
}
离线功能最佳实践:
How to optimize DApp performance? Reduce the number of blockchain calls.
How to optimize DApp performance? Reduce the number of blockchain calls.
考察点:性能优化策略。
答案:
DApp性能优化的核心在于减少不必要的区块链调用,因为每次调用都涉及网络延迟和计算成本。优化策略需要从数据缓存、批量操作、智能合约设计和前端架构等多个维度进行。
性能优化策略:
缓存策略实现:
// 智能缓存管理器
class SmartCacheManager {
constructor() {
this.cache = new Map();
this.pendingRequests = new Map();
this.cacheConfig = new Map();
}
// 配置缓存策略
setCacheConfig(key, config) {
this.cacheConfig.set(key, {
ttl: config.ttl || 300000, // 默认5分钟
refreshThreshold: config.refreshThreshold || 0.8, // 80%时间后刷新
staleWhileRevalidate: config.staleWhileRevalidate || true,
maxAge: config.maxAge || 3600000 // 最长1小时
});
}
// 获取缓存数据
async get(key, fetcher, options = {}) {
const config = this.cacheConfig.get(key) || {};
const cacheItem = this.cache.get(key);
const now = Date.now();
// 检查缓存是否有效
if (cacheItem && !this.isExpired(cacheItem, config)) {
// 检查是否需要后台刷新
if (this.shouldRefresh(cacheItem, config)) {
this.backgroundRefresh(key, fetcher, config);
}
return cacheItem.data;
}
// 检查是否有正在进行的请求
if (this.pendingRequests.has(key)) {
return await this.pendingRequests.get(key);
}
// 创建新的请求
const fetchPromise = this.executeFetch(key, fetcher, config);
this.pendingRequests.set(key, fetchPromise);
try {
const result = await fetchPromise;
return result;
} finally {
this.pendingRequests.delete(key);
}
}
async executeFetch(key, fetcher, config) {
try {
const data = await fetcher();
// 缓存结果
this.cache.set(key, {
data,
timestamp: Date.now(),
accessCount: 1,
lastAccess: Date.now()
});
return data;
} catch (error) {
// 如果有过期但可用的缓存,返回它
const staleCache = this.cache.get(key);
if (staleCache && config.staleWhileRevalidate) {
console.warn(`使用过期缓存数据: ${key}`, error);
return staleCache.data;
}
throw error;
}
}
backgroundRefresh(key, fetcher, config) {
// 后台异步刷新缓存
this.executeFetch(key, fetcher, config).catch(error => {
console.warn(`后台缓存刷新失败: ${key}`, error);
});
}
isExpired(cacheItem, config) {
const age = Date.now() - cacheItem.timestamp;
return age > config.ttl;
}
shouldRefresh(cacheItem, config) {
const age = Date.now() - cacheItem.timestamp;
return age > (config.ttl * config.refreshThreshold);
}
// 清理过期缓存
cleanup() {
const now = Date.now();
for (const [key, item] of this.cache.entries()) {
const config = this.cacheConfig.get(key) || {};
const age = now - item.timestamp;
if (age > config.maxAge) {
this.cache.delete(key);
}
}
}
}
批量调用实现:
// Multicall批量调用管理器
class MulticallManager {
constructor(multicallAddress, provider) {
this.multicallAddress = multicallAddress;
this.provider = provider;
this.pendingCalls = [];
this.batchTimeout = null;
this.batchSize = 100;
this.batchDelay = 50; // 50ms延迟批处理
}
// 添加调用到批处理队列
async addCall(target, functionName, params = [], options = {}) {
return new Promise((resolve, reject) => {
const call = {
target,
functionName,
params,
options,
resolve,
reject,
timestamp: Date.now()
};
this.pendingCalls.push(call);
this.scheduleBatch();
});
}
scheduleBatch() {
if (this.batchTimeout) return;
this.batchTimeout = setTimeout(() => {
this.executeBatch();
this.batchTimeout = null;
}, this.batchDelay);
}
async executeBatch() {
if (this.pendingCalls.length === 0) return;
const calls = this.pendingCalls.splice(0, this.batchSize);
try {
// 构建Multicall参数
const multicallData = calls.map(call => ({
target: call.target,
callData: this.encodeCallData(call)
}));
// 执行批量调用
const multicall = new ethers.Contract(
this.multicallAddress,
MULTICALL_ABI,
this.provider
);
const results = await multicall.callStatic.aggregate(multicallData);
// 解析结果并分发给对应的Promise
calls.forEach((call, index) => {
try {
const result = this.decodeResult(call, results.returnData[index]);
call.resolve(result);
} catch (error) {
call.reject(error);
}
});
} catch (error) {
// 批量调用失败,逐个执行
this.fallbackToIndividualCalls(calls);
}
// 如果还有待处理的调用,继续处理
if (this.pendingCalls.length > 0) {
this.scheduleBatch();
}
}
async fallbackToIndividualCalls(calls) {
for (const call of calls) {
try {
const contract = new ethers.Contract(
call.target,
call.options.abi || [],
this.provider
);
const result = await contract[call.functionName](...call.params);
call.resolve(result);
} catch (error) {
call.reject(error);
}
}
}
encodeCallData(call) {
const iface = new ethers.utils.Interface(call.options.abi || []);
return iface.encodeFunctionData(call.functionName, call.params);
}
decodeResult(call, returnData) {
const iface = new ethers.utils.Interface(call.options.abi || []);
return iface.decodeFunctionResult(call.functionName, returnData);
}
}
React性能优化Hook:
// 优化的合约数据Hook
function useOptimizedContractData(contractAddress, abi, method, params = [], options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const cacheManager = useMemo(() => new SmartCacheManager(), []);
const multicallManager = useMemo(() => new MulticallManager(
MULTICALL_ADDRESS,
provider
), []);
// 生成缓存键
const cacheKey = useMemo(() => {
const paramsStr = JSON.stringify(params);
return `${contractAddress}_${method}_${paramsStr}`;
}, [contractAddress, method, params]);
// 配置缓存策略
useEffect(() => {
cacheManager.setCacheConfig(cacheKey, {
ttl: options.ttl || 300000,
refreshThreshold: 0.8,
staleWhileRevalidate: true
});
}, [cacheKey, options.ttl]);
// 数据获取函数
const fetchData = useCallback(async () => {
if (options.useBatch) {
// 使用批量调用
return await multicallManager.addCall(
contractAddress,
method,
params,
{ abi }
);
} else {
// 单独调用
const contract = new ethers.Contract(contractAddress, abi, provider);
return await contract[method](...params);
}
}, [contractAddress, method, params, options.useBatch]);
// 加载数据
useEffect(() => {
let mounted = true;
const loadData = async () => {
setLoading(true);
setError(null);
try {
const result = await cacheManager.get(cacheKey, fetchData);
if (mounted) {
setData(result);
}
} catch (err) {
if (mounted) {
setError(err);
}
} finally {
if (mounted) {
setLoading(false);
}
}
};
loadData();
return () => {
mounted = false;
};
}, [cacheKey, fetchData]);
// 手动刷新
const refresh = useCallback(async () => {
setLoading(true);
try {
const result = await fetchData();
setData(result);
return result;
} catch (err) {
setError(err);
throw err;
} finally {
setLoading(false);
}
}, [fetchData]);
return { data, loading, error, refresh };
}
// 批量数据获取Hook
function useBatchContractData(requests, options = {}) {
const [results, setResults] = useState({});
const [loading, setLoading] = useState(true);
const [errors, setErrors] = useState({});
useEffect(() => {
let mounted = true;
const loadBatchData = async () => {
setLoading(true);
const batchPromises = requests.map(async (request) => {
const { key, contractAddress, abi, method, params } = request;
try {
const contract = new ethers.Contract(contractAddress, abi, provider);
const result = await contract[method](...(params || []));
return { key, result, error: null };
} catch (error) {
return { key, result: null, error };
}
});
try {
const batchResults = await Promise.allSettled(batchPromises);
if (mounted) {
const newResults = {};
const newErrors = {};
batchResults.forEach((settledResult, index) => {
const request = requests[index];
if (settledResult.status === 'fulfilled') {
const { key, result, error } = settledResult.value;
newResults[key] = result;
if (error) newErrors[key] = error;
} else {
newErrors[request.key] = settledResult.reason;
}
});
setResults(newResults);
setErrors(newErrors);
}
} finally {
if (mounted) {
setLoading(false);
}
}
};
loadBatchData();
return () => {
mounted = false;
};
}, [JSON.stringify(requests)]);
return { results, loading, errors };
}
预加载策略:
// 数据预加载管理器
class DataPreloader {
constructor(cacheManager) {
this.cacheManager = cacheManager;
this.preloadQueue = [];
this.isPreloading = false;
}
// 添加预加载任务
addPreloadTask(key, fetcher, priority = 1) {
this.preloadQueue.push({
key,
fetcher,
priority,
timestamp: Date.now()
});
// 按优先级排序
this.preloadQueue.sort((a, b) => b.priority - a.priority);
if (!this.isPreloading) {
this.startPreloading();
}
}
async startPreloading() {
this.isPreloading = true;
while (this.preloadQueue.length > 0) {
const task = this.preloadQueue.shift();
try {
// 检查缓存中是否已存在
const existing = await this.cacheManager.get(task.key, null);
if (!existing) {
// 预加载数据
await this.cacheManager.get(task.key, task.fetcher);
}
} catch (error) {
console.warn(`预加载失败: ${task.key}`, error);
}
// 避免阻塞主线程
await new Promise(resolve => setTimeout(resolve, 10));
}
this.isPreloading = false;
}
// 预加载用户可能需要的数据
preloadUserData(userAddress, tokenAddresses = []) {
// 预加载ETH余额
this.addPreloadTask(
`eth_balance_${userAddress}`,
async () => {
return await provider.getBalance(userAddress);
},
10
);
// 预加载代币余额
tokenAddresses.forEach(tokenAddress => {
this.addPreloadTask(
`token_balance_${tokenAddress}_${userAddress}`,
async () => {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
return await contract.balanceOf(userAddress);
},
8
);
});
// 预加载交易历史
this.addPreloadTask(
`transaction_history_${userAddress}`,
async () => {
// 这里可以调用区块链浏览器API或子图
return await fetchTransactionHistory(userAddress);
},
5
);
}
}
性能监控和分析:
// 性能监控工具
class DAppPerformanceMonitor {
constructor() {
this.metrics = {
blockchainCalls: 0,
cacheHits: 0,
cacheMisses: 0,
averageResponseTime: 0,
slowQueries: []
};
}
trackBlockchainCall(method, startTime, endTime, fromCache = false) {
const duration = endTime - startTime;
this.metrics.blockchainCalls++;
if (fromCache) {
this.metrics.cacheHits++;
} else {
this.metrics.cacheMisses++;
}
// 更新平均响应时间
this.updateAverageResponseTime(duration);
// 记录慢查询
if (duration > 5000) { // 5秒以上的查询
this.metrics.slowQueries.push({
method,
duration,
timestamp: Date.now()
});
}
// 定期清理旧的慢查询记录
if (this.metrics.slowQueries.length > 100) {
this.metrics.slowQueries = this.metrics.slowQueries.slice(-50);
}
}
updateAverageResponseTime(newDuration) {
const totalCalls = this.metrics.blockchainCalls;
const currentAvg = this.metrics.averageResponseTime;
this.metrics.averageResponseTime =
(currentAvg * (totalCalls - 1) + newDuration) / totalCalls;
}
getPerformanceReport() {
const cacheHitRate = this.metrics.cacheHits /
(this.metrics.cacheHits + this.metrics.cacheMisses);
return {
...this.metrics,
cacheHitRate: (cacheHitRate * 100).toFixed(2) + '%',
averageResponseTime: this.metrics.averageResponseTime.toFixed(0) + 'ms'
};
}
}
最佳实践总结:
What is optimistic update? How to implement optimistic UI updates in DApp?
What is optimistic update? How to implement optimistic UI updates in DApp?
考察点:用户体验优化。
答案:
乐观更新是一种用户体验优化策略,即在等待服务器响应之前就先更新UI界面,假设操作会成功。在DApp中,由于区块链交易确认需要时间,乐观更新能显著改善用户体验,让界面响应更加流畅。
乐观更新应用场景:
乐观更新架构实现:
// 乐观更新管理器
class OptimisticUpdateManager {
constructor() {
this.optimisticStates = new Map();
this.pendingOperations = new Map();
this.rollbackHistory = new Map();
this.eventBus = new EventTarget();
}
// 执行乐观更新
async executeOptimisticUpdate(operationId, optimisticState, actualOperation, rollbackFn) {
try {
// 保存当前状态用于回滚
this.rollbackHistory.set(operationId, {
rollbackFn,
timestamp: Date.now()
});
// 立即应用乐观状态
this.applyOptimisticState(operationId, optimisticState);
// 执行实际操作
const result = await actualOperation();
// 操作成功,清理乐观状态
this.confirmOptimisticUpdate(operationId, result);
return result;
} catch (error) {
// 操作失败,回滚状态
this.rollbackOptimisticUpdate(operationId, error);
throw error;
}
}
applyOptimisticState(operationId, optimisticState) {
this.optimisticStates.set(operationId, {
state: optimisticState,
timestamp: Date.now(),
status: 'pending'
});
this.notifyStateChange(operationId, optimisticState, 'optimistic');
}
confirmOptimisticUpdate(operationId, actualResult) {
const optimisticData = this.optimisticStates.get(operationId);
if (optimisticData) {
optimisticData.status = 'confirmed';
optimisticData.actualResult = actualResult;
this.notifyStateChange(operationId, actualResult, 'confirmed');
// 清理资源
setTimeout(() => {
this.cleanup(operationId);
}, 5000);
}
}
rollbackOptimisticUpdate(operationId, error) {
const rollbackData = this.rollbackHistory.get(operationId);
if (rollbackData) {
// 执行回滚
rollbackData.rollbackFn();
this.notifyStateChange(operationId, null, 'rollback', error);
}
this.cleanup(operationId);
}
notifyStateChange(operationId, state, type, error = null) {
this.eventBus.dispatchEvent(new CustomEvent('optimisticStateChange', {
detail: {
operationId,
state,
type,
error,
timestamp: Date.now()
}
}));
}
cleanup(operationId) {
this.optimisticStates.delete(operationId);
this.rollbackHistory.delete(operationId);
this.pendingOperations.delete(operationId);
}
// 获取当前的乐观状态
getOptimisticState(operationId) {
return this.optimisticStates.get(operationId);
}
// 检查是否有待处理的乐观更新
hasPendingUpdates(filter = null) {
if (!filter) {
return this.optimisticStates.size > 0;
}
for (const [id, data] of this.optimisticStates.entries()) {
if (filter(id, data)) {
return true;
}
}
return false;
}
}
代币转账乐观更新实现:
// 代币转账乐观更新
class TokenTransferOptimistic {
constructor(tokenContract, optimisticManager) {
this.contract = tokenContract;
this.optimistic = optimisticManager;
this.balanceCache = new Map();
}
async optimisticTransfer(to, amount, userAddress) {
const operationId = `transfer_${Date.now()}_${Math.random()}`;
// 获取当前余额
const currentBalance = await this.getCurrentBalance(userAddress);
const currentRecipientBalance = await this.getCurrentBalance(to);
// 计算乐观状态
const optimisticState = {
type: 'TOKEN_TRANSFER',
sender: userAddress,
recipient: to,
amount,
senderNewBalance: currentBalance.sub(amount),
recipientNewBalance: currentRecipientBalance.add(amount),
timestamp: Date.now()
};
// 乐观更新函数
const applyOptimistic = () => {
this.balanceCache.set(userAddress, optimisticState.senderNewBalance);
this.balanceCache.set(to, optimisticState.recipientNewBalance);
};
// 回滚函数
const rollback = () => {
this.balanceCache.set(userAddress, currentBalance);
this.balanceCache.set(to, currentRecipientBalance);
};
// 实际转账操作
const actualTransfer = async () => {
const tx = await this.contract.transfer(to, amount);
// 等待交易确认
const receipt = await tx.wait();
// 获取实际的最新余额
const [actualSenderBalance, actualRecipientBalance] = await Promise.all([
this.contract.balanceOf(userAddress),
this.contract.balanceOf(to)
]);
return {
txHash: tx.hash,
receipt,
senderBalance: actualSenderBalance,
recipientBalance: actualRecipientBalance
};
};
// 立即应用乐观更新
applyOptimistic();
// 执行乐观更新
return await this.optimistic.executeOptimisticUpdate(
operationId,
optimisticState,
actualTransfer,
rollback
);
}
async getCurrentBalance(address) {
// 优先使用缓存的乐观余额
if (this.balanceCache.has(address)) {
return this.balanceCache.get(address);
}
// 从合约获取实际余额
const balance = await this.contract.balanceOf(address);
this.balanceCache.set(address, balance);
return balance;
}
// 清除地址的缓存余额
clearBalanceCache(address) {
this.balanceCache.delete(address);
}
}
React乐观更新Hook:
// 乐观更新Hook
function useOptimisticUpdate() {
const [optimisticManager] = useState(() => new OptimisticUpdateManager());
const [optimisticStates, setOptimisticStates] = useState(new Map());
useEffect(() => {
const handleStateChange = (event) => {
const { operationId, state, type, error } = event.detail;
setOptimisticStates(prev => {
const newStates = new Map(prev);
if (type === 'rollback') {
newStates.delete(operationId);
} else {
newStates.set(operationId, { state, type, error });
}
return newStates;
});
};
optimisticManager.eventBus.addEventListener('optimisticStateChange', handleStateChange);
return () => {
optimisticManager.eventBus.removeEventListener('optimisticStateChange', handleStateChange);
};
}, [optimisticManager]);
const executeOptimistic = useCallback(async (
optimisticState,
actualOperation,
rollbackFn,
operationId = null
) => {
const id = operationId || `op_${Date.now()}_${Math.random()}`;
return await optimisticManager.executeOptimisticUpdate(
id,
optimisticState,
actualOperation,
rollbackFn
);
}, [optimisticManager]);
return {
optimisticStates,
executeOptimistic,
optimisticManager
};
}
// 代币余额乐观更新Hook
function useOptimisticTokenBalance(tokenContract, address) {
const [actualBalance, setActualBalance] = useState(null);
const [displayBalance, setDisplayBalance] = useState(null);
const { optimisticStates } = useOptimisticUpdate();
const [loading, setLoading] = useState(true);
// 获取实际余额
useEffect(() => {
if (!tokenContract || !address) return;
const fetchBalance = async () => {
try {
const balance = await tokenContract.balanceOf(address);
setActualBalance(balance);
setDisplayBalance(balance);
} catch (error) {
console.error('获取余额失败:', error);
} finally {
setLoading(false);
}
};
fetchBalance();
// 监听代币转账事件
const handleTransfer = (from, to, amount) => {
if (from === address || to === address) {
fetchBalance();
}
};
tokenContract.on('Transfer', handleTransfer);
return () => {
tokenContract.off('Transfer', handleTransfer);
};
}, [tokenContract, address]);
// 应用乐观更新
useEffect(() => {
let calculatedBalance = actualBalance;
// 应用所有相关的乐观更新
for (const [operationId, optimisticData] of optimisticStates.entries()) {
const { state, type } = optimisticData;
if (type === 'optimistic' && state.type === 'TOKEN_TRANSFER') {
if (state.sender === address) {
calculatedBalance = state.senderNewBalance;
} else if (state.recipient === address) {
calculatedBalance = state.recipientNewBalance;
}
}
}
setDisplayBalance(calculatedBalance);
}, [actualBalance, optimisticStates, address]);
return {
balance: displayBalance,
actualBalance,
loading,
isPending: optimisticStates.size > 0
};
}
// 乐观转账组件示例
function OptimisticTransferForm({ tokenContract, userAddress }) {
const [recipient, setRecipient] = useState('');
const [amount, setAmount] = useState('');
const [transferring, setTransferring] = useState(false);
const { executeOptimistic } = useOptimisticUpdate();
const { balance, isPending } = useOptimisticTokenBalance(tokenContract, userAddress);
const handleTransfer = async () => {
if (!recipient || !amount) return;
setTransferring(true);
try {
const transferAmount = ethers.utils.parseEther(amount);
const currentBalance = await tokenContract.balanceOf(userAddress);
const recipientCurrentBalance = await tokenContract.balanceOf(recipient);
await executeOptimistic(
// 乐观状态
{
type: 'TOKEN_TRANSFER',
sender: userAddress,
recipient,
amount: transferAmount,
senderNewBalance: currentBalance.sub(transferAmount),
recipientNewBalance: recipientCurrentBalance.add(transferAmount)
},
// 实际操作
async () => {
const tx = await tokenContract.transfer(recipient, transferAmount);
return await tx.wait();
},
// 回滚函数
() => {
console.log('转账失败,已回滚状态');
}
);
// 清空表单
setRecipient('');
setAmount('');
} catch (error) {
console.error('转账失败:', error);
} finally {
setTransferring(false);
}
};
return (
<div className="transfer-form">
<div className="balance-display">
<span>余额: {ethers.utils.formatEther(balance || 0)} TOKEN</span>
{isPending && <span className="pending-indicator">⏳ 交易处理中</span>}
</div>
<input
type="text"
placeholder="接收地址"
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
/>
<input
type="text"
placeholder="转账数量"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
<button
onClick={handleTransfer}
disabled={transferring || !recipient || !amount}
>
{transferring ? '转账中...' : '转账'}
</button>
</div>
);
}
乐观更新最佳实践:
How to handle complex asynchronous transaction workflows in DApp?
How to handle complex asynchronous transaction workflows in DApp?
考察点:复杂流程设计。
答案:
复杂的异步交易流程涉及多个步骤的交易序列,如DeFi协议交互、多签钱包操作、跨链交易等。需要设计状态机来管理流程状态,实现错误恢复和用户友好的进度展示。
流程管理架构:
// 交易工作流管理器
class TransactionWorkflowManager {
constructor() {
this.workflows = new Map();
this.stepDefinitions = new Map();
this.eventBus = new EventTarget();
}
// 定义工作流步骤
defineStep(stepId, stepConfig) {
this.stepDefinitions.set(stepId, {
id: stepId,
execute: stepConfig.execute,
validate: stepConfig.validate,
rollback: stepConfig.rollback,
retry: stepConfig.retry || 3,
timeout: stepConfig.timeout || 300000,
dependencies: stepConfig.dependencies || [],
description: stepConfig.description
});
}
// 创建工作流
createWorkflow(workflowId, steps, options = {}) {
const workflow = {
id: workflowId,
steps: steps.map(stepId => ({
id: stepId,
status: 'pending',
result: null,
error: null,
attempts: 0,
startTime: null,
endTime: null
})),
status: 'initialized',
currentStepIndex: 0,
options,
metadata: {},
startTime: Date.now(),
endTime: null
};
this.workflows.set(workflowId, workflow);
return workflow;
}
// 执行工作流
async executeWorkflow(workflowId, context = {}) {
const workflow = this.workflows.get(workflowId);
if (!workflow) {
throw new Error(`Workflow ${workflowId} not found`);
}
workflow.status = 'running';
this.emitEvent('workflowStarted', { workflowId, workflow });
try {
for (let i = workflow.currentStepIndex; i < workflow.steps.length; i++) {
const step = workflow.steps[i];
workflow.currentStepIndex = i;
await this.executeStep(workflowId, step, context);
// 检查是否需要暂停
if (workflow.status === 'paused') {
return workflow;
}
}
workflow.status = 'completed';
workflow.endTime = Date.now();
this.emitEvent('workflowCompleted', { workflowId, workflow });
return workflow;
} catch (error) {
workflow.status = 'failed';
workflow.endTime = Date.now();
workflow.error = error;
this.emitEvent('workflowFailed', { workflowId, workflow, error });
throw error;
}
}
async executeStep(workflowId, step, context) {
const stepDef = this.stepDefinitions.get(step.id);
if (!stepDef) {
throw new Error(`Step definition ${step.id} not found`);
}
step.status = 'running';
step.startTime = Date.now();
step.attempts++;
this.emitEvent('stepStarted', { workflowId, step });
try {
// 验证依赖
await this.validateDependencies(workflowId, stepDef.dependencies, context);
// 执行步骤
const result = await Promise.race([
stepDef.execute(context, step),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('Step timeout')), stepDef.timeout);
})
]);
step.status = 'completed';
step.result = result;
step.endTime = Date.now();
this.emitEvent('stepCompleted', { workflowId, step, result });
return result;
} catch (error) {
step.status = 'failed';
step.error = error;
step.endTime = Date.now();
this.emitEvent('stepFailed', { workflowId, step, error });
// 重试逻辑
if (step.attempts < stepDef.retry) {
console.log(`重试步骤 ${step.id}, 尝试次数: ${step.attempts}/${stepDef.retry}`);
// 指数退避延迟
const delay = Math.pow(2, step.attempts - 1) * 1000;
await new Promise(resolve => setTimeout(resolve, delay));
return await this.executeStep(workflowId, step, context);
}
throw error;
}
}
async validateDependencies(workflowId, dependencies, context) {
const workflow = this.workflows.get(workflowId);
for (const depId of dependencies) {
const depStep = workflow.steps.find(s => s.id === depId);
if (!depStep || depStep.status !== 'completed') {
throw new Error(`Dependency ${depId} not satisfied`);
}
}
}
// 暂停工作流
pauseWorkflow(workflowId) {
const workflow = this.workflows.get(workflowId);
if (workflow && workflow.status === 'running') {
workflow.status = 'paused';
this.emitEvent('workflowPaused', { workflowId, workflow });
}
}
// 恢复工作流
async resumeWorkflow(workflowId, context = {}) {
const workflow = this.workflows.get(workflowId);
if (workflow && workflow.status === 'paused') {
workflow.status = 'running';
this.emitEvent('workflowResumed', { workflowId, workflow });
return await this.executeWorkflow(workflowId, context);
}
}
emitEvent(eventType, data) {
this.eventBus.dispatchEvent(new CustomEvent(eventType, { detail: data }));
}
}
DeFi交易流程示例:
// DeFi Swap交易流程
class DeFiSwapWorkflow {
constructor(workflowManager, swapRouter, tokenA, tokenB) {
this.workflowManager = workflowManager;
this.swapRouter = swapRouter;
this.tokenA = tokenA;
this.tokenB = tokenB;
this.setupSteps();
}
setupSteps() {
// 步骤1: 检查余额
this.workflowManager.defineStep('checkBalance', {
description: '检查代币余额',
execute: async (context) => {
const balance = await this.tokenA.balanceOf(context.userAddress);
if (balance.lt(context.amountIn)) {
throw new Error('余额不足');
}
return { balance, sufficient: true };
},
validate: (context) => context.userAddress && context.amountIn
});
// 步骤2: 检查授权
this.workflowManager.defineStep('checkAllowance', {
description: '检查代币授权',
dependencies: ['checkBalance'],
execute: async (context) => {
const allowance = await this.tokenA.allowance(
context.userAddress,
this.swapRouter.address
);
return {
allowance,
needsApproval: allowance.lt(context.amountIn)
};
}
});
// 步骤3: 授权代币
this.workflowManager.defineStep('approveToken', {
description: '授权代币',
dependencies: ['checkAllowance'],
execute: async (context, step) => {
const prevStep = this.getStepResult(context.workflowId, 'checkAllowance');
if (!prevStep.needsApproval) {
return { skipped: true, reason: '无需授权' };
}
const tx = await this.tokenA.approve(
this.swapRouter.address,
ethers.constants.MaxUint256
);
const receipt = await tx.wait();
return { txHash: tx.hash, receipt };
},
timeout: 300000
});
// 步骤4: 获取交易路径和价格
this.workflowManager.defineStep('getQuote', {
description: '获取交易报价',
dependencies: ['checkBalance'],
execute: async (context) => {
const path = [this.tokenA.address, this.tokenB.address];
const amounts = await this.swapRouter.getAmountsOut(
context.amountIn,
path
);
return {
path,
amountOut: amounts[amounts.length - 1],
priceImpact: this.calculatePriceImpact(context.amountIn, amounts[1])
};
}
});
// 步骤5: 执行交换
this.workflowManager.defineStep('executeSwap', {
description: '执行代币交换',
dependencies: ['approveToken', 'getQuote'],
execute: async (context) => {
const quote = this.getStepResult(context.workflowId, 'getQuote');
const minAmountOut = quote.amountOut.mul(95).div(100); // 5% slippage
const tx = await this.swapRouter.swapExactTokensForTokens(
context.amountIn,
minAmountOut,
quote.path,
context.userAddress,
Math.floor(Date.now() / 1000) + 1800 // 30分钟超时
);
const receipt = await tx.wait();
return { txHash: tx.hash, receipt, actualAmountOut: null };
},
timeout: 600000
});
// 步骤6: 验证结果
this.workflowManager.defineStep('verifyResult', {
description: '验证交易结果',
dependencies: ['executeSwap'],
execute: async (context) => {
const swapResult = this.getStepResult(context.workflowId, 'executeSwap');
const balanceAfter = await this.tokenB.balanceOf(context.userAddress);
return {
balanceAfter,
transactionHash: swapResult.txHash,
success: true
};
}
});
}
// 启动交换流程
async startSwap(userAddress, amountIn) {
const workflowId = `swap_${Date.now()}_${Math.random()}`;
const workflow = this.workflowManager.createWorkflow(workflowId, [
'checkBalance',
'checkAllowance',
'approveToken',
'getQuote',
'executeSwap',
'verifyResult'
]);
const context = {
workflowId,
userAddress,
amountIn,
tokenA: this.tokenA.address,
tokenB: this.tokenB.address
};
return await this.workflowManager.executeWorkflow(workflowId, context);
}
getStepResult(workflowId, stepId) {
const workflow = this.workflowManager.workflows.get(workflowId);
const step = workflow.steps.find(s => s.id === stepId);
return step ? step.result : null;
}
calculatePriceImpact(amountIn, amountOut) {
// 简化的价格影响计算
// 实际应用中需要更复杂的计算逻辑
return 0.01; // 1%
}
}
React工作流组件:
// 工作流进度组件
function WorkflowProgress({ workflowId, workflowManager }) {
const [workflow, setWorkflow] = useState(null);
const [currentStep, setCurrentStep] = useState(null);
useEffect(() => {
const workflow = workflowManager.workflows.get(workflowId);
setWorkflow(workflow);
const handleWorkflowEvent = (event) => {
const { workflowId: eventWorkflowId } = event.detail;
if (eventWorkflowId === workflowId) {
const updatedWorkflow = workflowManager.workflows.get(workflowId);
setWorkflow({ ...updatedWorkflow });
if (event.type === 'stepStarted' || event.type === 'stepCompleted') {
setCurrentStep(event.detail.step);
}
}
};
const events = [
'workflowStarted', 'workflowCompleted', 'workflowFailed',
'stepStarted', 'stepCompleted', 'stepFailed'
];
events.forEach(eventType => {
workflowManager.eventBus.addEventListener(eventType, handleWorkflowEvent);
});
return () => {
events.forEach(eventType => {
workflowManager.eventBus.removeEventListener(eventType, handleWorkflowEvent);
});
};
}, [workflowId, workflowManager]);
if (!workflow) return <div>工作流不存在</div>;
const completedSteps = workflow.steps.filter(s => s.status === 'completed').length;
const totalSteps = workflow.steps.length;
const progress = (completedSteps / totalSteps) * 100;
return (
<div className="workflow-progress">
<div className="progress-header">
<h3>交易进度</h3>
<span className="progress-text">
{completedSteps}/{totalSteps} 步骤完成
</span>
</div>
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
<div className="steps-list">
{workflow.steps.map((step, index) => {
const stepDef = workflowManager.stepDefinitions.get(step.id);
return (
<div
key={step.id}
className={`step-item ${step.status}`}
>
<div className="step-icon">
{step.status === 'completed' && '✅'}
{step.status === 'running' && '⏳'}
{step.status === 'failed' && '❌'}
{step.status === 'pending' && '⏸️'}
</div>
<div className="step-content">
<div className="step-title">{stepDef?.description || step.id}</div>
{step.status === 'running' && (
<div className="step-status">执行中...</div>
)}
{step.status === 'failed' && (
<div className="step-error">
失败: {step.error?.message}
{step.attempts < 3 && <span> (将重试)</span>}
</div>
)}
{step.status === 'completed' && step.result && (
<div className="step-result">
{step.result.txHash && (
<a
href={`https://etherscan.io/tx/${step.result.txHash}`}
target="_blank"
rel="noopener noreferrer"
>
查看交易
</a>
)}
</div>
)}
</div>
</div>
);
})}
</div>
{workflow.status === 'failed' && (
<div className="workflow-actions">
<button onClick={() => workflowManager.resumeWorkflow(workflowId)}>
重试失败步骤
</button>
</div>
)}
</div>
);
}
// 使用示例
function SwapInterface() {
const [workflowId, setWorkflowId] = useState(null);
const [swapAmount, setSwapAmount] = useState('');
const workflowManager = useMemo(() => new TransactionWorkflowManager(), []);
const swapWorkflow = useMemo(() =>
new DeFiSwapWorkflow(workflowManager, swapRouter, tokenA, tokenB),
[workflowManager]
);
const handleSwap = async () => {
try {
const workflow = await swapWorkflow.startSwap(
userAddress,
ethers.utils.parseEther(swapAmount)
);
setWorkflowId(workflow.id);
} catch (error) {
console.error('交换失败:', error);
}
};
return (
<div className="swap-interface">
<div className="swap-form">
<input
type="text"
value={swapAmount}
onChange={(e) => setSwapAmount(e.target.value)}
placeholder="交换数量"
/>
<button onClick={handleSwap}>开始交换</button>
</div>
{workflowId && (
<WorkflowProgress
workflowId={workflowId}
workflowManager={workflowManager}
/>
)}
</div>
);
}
最佳实践:
How to design error recovery mechanisms for DApp? Handle transaction failure scenarios.
How to design error recovery mechanisms for DApp? Handle transaction failure scenarios.
考察点:错误恢复策略。
答案:
DApp错误恢复机制需要处理网络故障、交易失败、Gas费不足等多种异常情况。设计应包括自动重试、状态回滚、用户引导和数据恢复等策略。
错误恢复策略:
实现示例:
class ErrorRecoveryManager {
constructor() {
this.retryStrategies = new Map();
this.fallbackActions = new Map();
this.recoveryHistory = [];
}
async executeWithRecovery(operation, options = {}) {
const { maxRetries = 3, backoffBase = 1000, fallback = null } = options;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
const errorType = this.classifyError(error);
if (attempt === maxRetries || !this.isRecoverable(errorType)) {
if (fallback) {
return await fallback(error);
}
throw error;
}
await this.delay(backoffBase * Math.pow(2, attempt - 1));
}
}
}
classifyError(error) {
if (error.code === 4001) return 'USER_REJECTED';
if (error.message.includes('insufficient funds')) return 'INSUFFICIENT_FUNDS';
if (error.message.includes('network')) return 'NETWORK_ERROR';
return 'UNKNOWN_ERROR';
}
isRecoverable(errorType) {
return ['NETWORK_ERROR', 'RATE_LIMIT'].includes(errorType);
}
}
What is event-driven architecture? How to apply event patterns in DApp?
What is event-driven architecture? How to apply event patterns in DApp?
考察点:架构模式应用。
How to implement real-time data updates in DApp? Handle on-chain state changes.
How to implement real-time data updates in DApp? Handle on-chain state changes.
考察点:实时数据同步。
答案:
DApp中的实时数据更新主要通过事件监听、轮询和WebSocket连接实现。需要处理区块链事件、合约状态变化和网络延迟,确保数据的实时性和准确性。
实时更新策略:
实时数据管理实现:
// 实时数据管理器
class RealTimeDataManager {
constructor(provider) {
this.provider = provider;
this.subscriptions = new Map();
this.eventFilters = new Map();
this.pollingIntervals = new Map();
this.eventBus = new EventTarget();
this.isConnected = false;
}
// 订阅合约事件
subscribeToContractEvents(contractAddress, abi, eventName, filter = {}) {
const contract = new ethers.Contract(contractAddress, abi, this.provider);
const subscriptionId = `${contractAddress}_${eventName}_${JSON.stringify(filter)}`;
// 创建事件过滤器
const eventFilter = contract.filters[eventName](...Object.values(filter));
// 监听事件
contract.on(eventFilter, (...args) => {
const event = args[args.length - 1]; // 最后一个参数是事件对象
this.handleContractEvent({
subscriptionId,
contractAddress,
eventName,
args: args.slice(0, -1),
event,
blockNumber: event.blockNumber,
transactionHash: event.transactionHash
});
});
this.subscriptions.set(subscriptionId, {
type: 'CONTRACT_EVENT',
contract,
eventFilter,
eventName,
filter
});
return subscriptionId;
}
// 订阅区块事件
subscribeToBlocks(callback) {
const subscriptionId = 'NEW_BLOCKS';
this.provider.on('block', async (blockNumber) => {
try {
const block = await this.provider.getBlock(blockNumber);
this.eventBus.dispatchEvent(new CustomEvent('newBlock', {
detail: { block, blockNumber }
}));
if (callback) callback(block);
} catch (error) {
console.error('获取区块信息失败:', error);
}
});
this.subscriptions.set(subscriptionId, {
type: 'BLOCK_EVENT',
callback
});
return subscriptionId;
}
// 设置数据轮询
setupPolling(key, dataFetcher, interval = 30000) {
// 清除现有的轮询
if (this.pollingIntervals.has(key)) {
clearInterval(this.pollingIntervals.get(key));
}
const pollData = async () => {
try {
const data = await dataFetcher();
this.eventBus.dispatchEvent(new CustomEvent('dataPolled', {
detail: { key, data, timestamp: Date.now() }
}));
} catch (error) {
console.error(`轮询数据失败 ${key}:`, error);
}
};
// 立即执行一次
pollData();
// 设置定时轮询
const intervalId = setInterval(pollData, interval);
this.pollingIntervals.set(key, intervalId);
return key;
}
// 处理合约事件
handleContractEvent(eventData) {
// 事件去重处理
const eventKey = `${eventData.transactionHash}_${eventData.event.logIndex}`;
if (this.processedEvents?.has(eventKey)) {
return; // 已处理过的事件
}
this.processedEvents = this.processedEvents || new Set();
this.processedEvents.add(eventKey);
// 发出事件
this.eventBus.dispatchEvent(new CustomEvent('contractEvent', {
detail: eventData
}));
// 清理旧事件记录(保留最近1000个)
if (this.processedEvents.size > 1000) {
const eventsArray = Array.from(this.processedEvents);
this.processedEvents = new Set(eventsArray.slice(-500));
}
}
// 取消订阅
unsubscribe(subscriptionId) {
const subscription = this.subscriptions.get(subscriptionId);
if (subscription) {
switch (subscription.type) {
case 'CONTRACT_EVENT':
subscription.contract.removeAllListeners(subscription.eventFilter);
break;
case 'BLOCK_EVENT':
this.provider.removeAllListeners('block');
break;
}
this.subscriptions.delete(subscriptionId);
}
// 清除轮询
if (this.pollingIntervals.has(subscriptionId)) {
clearInterval(this.pollingIntervals.get(subscriptionId));
this.pollingIntervals.delete(subscriptionId);
}
}
// 清理所有订阅
cleanup() {
this.subscriptions.forEach((_, id) => this.unsubscribe(id));
this.pollingIntervals.forEach(intervalId => clearInterval(intervalId));
this.subscriptions.clear();
this.pollingIntervals.clear();
}
}
React实时数据Hook:
// 实时合约数据Hook
function useRealTimeContractData(
contractAddress,
abi,
method,
params = [],
events = [],
options = {}
) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [lastUpdated, setLastUpdated] = useState(null);
const dataManager = useMemo(() => new RealTimeDataManager(provider), []);
// 数据获取函数
const fetchData = useCallback(async () => {
try {
const contract = new ethers.Contract(contractAddress, abi, provider);
const result = await contract[method](...params);
return result;
} catch (error) {
console.error('获取合约数据失败:', error);
throw error;
}
}, [contractAddress, method, JSON.stringify(params)]);
// 初始数据加载
useEffect(() => {
let mounted = true;
const loadInitialData = async () => {
try {
const result = await fetchData();
if (mounted) {
setData(result);
setLastUpdated(Date.now());
}
} catch (error) {
console.error('初始数据加载失败:', error);
} finally {
if (mounted) setLoading(false);
}
};
loadInitialData();
return () => {
mounted = false;
};
}, [fetchData]);
// 设置事件监听
useEffect(() => {
const subscriptionIds = [];
// 监听相关合约事件
events.forEach(eventName => {
const subscriptionId = dataManager.subscribeToContractEvents(
contractAddress,
abi,
eventName
);
subscriptionIds.push(subscriptionId);
});
// 监听事件并刷新数据
const handleContractEvent = async (event) => {
const { contractAddress: eventContract } = event.detail;
if (eventContract.toLowerCase() === contractAddress.toLowerCase()) {
try {
const newData = await fetchData();
setData(newData);
setLastUpdated(Date.now());
} catch (error) {
console.error('事件触发的数据刷新失败:', error);
}
}
};
dataManager.eventBus.addEventListener('contractEvent', handleContractEvent);
// 设置轮询(可选)
if (options.polling && options.pollingInterval) {
const pollingId = dataManager.setupPolling(
`${contractAddress}_${method}`,
async () => {
const newData = await fetchData();
setData(newData);
setLastUpdated(Date.now());
return newData;
},
options.pollingInterval
);
subscriptionIds.push(pollingId);
}
return () => {
subscriptionIds.forEach(id => dataManager.unsubscribe(id));
dataManager.eventBus.removeEventListener('contractEvent', handleContractEvent);
};
}, [contractAddress, fetchData, events, options]);
// 手动刷新
const refresh = useCallback(async () => {
setLoading(true);
try {
const newData = await fetchData();
setData(newData);
setLastUpdated(Date.now());
return newData;
} catch (error) {
console.error('手动刷新失败:', error);
throw error;
} finally {
setLoading(false);
}
}, [fetchData]);
return {
data,
loading,
lastUpdated,
refresh
};
}
// 实时余额Hook
function useRealTimeBalance(tokenAddress, userAddress) {
return useRealTimeContractData(
tokenAddress,
ERC20_ABI,
'balanceOf',
[userAddress],
['Transfer'], // 监听Transfer事件
{
polling: true,
pollingInterval: 30000 // 30秒轮询
}
);
}
// 实时价格Hook
function useRealTimePrice(tokenAddress, baseToken = WETH_ADDRESS) {
const [price, setPrice] = useState(null);
const [priceChange24h, setPriceChange24h] = useState(null);
useEffect(() => {
let ws;
let reconnectTimeout;
const connectWebSocket = () => {
// 连接价格数据WebSocket
ws = new WebSocket('wss://api.dexscreener.com/ws');
ws.onopen = () => {
console.log('价格WebSocket已连接');
// 订阅价格数据
ws.send(JSON.stringify({
type: 'subscribe',
token: tokenAddress,
base: baseToken
}));
};
ws.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.type === 'priceUpdate') {
setPrice(data.price);
setPriceChange24h(data.change24h);
}
} catch (error) {
console.error('价格数据解析失败:', error);
}
};
ws.onclose = () => {
console.log('价格WebSocket已断开');
// 5秒后重连
reconnectTimeout = setTimeout(connectWebSocket, 5000);
};
ws.onerror = (error) => {
console.error('价格WebSocket错误:', error);
};
};
connectWebSocket();
return () => {
if (ws) {
ws.close();
}
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
}
};
}, [tokenAddress, baseToken]);
return { price, priceChange24h };
}
// 使用示例
function TokenDashboard({ tokenAddress, userAddress }) {
const { data: balance, loading: balanceLoading, lastUpdated } = useRealTimeBalance(
tokenAddress,
userAddress
);
const { price, priceChange24h } = useRealTimePrice(tokenAddress);
const formatTimestamp = (timestamp) => {
return new Date(timestamp).toLocaleTimeString();
};
return (
<div className="token-dashboard">
<div className="balance-section">
<h3>代币余额</h3>
{balanceLoading ? (
<div>加载中...</div>
) : (
<div>
<div className="balance-amount">
{ethers.utils.formatEther(balance || 0)} TOKEN
</div>
{lastUpdated && (
<div className="last-updated">
最后更新: {formatTimestamp(lastUpdated)}
</div>
)}
</div>
)}
</div>
<div className="price-section">
<h3>实时价格</h3>
{price ? (
<div>
<div className="price-amount">${price}</div>
<div className={`price-change ${priceChange24h >= 0 ? 'positive' : 'negative'}`}>
24h: {priceChange24h >= 0 ? '+' : ''}{priceChange24h?.toFixed(2)}%
</div>
</div>
) : (
<div>价格加载中...</div>
)}
</div>
</div>
);
}
性能优化技巧:
How to design frontend architecture for cross-chain DApp?
How to design frontend architecture for cross-chain DApp?
考察点:跨链应用设计。
How should caching strategy be designed in DApp? Balance performance and data consistency.
How should caching strategy be designed in DApp? Balance performance and data consistency.
考察点:缓存设计策略。
How to implement efficient batch operations in DApp?
How to implement efficient batch operations in DApp?
考察点:批量操作优化。
答案:
DApp中的批量操作主要通过Multicall合约、交易打包和并发处理实现。可以显著减少交易数量和Gas消耗,提高操作效率。
批量操作策略:
实现示例:
class BatchOperationManager {
constructor(multicallContract) {
this.multicall = multicallContract;
this.batchQueue = [];
this.processing = false;
}
addToBatch(target, calldata, options = {}) {
this.batchQueue.push({
target,
calldata,
options,
timestamp: Date.now()
});
if (this.batchQueue.length >= 10 || options.urgent) {
this.processBatch();
}
}
async processBatch() {
if (this.processing || this.batchQueue.length === 0) return;
this.processing = true;
const calls = this.batchQueue.splice(0, 50); // 最多50个调用
try {
const multicallData = calls.map(call => ({
target: call.target,
callData: call.calldata
}));
const tx = await this.multicall.aggregate(multicallData);
const receipt = await tx.wait();
return { success: true, receipt, callCount: calls.length };
} catch (error) {
console.error('批量操作失败:', error);
throw error;
} finally {
this.processing = false;
}
}
}
What is MEV protection? How to implement transaction protection in DApp frontend?
What is MEV protection? How to implement transaction protection in DApp frontend?
考察点:交易保护机制。
How should testing strategy be designed for DApp? How to simulate blockchain environment?
How should testing strategy be designed for DApp? How to simulate blockchain environment?
考察点:测试策略设计。
答案:
DApp测试策略需要覆盖前端组件、合约交互、网络层和端到端流程。由于涉及区块链交互,需要设计多层次的测试环境,包括本地模拟、测试网络和主网分叉测试。
测试策略层次:
区块链环境模拟:
// 区块链环境模拟器
class BlockchainTestEnvironment {
constructor() {
this.providers = new Map();
this.contracts = new Map();
this.accounts = [];
this.snapshots = new Map();
}
// 设置本地测试网络
async setupLocalNetwork() {
// 使用Hardhat本地网络
const provider = new ethers.providers.JsonRpcProvider('http://localhost:8545');
// 获取测试账户
const accounts = await provider.listAccounts();
this.accounts = accounts.map(address => ({
address,
signer: provider.getSigner(address)
}));
this.providers.set('local', provider);
return provider;
}
// 设置主网分叉
async setupMainnetFork(blockNumber = 'latest') {
const provider = new ethers.providers.JsonRpcProvider({
url: 'http://localhost:8545',
timeout: 60000
});
// 分叉主网到指定区块
await provider.send('hardhat_reset', [{
forking: {
jsonRpcUrl: process.env.MAINNET_RPC_URL,
blockNumber: blockNumber === 'latest' ? undefined : blockNumber
}
}]);
this.providers.set('mainnet-fork', provider);
return provider;
}
// 部署测试合约
async deployTestContracts(contractConfigs, provider) {
const deployedContracts = {};
for (const config of contractConfigs) {
const { name, contractFactory, constructorArgs = [] } = config;
try {
const signer = provider.getSigner();
const contract = await contractFactory.connect(signer).deploy(...constructorArgs);
await contract.deployed();
deployedContracts[name] = {
contract,
address: contract.address,
abi: contract.interface.fragments
};
this.contracts.set(name, deployedContracts[name]);
} catch (error) {
console.error(`部署合约 ${name} 失败:`, error);
throw error;
}
}
return deployedContracts;
}
// 创建快照
async createSnapshot(provider, snapshotId = null) {
const id = snapshotId || `snapshot_${Date.now()}`;
try {
const snapshotResult = await provider.send('evm_snapshot', []);
this.snapshots.set(id, snapshotResult);
return { id, snapshotId: snapshotResult };
} catch (error) {
console.error('创建快照失败:', error);
throw error;
}
}
// 恢复快照
async restoreSnapshot(provider, snapshotId) {
const snapshot = this.snapshots.get(snapshotId);
if (!snapshot) {
throw new Error(`快照 ${snapshotId} 不存在`);
}
try {
await provider.send('evm_revert', [snapshot]);
// 创建新快照以便后续使用
const newSnapshot = await provider.send('evm_snapshot', []);
this.snapshots.set(snapshotId, newSnapshot);
} catch (error) {
console.error('恢复快照失败:', error);
throw error;
}
}
// 时间操作
async increaseTime(provider, seconds) {
await provider.send('evm_increaseTime', [seconds]);
await provider.send('evm_mine', []);
}
async mineBlocks(provider, blocks) {
for (let i = 0; i < blocks; i++) {
await provider.send('evm_mine', []);
}
}
// 设置账户余额
async setBalance(provider, address, balance) {
await provider.send('hardhat_setBalance', [
address,
ethers.utils.hexValue(balance)
]);
}
// 模拟账户
async impersonateAccount(provider, address) {
await provider.send('hardhat_impersonateAccount', [address]);
return provider.getSigner(address);
}
// 清理环境
async cleanup() {
this.providers.clear();
this.contracts.clear();
this.snapshots.clear();
this.accounts = [];
}
}
Jest测试配置和工具:
// Jest测试配置 (jest.config.js)
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/tests/setup.js'],
moduleNameMapping: {
'^@/(.*)$': '<rootDir>/src/$1',
'^@tests/(.*)$': '<rootDir>/src/tests/$1'
},
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{test,spec}.{js,jsx,ts,tsx}'
],
collectCoverageFrom: [
'src/**/*.{js,jsx,ts,tsx}',
'!src/**/*.d.ts',
'!src/tests/**/*'
],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
}
};
// 测试工具函数 (src/tests/utils.js)
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { ethers } from 'ethers';
import { BlockchainTestEnvironment } from './BlockchainTestEnvironment';
// 区块链测试工具
export class DAppTestUtils {
constructor() {
this.testEnv = new BlockchainTestEnvironment();
this.mockProvider = null;
this.mockSigner = null;
}
// 设置测试环境
async setupTestEnvironment() {
this.mockProvider = await this.testEnv.setupLocalNetwork();
this.mockSigner = this.mockProvider.getSigner();
return {
provider: this.mockProvider,
signer: this.mockSigner,
accounts: this.testEnv.accounts
};
}
// 创建Mock Provider
createMockProvider(overrides = {}) {
const defaultMethods = {
getNetwork: jest.fn().mockResolvedValue({ chainId: 1, name: 'homestead' }),
getBalance: jest.fn().mockResolvedValue(ethers.utils.parseEther('10')),
getBlockNumber: jest.fn().mockResolvedValue(12345),
estimateGas: jest.fn().mockResolvedValue(ethers.BigNumber.from('21000')),
getFeeData: jest.fn().mockResolvedValue({
maxFeePerGas: ethers.utils.parseUnits('20', 'gwei'),
maxPriorityFeePerGas: ethers.utils.parseUnits('2', 'gwei')
}),
sendTransaction: jest.fn().mockImplementation(() => ({
hash: '0x' + '0'.repeat(64),
wait: jest.fn().mockResolvedValue({
status: 1,
blockNumber: 12346,
gasUsed: ethers.BigNumber.from('21000')
})
}))
};
return { ...defaultMethods, ...overrides };
}
// 创建Mock合约
createMockContract(abi, address, overrides = {}) {
const mockContract = {
address,
interface: new ethers.utils.Interface(abi),
provider: this.mockProvider,
...overrides
};
// 自动创建方法Mock
abi.forEach(fragment => {
if (fragment.type === 'function') {
mockContract[fragment.name] = jest.fn();
}
});
return mockContract;
}
// 等待交易确认
async waitForTransaction(txHash, confirmations = 1) {
return new Promise(resolve => {
setTimeout(() => {
resolve({
status: 1,
blockNumber: 12346,
gasUsed: ethers.BigNumber.from('21000'),
transactionHash: txHash
});
}, 100);
});
}
}
// React组件测试辅助函数
export const renderWithProviders = (component, {
web3Provider = null,
userAddress = '0x123...',
chainId = 1
} = {}) => {
const TestWrapper = ({ children }) => {
const mockWeb3Context = {
provider: web3Provider,
userAddress,
chainId,
isConnected: !!userAddress,
connect: jest.fn(),
disconnect: jest.fn()
};
return (
<Web3Context.Provider value={mockWeb3Context}>
{children}
</Web3Context.Provider>
);
};
return render(component, { wrapper: TestWrapper });
};
实际测试用例示例:
// 代币转账组件测试
describe('TokenTransferForm', () => {
let testUtils;
let mockContract;
let mockProvider;
beforeEach(async () => {
testUtils = new DAppTestUtils();
const { provider } = await testUtils.setupTestEnvironment();
mockProvider = testUtils.createMockProvider();
mockContract = testUtils.createMockContract(ERC20_ABI, '0x123...', {
balanceOf: jest.fn().mockResolvedValue(ethers.utils.parseEther('100')),
transfer: jest.fn().mockResolvedValue({
hash: '0xabc123...',
wait: jest.fn().mockResolvedValue({ status: 1 })
})
});
});
afterEach(async () => {
await testUtils.testEnv.cleanup();
});
test('应该正确显示用户余额', async () => {
renderWithProviders(
<TokenTransferForm contract={mockContract} />,
{ web3Provider: mockProvider }
);
await waitFor(() => {
expect(screen.getByText(/余额: 100/)).toBeInTheDocument();
});
expect(mockContract.balanceOf).toHaveBeenCalledWith(
expect.any(String) // 用户地址
);
});
test('应该成功提交转账交易', async () => {
renderWithProviders(
<TokenTransferForm contract={mockContract} />,
{ web3Provider: mockProvider }
);
// 填写表单
const recipientInput = screen.getByPlaceholderText('接收地址');
const amountInput = screen.getByPlaceholderText('转账数量');
const submitButton = screen.getByText('转账');
fireEvent.change(recipientInput, { target: { value: '0x456...' } });
fireEvent.change(amountInput, { target: { value: '10' } });
fireEvent.click(submitButton);
// 验证交易调用
await waitFor(() => {
expect(mockContract.transfer).toHaveBeenCalledWith(
'0x456...',
ethers.utils.parseEther('10')
);
});
});
test('余额不足时应该显示错误', async () => {
// 模拟余额不足
mockContract.balanceOf.mockResolvedValue(ethers.utils.parseEther('5'));
renderWithProviders(
<TokenTransferForm contract={mockContract} />,
{ web3Provider: mockProvider }
);
const amountInput = screen.getByPlaceholderText('转账数量');
fireEvent.change(amountInput, { target: { value: '10' } });
await waitFor(() => {
expect(screen.getByText(/余额不足/)).toBeInTheDocument();
});
});
});
// 端到端测试示例 (使用Playwright)
describe('DApp E2E Tests', () => {
let testEnv;
let page;
beforeAll(async () => {
testEnv = new BlockchainTestEnvironment();
await testEnv.setupLocalNetwork();
});
beforeEach(async () => {
page = await browser.newPage();
// 注入测试环境
await page.addInitScript(() => {
window.ethereum = {
request: async ({ method, params }) => {
// 模拟MetaMask行为
switch (method) {
case 'eth_requestAccounts':
return ['0x123...'];
case 'eth_chainId':
return '0x1';
default:
return null;
}
},
on: () => {},
removeListener: () => {}
};
});
});
afterEach(async () => {
await page.close();
});
afterAll(async () => {
await testEnv.cleanup();
});
test('完整的代币交换流程', async () => {
await page.goto('http://localhost:3000');
// 连接钱包
await page.click('[data-testid="connect-wallet"]');
await page.waitForSelector('[data-testid="wallet-connected"]');
// 选择代币
await page.click('[data-testid="select-token-from"]');
await page.click('[data-testid="token-ETH"]');
await page.click('[data-testid="select-token-to"]');
await page.click('[data-testid="token-USDC"]');
// 输入交换数量
await page.fill('[data-testid="amount-input"]', '1');
// 点击交换按钮
await page.click('[data-testid="swap-button"]');
// 等待交易确认
await page.waitForSelector('[data-testid="transaction-success"]', {
timeout: 30000
});
// 验证余额更新
const newBalance = await page.textContent('[data-testid="eth-balance"]');
expect(parseFloat(newBalance)).toBeLessThan(10); // 假设初始余额是10 ETH
});
});
测试最佳实践:
How to design deployment and release process for DApp? Handle smart contract upgrades.
How to design deployment and release process for DApp? Handle smart contract upgrades.
考察点:部署流程设计。
How to implement user data privacy protection in DApp?
How to implement user data privacy protection in DApp?
考察点:隐私保护机制。
答案:
DApp隐私保护涉及链上数据匿名化、链下数据加密、零知识证明和隐私计算等技术。需要在透明性和隐私性之间找到平衡。
隐私保护策略:
实现示例:
class PrivacyManager {
constructor() {
this.encryptionKey = null;
this.zkProofs = new Map();
}
// 客户端数据加密
async encryptSensitiveData(data, userKey) {
const encoder = new TextEncoder();
const keyMaterial = await window.crypto.subtle.importKey(
'raw',
encoder.encode(userKey),
{ name: 'PBKDF2' },
false,
['deriveBits', 'deriveKey']
);
const key = await window.crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: encoder.encode('dapp-salt'),
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
true,
['encrypt', 'decrypt']
);
const iv = window.crypto.getRandomValues(new Uint8Array(12));
const encrypted = await window.crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
encoder.encode(JSON.stringify(data))
);
return {
encrypted: Array.from(new Uint8Array(encrypted)),
iv: Array.from(iv)
};
}
// 生成零知识证明
async generateZKProof(secret, publicInput) {
// 这里是简化示例,实际需要使用专门的ZK库
const commitment = await this.hash(secret + publicInput);
return {
commitment,
proof: 'zk-proof-data', // 实际的ZK证明
publicInputs: [publicInput]
};
}
// 匿名化地址
generatePseudoAddress(realAddress, salt) {
return ethers.utils.keccak256(
ethers.utils.defaultAbiCoder.encode(
['address', 'bytes32'],
[realAddress, salt]
)
);
}
}
隐私保护最佳实践: