@li-shi-chen/metanode-wallet-sdk
v1.0.2
Published
A lightweight wallet SDK for Web3 applications
Maintainers
Readme
轻量级钱包SDK教学课件
📚 课程概览
课程目标
- 理解现代Web3钱包连接的核心概念
- 学习如何构建一个轻量级的钱包SDK
- 掌握React Context模式在复杂状态管理中的应用
- 了解多钱包支持和链切换的实现原理
项目介绍
这是一个轻量级的钱包连接SDK,作为RainbowKit的简化替代方案,提供了:
- 🔌 多钱包支持(MetaMask、Coinbase、WalletConnect)
- 🔗 多链支持和链切换功能
- 🎨 灵活的UI组件设计
- ⚡ 轻量级、无多余依赖
📁 第一章:项目架构设计
1.1 项目目录结构
src/wallet-sdk/
├── components/ # UI组件
│ ├── ConnectButton.tsx # 连接按钮主组件
│ ├── WalletButton.tsx # 单个钱包按钮
│ └── WalletModal.tsx # 钱包选择弹窗
├── connectors/ # 钱包连接器
│ ├── metamask.ts # MetaMask连接器
│ ├── coinbase.ts # Coinbase连接器
│ └── walletconnect.ts # WalletConnect连接器
├── providers/ # 上下文提供者
│ └── WalletProvider.tsx # 核心状态管理
├── icons/ # 图标组件
├── types.ts # TypeScript类型定义
└── utils.ts # 工具函数1.2 架构设计原则
🎯 关注点分离
- 组件层: 纯UI展示逻辑
- 连接器层: 特定钱包的连接逻辑
- 状态层: 全局状态管理
- 工具层: 通用函数和类型
🔄 可扩展性
- 新钱包连接器易于添加
- 支持自定义主题和样式
- 链配置完全可定制
🔧 第二章:核心类型系统
2.1 基础接口设计
// 钱包接口 - 定义每个钱包的核心属性
interface Wallet {
id: string; // 唯一标识符
name: string; // 显示名称
icon: React.FC; // 图标组件
connector: () => Promise<any>; // 连接函数
description?: string; // 描述信息
installed?: boolean; // 是否已安装
downloadUrl?: string; // 下载链接
}
// 链配置接口 - 支持多链环境
interface Chain {
id: number; // 链ID
name: string; // 链名称
rpcUrl: string; // RPC节点地址
currency: { // 原生代币信息
name: string;
symbol: string;
decimals: number;
};
blockExplorer?: { // 区块浏览器(可选)
name: string;
url: string;
};
}2.2 状态管理类型
// 钱包状态 - 核心连接状态
interface WalletState {
address: string | null; // 用户地址
chainId: number | null; // 当前链ID
isConnecting: boolean; // 连接中状态
isConnected: boolean; // 连接状态
ensName: string | null; // ENS域名
error: Error | null; // 错误信息
}
// Context值 - 组合状态和方法
interface WalletContextValue extends WalletState {
connect: (walletId: string) => Promise<void>;
disconnect: () => Promise<void>;
switchChain: (chainId: number) => Promise<void>;
openModal: () => void;
closeModal: () => void;
isModalOpen: boolean;
provider: any;
chains: Chain[];
}⚡ 第三章:状态管理核心 - WalletProvider
3.1 React Context模式的应用
🎯 为什么选择Context?
- 避免属性钻井: 深层组件直接访问钱包状态
- 集中式状态: 统一管理连接状态和方法
- 性能优化: 减少不必要的re-render
🔄 Context设计模式
// 1. 创建Context
const WalletContext = createContext<WalletContextValue>(defaultValue);
// 2. 提供Provider
export const WalletProvider: React.FC<WalletProviderProps> = ({
children, chains, wallets, autoConnect = true
}) => {
// 状态管理逻辑
return (
<WalletContext.Provider value={contextValue}>
{children}
<WalletModal /> {/* 模态框组件 */}
</WalletContext.Provider>
);
};
// 3. 自定义Hook
export const useWallet = (): WalletContextValue => {
const context = useContext(WalletContext);
if (!context) {
throw new Error('useWallet must be used within WalletProvider');
}
return context;
};3.2 核心功能实现
🔌 连接功能
const connect = async (walletId: string): Promise<void> => {
try {
const wallet = walletsMap[walletId];
if (!wallet) throw new Error(`Wallet ${walletId} not found`);
setState(prev => ({ ...prev, isConnecting: true, error: null }));
// 调用具体钱包的连接器
const { address, chainId, provider, signer } = await wallet.connector();
setState({
address, chainId,
isConnecting: false,
isConnected: true,
ensName: null,
error: null,
});
// 持久化连接信息
setStorageItem('lastConnectedWallet', walletId, 24);
setIsModalOpen(false);
} catch (error) {
setState(prev => ({
...prev,
isConnecting: false,
error: error as Error,
}));
}
};🔄 链切换功能
const switchChain = async (chainId: number): Promise<void> => {
const targetChain = chains.find(chain => chain.id === chainId);
if (!targetChain) {
throw new Error(`Chain ${chainId} not supported`);
}
try {
// 尝试切换到目标链
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: `0x${chainId.toString(16)}` }],
});
} catch (switchError: any) {
// 如果链未添加,则添加该链
if (switchError.code === 4902) {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: `0x${chainId.toString(16)}`,
chainName: targetChain.name,
rpcUrls: [targetChain.rpcUrl],
nativeCurrency: targetChain.currency,
blockExplorerUrls: targetChain.blockExplorer
? [targetChain.blockExplorer.url] : undefined,
}],
});
}
}
};3.3 事件监听和响应
📡 钱包事件处理
useEffect(() => {
const handleAccountsChanged = (event: CustomEvent<{accounts: string[]}>) => {
const [newAddress] = event.detail.accounts;
setState(prev => ({
...prev,
address: newAddress || null,
isConnected: !!newAddress,
}));
};
const handleChainChanged = (event: CustomEvent<{chainId: number}>) => {
setState(prev => ({
...prev,
chainId: event.detail.chainId,
}));
};
// 注册事件监听器
window.addEventListener('wallet_accountsChanged', handleAccountsChanged);
window.addEventListener('wallet_chainChanged', handleChainChanged);
return () => {
// 清理事件监听器
window.removeEventListener('wallet_accountsChanged', handleAccountsChanged);
window.removeEventListener('wallet_chainChanged', handleChainChanged);
};
}, []);🔌 第四章:钱包连接器详解
4.1 连接器设计模式
🎯 统一接口
所有钱包连接器都遵循相同的接口:
const connector = async (): Promise<{
provider: any;
signer: any;
address: string;
chainId: number;
}> => {
// 具体连接逻辑
};4.2 MetaMask连接器实现
🦊 核心连接逻辑
const connectMetaMask = async (): Promise<any> => {
// 1. 检查安装状态
if (!isMetaMaskInstalled()) {
throw new Error('MetaMask is not installed');
}
// 2. 请求账户访问权限
const accounts = await window.ethereum?.request({
method: 'eth_requestAccounts',
});
// 3. 创建Provider和Signer
const provider = new ethers.BrowserProvider(window.ethereum!);
const signer = await provider.getSigner();
const address = await signer.getAddress();
const { chainId } = await provider.getNetwork();
// 4. 设置事件监听
setupEventListeners();
return { provider, signer, address, chainId };
};📡 事件监听设置
const setupEventListeners = () => {
window.ethereum?.on('accountsChanged', (newAccounts: string[]) => {
if (newAccounts.length === 0) {
window.dispatchEvent(new CustomEvent('wallet_disconnected'));
} else {
window.dispatchEvent(new CustomEvent('wallet_accountsChanged', {
detail: { accounts: newAccounts },
}));
}
});
window.ethereum?.on('chainChanged', (chainIdHex: string) => {
const newChainId = parseInt(chainIdHex, 16);
window.dispatchEvent(new CustomEvent('wallet_chainChanged', {
detail: { chainId: newChainId },
}));
});
};4.3 多钱包支持策略
🔄 钱包检测逻辑
// utils.ts
export const isMetaMaskInstalled = (): boolean => {
return typeof window !== 'undefined' &&
typeof window.ethereum !== 'undefined' &&
window.ethereum.isMetaMask === true;
};
export const isCoinbaseInstalled = (): boolean => {
return typeof window !== 'undefined' &&
typeof window.ethereum !== 'undefined' &&
window.ethereum.isCoinbaseWallet === true;
};🎨 第五章:UI组件设计
5.1 ConnectButton组件架构
🎛️ 多状态设计
const ConnectButton: React.FC<ConnectButtonProps> = ({
label = 'Connect Wallet',
showBalance = true,
chainStatus = 'full', // 链显示模式
accountStatus = 'full', // 账户显示模式
size = 'md', // 按钮尺寸
className = '',
}) => {
const {
isConnected, address, chainId,
openModal, disconnect, switchChain
} = useWallet();
// 未连接状态:显示连接按钮
if (!isConnected) {
return (
<button onClick={openModal} className={buttonStyles}>
<Wallet size={iconSize} />
{label}
</button>
);
}
// 已连接状态:显示网络选择器和账户信息
return (
<div className="flex items-center gap-2">
<NetworkSelector />
<AccountDisplay />
</div>
);
};🌐 网络选择器
const NetworkSelector = () => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
return (
<div className="relative">
<button onClick={() => setIsDropdownOpen(!isDropdownOpen)}>
<Globe size={16} />
{getNetworkName(chainId)}
<ChevronDown size={16} />
</button>
{isDropdownOpen && (
<div className="dropdown-menu">
{chains.map((chain) => (
<button
key={chain.id}
onClick={() => handleChainSwitch(chain.id)}
className={chain.id === chainId ? 'active' : ''}
>
{chain.name}
</button>
))}
</div>
)}
</div>
);
};5.2 响应式设计和主题
🎨 Tailwind CSS集成
const sizeClasses = {
sm: 'text-sm px-3 py-1.5',
md: 'text-base px-4 py-2',
lg: 'text-lg px-6 py-3',
};
const buttonStyles = `
flex items-center justify-center gap-2
bg-blue-600 hover:bg-blue-700
text-white font-medium rounded-xl
transition-colors duration-200
${sizeClasses[size]}
${className}
`;🌙 深色模式支持
/* 自动适配深色模式 */
.wallet-button {
@apply bg-white dark:bg-gray-800
border-gray-200 dark:border-gray-700
text-gray-900 dark:text-white
hover:bg-gray-50 dark:hover:bg-gray-750;
}🛠️ 第六章:工具函数和实用功能
6.1 地址处理工具
✂️ 地址截断
export const truncateAddress = (address: string): string => {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
};💰 余额格式化
export const formatEther = (wei: bigint): string => {
const ether = ethers.formatEther(wei);
const num = parseFloat(ether);
if (num < 0.0001) return '< 0.0001 ETH';
if (num < 1) return `${num.toFixed(4)} ETH`;
if (num < 1000) return `${num.toFixed(3)} ETH`;
return `${(num / 1000).toFixed(2)}K ETH`;
};6.2 本地存储管理
💾 持久化工具
export const setStorageItem = <T>(
key: string,
value: T,
expiryHours: number = 24
): void => {
const item = {
value,
expiry: Date.now() + (expiryHours * 60 * 60 * 1000),
};
localStorage.setItem(key, JSON.stringify(item));
};
export const getStorageItem = <T>(key: string): T | null => {
const itemStr = localStorage.getItem(key);
if (!itemStr) return null;
const item = JSON.parse(itemStr);
if (Date.now() > item.expiry) {
localStorage.removeItem(key);
return null;
}
return item.value;
};6.3 网络工具
🌐 网络名称映射
export const getNetworkName = (chainId: number): string => {
const networks: Record<number, string> = {
1: 'Ethereum',
5: 'Goerli',
11155111: 'Sepolia',
137: 'Polygon',
80001: 'Mumbai',
56: 'BSC',
97: 'BSC Testnet',
};
return networks[chainId] || `Chain ${chainId}`;
};🔧 第七章:项目配置和构建
7.1 开发环境配置
📦 核心依赖
{
"dependencies": {
"ethers": "^6.14.3", // 以太坊交互库
"lucide-react": "^0.344.0", // 图标库
"react": "^19.0.0", // React框架
"wagmi": "^2.15.4" // Web3 React Hooks
},
"devDependencies": {
"tailwindcss": "^3.4.1", // CSS框架
"typescript": "^5.5.3", // TypeScript
"vite": "^5.4.2", // 构建工具
"terser": "^5.44.1" // 代码压缩
}
}⚙️ TypeScript配置
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
}
}7.2 构建和部署
🚀 双模式构建系统
本项目支持两种构建模式:
- SDK库模式 - 构建可发布的npm包
- 演示网页模式 - 构建展示SDK功能的静态网站
📦 构建脚本说明
{
"scripts": {
"dev": "vite", // 开发服务器(SDK演示)
"dev:demo": "vite --mode demo", // 演示网页开发模式
"build": "npm run build:sdk && npm run build:demo", // 构建所有
"build:sdk": "vite build --mode lib", // 构建SDK库
"build:demo": "vite build --mode demo", // 构建演示网页
"build:lib": "npm run build:sdk && npm run build:types", // 构建SDK+类型
"preview": "vite preview", // 预览SDK构建
"preview:demo": "vite preview --outDir docs" // 预览演示网页
}
}⚙️ Vite配置(双模式)
// vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'path';
export default defineConfig(({ mode }) => {
const isLib = mode === 'lib';
return {
plugins: [react()],
build: isLib ? {
// SDK库模式配置
lib: {
entry: resolve(__dirname, 'src/wallet-sdk/index.ts'),
name: 'MetaNodeWalletSDK',
fileName: (format) => `metanode-wallet-sdk.${format}.js`,
formats: ['es', 'umd', 'cjs']
},
rollupOptions: {
external: ['react', 'react-dom', 'react/jsx-runtime', 'ethers'],
output: {
globals: {
'react': 'React',
'react-dom': 'ReactDOM',
'react/jsx-runtime': 'React',
'ethers': 'ethers'
}
}
},
outDir: 'dist',
minify: 'terser'
} : {
// 演示网页模式配置
outDir: 'docs',
rollupOptions: {
output: {
manualChunks: {
'vendor': ['react', 'react-dom'],
'highlight': ['highlight.js']
}
}
},
minify: 'terser'
}
};
});📁 构建输出目录
- dist/ - SDK库文件(npm发布)
metanode-wallet-sdk.es.js- ES模块版本metanode-wallet-sdk.umd.js- UMD版本(浏览器)metanode-wallet-sdk.cjs.js- CommonJS版本
- docs/ - 演示网页文件(可部署到GitHub Pages)
index.html- 主页assets/- 静态资源
🚀 快速开始
# 安装依赖
npm install
# 开发模式(演示网页)
npm run dev
# 构建SDK库
npm run build:sdk
# 构建演示网页
npm run build:demo
# 一键构建所有
npm run build
# 预览构建结果
npm run preview:demo🎯 第八章:使用示例和最佳实践
8.1 基础使用
🏗️ 应用设置
// App.tsx
import { WalletProvider } from './wallet-sdk';
import { metaMaskWallet, coinbaseWallet } from './wallet-sdk/connectors';
const chains = [
{
id: 1,
name: 'Ethereum',
rpcUrl: 'https://eth-mainnet.g.alchemy.com/v2/your-api-key',
currency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
},
{
id: 11155111,
name: 'Sepolia',
rpcUrl: 'https://sepolia.infura.io/v3/your-project-id',
currency: { name: 'Sepolia Ether', symbol: 'ETH', decimals: 18 },
},
];
const wallets = [metaMaskWallet, coinbaseWallet];
function App() {
return (
<WalletProvider chains={chains} wallets={wallets}>
<YourApp />
</WalletProvider>
);
}🎛️ 组件使用
// 在任何子组件中
import { useWallet } from './wallet-sdk';
import ConnectButton from './wallet-sdk/components/ConnectButton';
function MyComponent() {
const { isConnected, address, chainId } = useWallet();
return (
<div>
<ConnectButton
label="连接钱包"
size="lg"
chainStatus="name"
accountStatus="full"
/>
{isConnected && (
<div>
<p>地址: {address}</p>
<p>链ID: {chainId}</p>
</div>
)}
</div>
);
}8.2 高级用法
🔧 自定义连接器
// 创建新的钱包连接器
import { Wallet } from './wallet-sdk/types';
const customWallet: Wallet = {
id: 'custom-wallet',
name: 'Custom Wallet',
icon: CustomIcon,
connector: async () => {
// 自定义连接逻辑
const provider = await connectToCustomWallet();
const signer = provider.getSigner();
const address = await signer.getAddress();
const network = await provider.getNetwork();
return {
provider,
signer,
address,
chainId: Number(network.chainId),
};
},
installed: checkCustomWalletInstallation(),
downloadUrl: 'https://custom-wallet.com/download',
};🎨 自定义主题
const customTheme = {
accentColor: '#6366f1',
accentColorForeground: '#ffffff',
borderRadius: '12px',
fontStack: 'system-ui, sans-serif',
};
<WalletProvider
chains={chains}
wallets={wallets}
theme={customTheme}
>
<App />
</WalletProvider>🚀 第九章:性能优化和最佳实践
9.1 性能优化策略
⚡ 组件优化
// 使用React.memo避免不必要的重渲染
const ConnectButton = React.memo<ConnectButtonProps>(({
label, size, showBalance
}) => {
// 组件逻辑
});
// 使用useMemo缓存计算结果
const walletsMap = useMemo(() => {
return wallets.reduce((acc, wallet) => {
acc[wallet.id] = wallet;
return acc;
}, {} as Record<string, Wallet>);
}, [wallets]);
// 使用useCallback缓存函数引用
const handleConnect = useCallback(async (walletId: string) => {
await connect(walletId);
}, [connect]);🔄 状态更新优化
// 批量状态更新
setState(prev => ({
...prev,
isConnecting: false,
isConnected: true,
address: newAddress,
chainId: newChainId,
}));
// 避免深层对象比较
const contextValue = useMemo(() => ({
...state,
connect,
disconnect,
switchChain,
openModal,
closeModal,
isModalOpen,
provider,
chains,
}), [state, connect, disconnect, switchChain, /* 其他依赖 */]);9.2 错误处理最佳实践
🛡️ 错误边界
class WalletErrorBoundary extends React.Component {
constructor(props: any) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error) {
return { hasError: true };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error('钱包连接错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>钱包连接出现错误,请刷新页面重试</div>;
}
return this.props.children;
}
}⚠️ 优雅错误处理
const connect = async (walletId: string): Promise<void> => {
try {
setState(prev => ({ ...prev, isConnecting: true, error: null }));
const wallet = walletsMap[walletId];
if (!wallet) {
throw new Error(`钱包 "${walletId}" 未找到`);
}
if (!wallet.installed) {
throw new Error(`请先安装 ${wallet.name}`);
}
const result = await wallet.connector();
// 成功处理逻辑
} catch (error) {
const errorMessage = error instanceof Error
? error.message
: '连接失败,请重试';
setState(prev => ({
...prev,
isConnecting: false,
error: new Error(errorMessage),
}));
// 可选:错误上报
reportError(error, { walletId, userAgent: navigator.userAgent });
}
};9.3 安全考虑
🔒 安全最佳实践
// 1. 验证钱包地址格式
const isValidAddress = (address: string): boolean => {
return /^0x[a-fA-F0-9]{40}$/.test(address);
};
// 2. 限制RPC调用频率
const rateLimitedRequest = throttle(async (method: string, params: any[]) => {
return await provider.send(method, params);
}, 1000); // 1秒限制
// 3. 敏感数据不持久化
const sanitizeForStorage = (data: any) => {
const { privateKey, mnemonic, ...safeData } = data;
return safeData;
};
// 4. CSP安全头设置
// <meta http-equiv="Content-Security-Policy"
// content="default-src 'self'; connect-src 'self' https:;">📊 第十章:测试和调试
10.1 单元测试
🧪 组件测试
// ConnectButton.test.tsx
import { render, fireEvent, screen } from '@testing-library/react';
import { WalletProvider } from '../providers/WalletProvider';
import ConnectButton from '../components/ConnectButton';
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
<WalletProvider chains={mockChains} wallets={mockWallets}>
{children}
</WalletProvider>
);
describe('ConnectButton', () => {
test('显示连接按钮当未连接时', () => {
render(
<TestWrapper>
<ConnectButton label="连接钱包" />
</TestWrapper>
);
expect(screen.getByText('连接钱包')).toBeInTheDocument();
});
test('点击按钮打开模态框', () => {
render(
<TestWrapper>
<ConnectButton />
</TestWrapper>
);
fireEvent.click(screen.getByRole('button'));
expect(screen.getByText('选择钱包')).toBeInTheDocument();
});
});🔧 Hook测试
// useWallet.test.ts
import { renderHook, act } from '@testing-library/react';
import { useWallet } from '../providers/WalletProvider';
describe('useWallet', () => {
test('初始状态正确', () => {
const { result } = renderHook(() => useWallet(), {
wrapper: TestWrapper,
});
expect(result.current.isConnected).toBe(false);
expect(result.current.address).toBeNull();
});
test('连接钱包更新状态', async () => {
const { result } = renderHook(() => useWallet(), {
wrapper: TestWrapper,
});
await act(async () => {
await result.current.connect('metamask');
});
expect(result.current.isConnected).toBe(true);
expect(result.current.address).toBeTruthy();
});
});10.2 集成测试
🔄 端到端测试
// e2e/wallet-connection.spec.ts
import { test, expect } from '@playwright/test';
test('用户可以连接MetaMask钱包', async ({ page }) => {
await page.goto('http://localhost:3000');
// 点击连接按钮
await page.click('[data-testid="connect-button"]');
// 选择MetaMask
await page.click('[data-testid="wallet-metamask"]');
// 模拟MetaMask确认
await page.evaluate(() => {
window.ethereum.selectedAddress = '0x1234...';
window.ethereum.chainId = '0x1';
});
// 验证连接状态
await expect(page.locator('[data-testid="wallet-address"]')).toBeVisible();
});10.3 调试工具
🛠️ 开发调试
// 开发环境调试工具
const DebugPanel = () => {
const wallet = useWallet();
if (process.env.NODE_ENV !== 'development') return null;
return (
<div className="fixed bottom-4 right-4 bg-black text-white p-4 rounded">
<h3>钱包调试信息</h3>
<pre>{JSON.stringify(wallet, null, 2)}</pre>
<button onClick={() => wallet.disconnect()}>强制断开</button>
</div>
);
};
// 错误监控
const errorReporter = (error: Error, context: any) => {
if (process.env.NODE_ENV === 'production') {
// 发送到错误监控服务
analytics.track('wallet_error', {
error: error.message,
stack: error.stack,
context,
});
} else {
console.error('钱包错误:', error, context);
}
};🎓 第十一章:课程总结和扩展
11.1 核心概念回顾
🎯 学习要点
- 架构设计: 模块化、可扩展的SDK架构
- 状态管理: React Context模式的深度应用
- 类型安全: TypeScript在复杂项目中的使用
- 用户体验: 流畅的钱包连接和切换体验
- 错误处理: 优雅的错误处理和用户反馈
🔄 设计模式总结
- Provider模式: 全局状态管理
- 策略模式: 多钱包连接器设计
- 观察者模式: 钱包事件监听
- 工厂模式: 钱包实例创建
11.2 扩展方向
🚀 功能扩展
// 1. ENS域名解析
const resolveENS = async (address: string): Promise<string | null> => {
try {
const provider = new ethers.providers.JsonRpcProvider();
return await provider.lookupAddress(address);
} catch {
return null;
}
};
// 2. 多签钱包支持
interface MultiSigWallet extends Wallet {
threshold: number;
owners: string[];
pendingTransactions: Transaction[];
}
// 3. NFT头像支持
const fetchNFTAvatar = async (address: string): Promise<string | null> => {
// 获取用户的NFT头像
const nfts = await fetchUserNFTs(address);
return nfts.find(nft => nft.isAvatar)?.image || null;
};
// 4. 交易历史
const useTransactionHistory = (address: string) => {
const [transactions, setTransactions] = useState([]);
useEffect(() => {
if (address) {
fetchTransactionHistory(address).then(setTransactions);
}
}, [address]);
return transactions;
};🌍 生态集成
// 1. DeFi协议集成
const useTokenBalance = (tokenAddress: string) => {
const { provider, address } = useWallet();
const [balance, setBalance] = useState('0');
useEffect(() => {
if (provider && address && tokenAddress) {
const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
contract.balanceOf(address).then(setBalance);
}
}, [provider, address, tokenAddress]);
return balance;
};
// 2. IPFS集成
const uploadToIPFS = async (file: File): Promise<string> => {
const formData = new FormData();
formData.append('file', file);
const response = await fetch('/api/ipfs/upload', {
method: 'POST',
body: formData,
});
const { hash } = await response.json();
return `ipfs://${hash}`;
};
// 3. Layer 2 支持
const bridgeToL2 = async (amount: string, l2ChainId: number) => {
const bridgeContract = new ethers.Contract(
BRIDGE_ADDRESS,
BRIDGE_ABI,
signer
);
return await bridgeContract.depositETH(l2ChainId, {
value: ethers.parseEther(amount),
});
};11.3 实际项目应用
🏗️ 企业级应用
// 1. 权限管理
const useRoleBasedAccess = (requiredRole: string) => {
const { address } = useWallet();
const [hasAccess, setHasAccess] = useState(false);
useEffect(() => {
if (address) {
checkUserRole(address, requiredRole).then(setHasAccess);
}
}, [address, requiredRole]);
return hasAccess;
};
// 2. 审计日志
const auditLogger = {
logWalletConnection: (address: string, walletType: string) => {
console.log(`用户 ${address} 通过 ${walletType} 连接`);
// 发送到审计服务
},
logTransactionSignature: (txHash: string, address: string) => {
console.log(`用户 ${address} 签署交易 ${txHash}`);
// 记录关键操作
},
};
// 3. 合规检查
const complianceCheck = async (address: string): Promise<boolean> => {
const sanctions = await checkSanctionsList(address);
const kyc = await checkKYCStatus(address);
return !sanctions && kyc;
};🎯 课程作业和实践
练习1: 基础集成
在你的React项目中集成钱包SDK,实现:
- 钱包连接/断开
- 网络切换
- 余额显示
练习2: 自定义连接器
创建一个新的钱包连接器(如Phantom钱包),包括:
- 安装检测
- 连接逻辑
- 事件监听
练习3: 高级功能
扩展SDK功能:
- 添加交易签名功能
- 实现消息签名
- 集成ENS域名解析
练习4: UI定制
定制ConnectButton组件:
- 自定义样式主题
- 添加动画效果
- 实现暗黑模式切换
📚 推荐资源
文档和工具
社区和教程
感谢参与本课程!希望这个轻量级钱包SDK项目能帮助你更好地理解Web3前端开发的核心概念和实践技巧。 🚀
