npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@li-shi-chen/metanode-wallet-sdk

v1.0.2

Published

A lightweight wallet SDK for Web3 applications

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?

  1. 避免属性钻井: 深层组件直接访问钱包状态
  2. 集中式状态: 统一管理连接状态和方法
  3. 性能优化: 减少不必要的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 构建和部署

🚀 双模式构建系统

本项目支持两种构建模式:

  1. SDK库模式 - 构建可发布的npm包
  2. 演示网页模式 - 构建展示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 核心概念回顾

🎯 学习要点

  1. 架构设计: 模块化、可扩展的SDK架构
  2. 状态管理: React Context模式的深度应用
  3. 类型安全: TypeScript在复杂项目中的使用
  4. 用户体验: 流畅的钱包连接和切换体验
  5. 错误处理: 优雅的错误处理和用户反馈

🔄 设计模式总结

  • 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前端开发的核心概念和实践技巧。 🚀