<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      簡易ETH錢包

      代碼采用React+TypeScript開發(fā),保持了良好的可維護性和用戶體驗:

      功能說明

      1. BIP-39助記詞功能

        • 完全符合BIP-39標準,生成12個單詞的助記詞
        • 強制助記詞驗證流程,確保用戶正確備份
        • 支持通過助記詞導(dǎo)入錢包,兼容行業(yè)標準
      2. ERC-20代幣支持

        • 內(nèi)置常用ERC-20代幣列表(USDT、USDC、DAI等)
        • 支持添加自定義ERC-20代幣,自動識別代幣名稱、符號和小數(shù)位
        • 實時顯示代幣余額,支持代幣轉(zhuǎn)賬功能
      3. 交易歷史查詢

        • 整合Etherscan API查詢ETH和ERC-20代幣交易歷史
        • 顯示交易哈希、發(fā)送方、接收方、金額、時間和確認數(shù)
        • 支持交易歷史刷新,區(qū)分ETH和代幣交易
      4. 私鑰AES加密存儲

        • 使用crypto-js庫實現(xiàn)AES加密算法
        • 私鑰加密后存儲在localStorage,不直接存儲明文
        • 錢包解鎖需要密碼驗證,增強安全性

      實現(xiàn)代碼

      實現(xiàn)代碼

      import React, { useState, useEffect } from 'react';
      import { ethers, HDNodeWallet } from 'ethers';
      import CryptoJS from 'crypto-js';
      import axios from 'axios';
      
      // ERC-20代幣標準ABI
      const ERC20_ABI = [
        "function name() view returns (string)",
        "function symbol() view returns (string)",
        "function decimals() view returns (uint8)",
        "function balanceOf(address) view returns (uint256)",
        "function transfer(address to, uint256 amount) returns (bool)",
        "event Transfer(address indexed from, address indexed to, uint256 value)"
      ];
      
      // 常用ERC-20代幣列表(Sepolia測試網(wǎng))
      const COMMON_TOKENS = [
        {
          symbol: "USDT",
          address: "0x7163aF91147b087166083F24Eb3a99F59F451039",
          decimals: 18
        },
        {
          symbol: "USDC",
          address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
          decimals: 6
        },
        {
          symbol: "DAI",
          address: "0x8EC166D656226935917538F69D3F660416aD219d",
          decimals: 18
        }
      ];
      
      // 樣式組件
      const Container = ({ children }: { children: React.ReactNode }) => (
        <div style={{ 
          maxWidth: 900, 
          margin: '0 auto', 
          padding: '20px', 
          fontFamily: 'Arial, sans-serif',
          backgroundColor: '#f9f9f9',
          minHeight: '100vh'
        }}>
          {children}
        </div>
      );
      
      const Card = ({ title, children }: { title: string; children: React.ReactNode }) => (
        <div style={{ 
          marginBottom: '20px', 
          padding: '15px', 
          border: '1px solid #e0e0e0', 
          borderRadius: '8px',
          backgroundColor: 'white',
          boxShadow: '0 2px 4px rgba(0,0,0,0.05)'
        }}>
          <h3 style={{ 
            margin: '0 0 15px 0', 
            color: '#2c3e50',
            fontSize: '1.2rem',
            borderBottom: '1px solid #f0f0f0',
            paddingBottom: '8px'
          }}>{title}</h3>
          {children}
        </div>
      );
      
      const Button = ({ 
        onClick, 
        children, 
        disabled = false, 
        primary = false,
        danger = false
      }: { 
        onClick: () => void; 
        children: React.ReactNode; 
        disabled?: boolean;
        primary?: boolean;
        danger?: boolean;
      }) => (
        <button
          onClick={onClick}
          disabled={disabled}
          style={{
            padding: '8px 16px',
            fontSize: '14px',
            cursor: disabled ? 'not-allowed' : 'pointer',
            border: 'none',
            borderRadius: '4px',
            backgroundColor: disabled 
              ? '#f0f0f0' 
              : danger
                ? '#e74c3c'
                : primary 
                  ? '#3498db' 
                  : '#2ecc71',
            color: 'white',
            margin: '5px',
            transition: 'background-color 0.2s',
            '&:hover': {
              opacity: 0.9
            }
          }}
        >
          {children}
        </button>
      );
      
      const Input = ({ 
        value, 
        onChange, 
        placeholder, 
        type = 'text',
        style = {},
        multiline = false
      }: { 
        value: string; 
        onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void; 
        placeholder: string;
        type?: string;
        style?: React.CSSProperties;
        multiline?: boolean;
      }) => {
        const Element = multiline ? 'textarea' : 'input';
        return React.createElement(Element, {
          type,
          value,
          onChange,
          placeholder,
          style: {
            padding: '10px',
            fontSize: '14px',
            borderRadius: '4px',
            border: '1px solid #ddd',
            width: '100%',
            boxSizing: 'border-box',
            minHeight: multiline ? '80px' : 'auto',
            resize: multiline ? 'vertical' : 'none',
            ...style
          }
        });
      };
      
      const Alert = ({ message, type = 'info' }: { message: string; type?: 'info' | 'error' | 'success' }) => {
        const styles = {
          info: { bg: '#e3f2fd', text: '#1565c0', border: '#bbdefb' },
          error: { bg: '#ffebee', text: '#b71c1c', border: '#f8bbd0' },
          success: { bg: '#e8f5e9', text: '#2e7d32', border: '#c8e6c9' }
        };
        
        const style = styles[type];
        
        return (
          <div style={{
            padding: '12px',
            borderRadius: '4px',
            backgroundColor: style.bg,
            color: style.text,
            border: `1px solid ${style.border}`,
            margin: '10px 0',
            fontSize: '14px'
          }}>
            {message}
          </div>
        );
      };
      
      const Modal = ({ 
        visible, 
        onClose, 
        title, 
        children,
        footer 
      }: { 
        visible: boolean; 
        onClose: () => void; 
        title: string; 
        children: React.ReactNode;
        footer?: React.ReactNode;
      }) => {
        if (!visible) return null;
        
        return (
          <div style={{
            position: 'fixed',
            top: 0,
            left: 0,
            right: 0,
            bottom: 0,
            backgroundColor: 'rgba(0, 0, 0, 0.5)',
            display: 'flex',
            alignItems: 'center',
            justifyContent: 'center',
            zIndex: 1000
          }}>
            <div style={{
              backgroundColor: 'white',
              padding: '20px',
              borderRadius: '8px',
              width: '90%',
              maxWidth: 600,
              maxHeight: '80vh',
              overflowY: 'auto'
            }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '15px' }}>
                <h3 style={{ margin: 0, color: '#2c3e50' }}>{title}</h3>
                <button 
                  onClick={onClose} 
                  style={{ 
                    background: 'none', 
                    border: 'none', 
                    cursor: 'pointer', 
                    fontSize: '20px',
                    color: '#777'
                  }}
                >
                  &times;
                </button>
              </div>
              {children}
              {footer && (
                <div style={{ marginTop: '20px', display: 'flex', justifyContent: 'flex-end' }}>
                  {footer}
                </div>
              )}
            </div>
          </div>
        );
      };
      
      // 主錢包組件
      const Wallet: React.FC = () => {
        // 核心狀態(tài)管理
        const [address, setAddress] = useState<string>('');
        const [privateKey, setPrivateKey] = useState<string>('');
        const [mnemonic, setMnemonic] = useState<string>('');
        const [ethBalance, setEthBalance] = useState<string>('0');
        const [recipient, setRecipient] = useState<string>('');
        const [amount, setAmount] = useState<string>('0.01');
        const [loading, setLoading] = useState<boolean>(false);
        const [message, setMessage] = useState<{ text: string; type: 'info' | 'error' | 'success' } | null>(null);
        const [provider, setProvider] = useState<ethers.JsonRpcProvider | null>(null);
        const [showPrivateKey, setShowPrivateKey] = useState<boolean>(false);
        const [importType, setImportType] = useState<'privateKey' | 'mnemonic'>('privateKey');
        const [importValue, setImportValue] = useState<string>('');
        
        // 加密相關(guān)狀態(tài)
        const [password, setPassword] = useState<string>('');
        const [confirmPassword, setConfirmPassword] = useState<string>('');
        const [unlockPassword, setUnlockPassword] = useState<string>('');
        const [isLocked, setIsLocked] = useState<boolean>(true);
        
        // 代幣相關(guān)狀態(tài)
        const [tokens, setTokens] = useState<Array<{
          symbol: string;
          address: string;
          balance: string;
          decimals: number;
          name: string;
        }>>([]);
        const [selectedToken, setSelectedToken] = useState<string>('ETH');
        const [tokenAddress, setTokenAddress] = useState<string>('');
        
        // 交易歷史相關(guān)狀態(tài)
        const [transactions, setTransactions] = useState<Array<{
          hash: string;
          from: string;
          to: string;
          value: string;
          tokenSymbol?: string;
          timeStamp: string;
          confirmations: string;
        }>>([]);
        const [transactionsLoading, setTransactionsLoading] = useState<boolean>(false);
        
        // 彈窗狀態(tài)
        const [mnemonicModalVisible, setMnemonicModalVisible] = useState<boolean>(false);
        const [saveReminderVisible, setSaveReminderVisible] = useState<boolean>(false);
        const [confirmMnemonic, setConfirmMnemonic] = useState<string>('');
        const [mnemonicConfirmed, setMnemonicConfirmed] = useState<boolean>(false);
        const [addTokenModalVisible, setAddTokenModalVisible] = useState<boolean>(false);
        const [transactionHistoryVisible, setTransactionHistoryVisible] = useState<boolean>(false);
      
        // 初始化Provider和檢查存儲的錢包
        useEffect(() => {
          // 初始化以太坊測試網(wǎng)Provider
          const initProvider = () => {
            try {
              const newProvider = new ethers.JsonRpcProvider(
                'https://sepolia.infura.io/v3/your-api-key' // 替換為你的Infura API密鑰
              );
              setProvider(newProvider);
            } catch (err) {
              setMessage({ text: '初始化區(qū)塊鏈連接失敗', type: 'error' });
              console.error(err);
            }
          };
      
          // 檢查是否有加密存儲的私鑰
          const checkStoredWallet = () => {
            const encryptedKey = localStorage.getItem('encrypted_eth_private_key');
            if (encryptedKey) {
              setIsLocked(true); // 需要密碼解鎖
            } else {
              setIsLocked(false); // 沒有存儲的錢包,顯示創(chuàng)建/導(dǎo)入界面
            }
          };
      
          initProvider();
          checkStoredWallet();
        }, []);
      
        // 錢包解鎖后加載數(shù)據(jù)
        useEffect(() => {
          if (address && provider && !isLocked) {
            loadTokens();
            fetchTransactionHistory();
          }
        }, [address, provider, isLocked]);
      
        // 解鎖錢包(AES解密)
        const unlockWallet = () => {
          if (!unlockPassword) {
            setMessage({ text: '請輸入密碼', type: 'error' });
            return;
          }
      
          try {
            const encryptedKey = localStorage.getItem('encrypted_eth_private_key');
            if (!encryptedKey) {
              setMessage({ text: '沒有找到存儲的錢包', type: 'error' });
              return;
            }
            
            // 使用AES解密私鑰
            const bytes = CryptoJS.AES.decrypt(encryptedKey, unlockPassword);
            const privateKey = bytes.toString(CryptoJS.enc.Utf8);
            
            if (!privateKey || !privateKey.startsWith('0x')) {
              setMessage({ text: '密碼錯誤', type: 'error' });
              return;
            }
            
            const wallet = new ethers.Wallet(privateKey);
            setPrivateKey(privateKey);
            setAddress(wallet.address);
            setIsLocked(false);
            setMessage({ text: '錢包解鎖成功', type: 'success' });
          } catch (err) {
            setMessage({ text: '解鎖失敗,密碼可能不正確', type: 'error' });
            console.error(err);
          }
        };
      
        // 創(chuàng)建新錢包(BIP-39標準)
        const createWallet = async () => {
          if (!password) {
            setMessage({ text: '請設(shè)置密碼', type: 'error' });
            return;
          }
          
          if (password !== confirmPassword) {
            setMessage({ text: '兩次輸入的密碼不一致', type: 'error' });
            return;
          }
          
          try {
            // 生成符合BIP-39標準的隨機錢包
            const wallet = ethers.Wallet.createRandom();
            const mnemonicPhrase = wallet.mnemonic.phrase;
            
            setPrivateKey(wallet.privateKey);
            setAddress(wallet.address);
            setMnemonic(mnemonicPhrase);
            setConfirmMnemonic('');
            setMnemonicConfirmed(false);
            
            // 顯示助記詞彈窗
            setMnemonicModalVisible(true);
          } catch (err) {
            setMessage({ 
              text: `創(chuàng)建錢包失敗: ${err instanceof Error ? err.message : String(err)}`, 
              type: 'error' 
            });
          }
        };
      
        // 驗證助記詞并加密保存私鑰
        const verifyMnemonic = () => {
          if (!confirmMnemonic.trim()) {
            setMessage({ text: '請輸入助記詞', type: 'error' });
            return;
          }
          
          if (confirmMnemonic.trim() === mnemonic.trim()) {
            // 驗證成功,使用AES加密私鑰并存儲
            const encrypted = CryptoJS.AES.encrypt(privateKey, password).toString();
            localStorage.setItem('encrypted_eth_private_key', encrypted);
            
            setMnemonicConfirmed(true);
            setIsLocked(false);
            setMessage({ text: '助記詞驗證成功,錢包已創(chuàng)建', type: 'success' });
            setSaveReminderVisible(true);
          } else {
            setMessage({ text: '助記詞不匹配,請重新輸入', type: 'error' });
          }
        };
      
        // 導(dǎo)入錢包(私鑰或助記詞)
        const importWallet = () => {
          if (!importValue.trim()) {
            setMessage({ text: '請輸入私鑰或助記詞', type: 'error' });
            return;
          }
          
          if (!password) {
            setMessage({ text: '請設(shè)置密碼', type: 'error' });
            return;
          }
      
          try {
            let wallet: ethers.Wallet;
            
            if (importType === 'privateKey') {
              // 私鑰導(dǎo)入
              if (!importValue.startsWith('0x')) {
                setMessage({ text: '私鑰必須以0x開頭', type: 'error' });
                return;
              }
              wallet = new ethers.Wallet(importValue);
            } else {
              // 助記詞導(dǎo)入 (BIP-39標準)
              const words = importValue.trim().split(/\s+/);
              // BIP-39支持12, 15, 18, 21或24個單詞
              if (![12, 15, 18, 21, 24].includes(words.length)) {
                setMessage({ text: '助記詞必須是12, 15, 18, 21或24個單詞', type: 'error' });
                return;
              }
              wallet = HDNodeWallet.fromPhrase(importValue);
            }
            
            // 加密并保存私鑰
            const encrypted = CryptoJS.AES.encrypt(wallet.privateKey, password).toString();
            localStorage.setItem('encrypted_eth_private_key', encrypted);
            
            setPrivateKey(wallet.privateKey);
            setAddress(wallet.address);
            setIsLocked(false);
            setMessage({ text: '錢包導(dǎo)入成功', type: 'success' });
            setImportValue('');
            setPassword('');
          } catch (err) {
            setMessage({ 
              text: `導(dǎo)入失敗: ${err instanceof Error ? err.message : String(err)}`, 
              type: 'error' 
            });
          }
        };
      
        // 加載ERC-20代幣余額
        const loadTokens = async () => {
          if (!address || !provider) return;
          
          try {
            setLoading(true);
            
            // 先加載ETH余額
            const balance = await provider.getBalance(address);
            setEthBalance(ethers.formatEther(balance));
            
            // 加載常用代幣
            const tokenData = await Promise.all(COMMON_TOKENS.map(async (token) => {
              try {
                const contract = new ethers.Contract(token.address, ERC20_ABI, provider);
                const balance = await contract.balanceOf(address);
                const name = await contract.name();
                const symbol = await contract.symbol();
                const decimals = await contract.decimals();
                
                return {
                  symbol,
                  address: token.address,
                  balance: ethers.formatUnits(balance, decimals),
                  decimals,
                  name
                };
              } catch (err) {
                console.error(`加載${token.symbol}失敗:`, err);
                return null;
              }
            }));
            
            // 過濾掉加載失敗的代幣
            const validTokens = tokenData.filter(Boolean) as Array<{
              symbol: string;
              address: string;
              balance: string;
              decimals: number;
              name: string;
            }>;
            
            // 加載用戶添加的自定義代幣
            const customTokens = JSON.parse(localStorage.getItem('custom_erc20_tokens') || '[]');
            if (customTokens.length > 0) {
              const customTokenData = await Promise.all(customTokens.map(async (token: any) => {
                try {
                  const contract = new ethers.Contract(token.address, ERC20_ABI, provider);
                  const balance = await contract.balanceOf(address);
                  const name = await contract.name();
                  const symbol = await contract.symbol();
                  const decimals = await contract.decimals();
                  
                  return {
                    symbol,
                    address: token.address,
                    balance: ethers.formatUnits(balance, decimals),
                    decimals,
                    name
                  };
                } catch (err) {
                  console.error(`加載自定義代幣失敗:`, err);
                  return null;
                }
              }));
              
              validTokens.push(...customTokenData.filter(Boolean) as any);
            }
            
            setTokens(validTokens);
          } catch (err) {
            setMessage({ text: '加載代幣失敗', type: 'error' });
            console.error(err);
          } finally {
            setLoading(false);
          }
        };
      
        // 添加自定義ERC-20代幣
        const addCustomToken = async () => {
          if (!tokenAddress || !ethers.isAddress(tokenAddress)) {
            setMessage({ text: '請輸入有效的代幣合約地址', type: 'error' });
            return;
          }
          
          if (!provider) return;
          
          try {
            setLoading(true);
            const contract = new ethers.Contract(tokenAddress, ERC20_ABI, provider);
            
            // 驗證ERC-20合約
            const name = await contract.name();
            const symbol = await contract.symbol();
            const decimals = await contract.decimals();
            
            // 檢查余額
            const balance = await contract.balanceOf(address);
            
            // 保存到本地存儲
            const customTokens = JSON.parse(localStorage.getItem('custom_erc20_tokens') || '[]');
            // 避免重復(fù)添加
            if (!customTokens.some((t: any) => t.address.toLowerCase() === tokenAddress.toLowerCase())) {
              customTokens.push({ address: tokenAddress });
              localStorage.setItem('custom_erc20_tokens', JSON.stringify(customTokens));
            }
            
            // 更新代幣列表
            setTokens([...tokens, {
              symbol,
              address: tokenAddress,
              balance: ethers.formatUnits(balance, decimals),
              decimals,
              name
            }]);
            
            setTokenAddress('');
            setAddTokenModalVisible(false);
            setMessage({ text: `成功添加代幣: ${symbol}`, type: 'success' });
          } catch (err) {
            setMessage({ text: '添加代幣失敗,可能不是有效的ERC-20合約', type: 'error' });
            console.error(err);
          } finally {
            setLoading(false);
          }
        };
      
        // 查詢交易歷史
        const fetchTransactionHistory = async () => {
          if (!address) return;
          
          try {
            setTransactionsLoading(true);
            // 使用Etherscan API查詢交易歷史
            const apiKey = 'your-etherscan-api-key'; // 替換為你的Etherscan API密鑰
            const chainId = (await provider?.getNetwork())?.chainId || 11155111; // Sepolia測試網(wǎng)
            
            // 構(gòu)建API URL
            let baseUrl = chainId === 1 
              ? 'https://api.etherscan.io/api' 
              : 'https://api-sepolia.etherscan.io/api';
            
            // 查詢ETH交易
            const ethTxUrl = new URL(baseUrl);
            ethTxUrl.searchParams.append('module', 'account');
            ethTxUrl.searchParams.append('action', 'txlist');
            ethTxUrl.searchParams.append('address', address);
            ethTxUrl.searchParams.append('startblock', '0');
            ethTxUrl.searchParams.append('endblock', '99999999');
            ethTxUrl.searchParams.append('page', '1');
            ethTxUrl.searchParams.append('offset', '20');
            ethTxUrl.searchParams.append('sort', 'desc');
            ethTxUrl.searchParams.append('apikey', apiKey);
            
            const ethTxResponse = await axios.get(ethTxUrl.toString());
            
            if (ethTxResponse.data.status !== '1') {
              setMessage({ text: '查詢ETH交易歷史失敗', type: 'error' });
              setTransactionsLoading(false);
              return;
            }
            
            // 格式化ETH交易
            const ethTransactions = ethTxResponse.data.result.map((tx: any) => ({
              hash: tx.hash,
              from: tx.from,
              to: tx.to || '合約創(chuàng)建',
              value: ethers.formatEther(tx.value),
              timeStamp: new Date(parseInt(tx.timeStamp) * 1000).toLocaleString(),
              confirmations: tx.confirmations
            }));
            
            // 查詢ERC-20代幣交易
            const tokenTxUrl = new URL(baseUrl);
            tokenTxUrl.searchParams.append('module', 'account');
            tokenTxUrl.searchParams.append('action', 'tokentx');
            tokenTxUrl.searchParams.append('address', address);
            tokenTxUrl.searchParams.append('startblock', '0');
            tokenTxUrl.searchParams.append('endblock', '99999999');
            tokenTxUrl.searchParams.append('page', '1');
            tokenTxUrl.searchParams.append('offset', '20');
            tokenTxUrl.searchParams.append('sort', 'desc');
            tokenTxUrl.searchParams.append('apikey', apiKey);
            
            const tokenTxResponse = await axios.get(tokenTxUrl.toString());
            
            // 格式化代幣交易
            let tokenTransactions: any[] = [];
            if (tokenTxResponse.data.status === '1') {
              tokenTransactions = tokenTxResponse.data.result.map((tx: any) => {
                // 查找對應(yīng)的代幣信息
                const token = tokens.find(t => t.address.toLowerCase() === tx.contractAddress.toLowerCase());
                const decimals = token?.decimals || 18;
                
                return {
                  hash: tx.hash,
                  from: tx.from,
                  to: tx.to,
                  value: ethers.formatUnits(tx.value, decimals),
                  tokenSymbol: tx.tokenSymbol,
                  timeStamp: new Date(parseInt(tx.timeStamp) * 1000).toLocaleString(),
                  confirmations: tx.confirmations
                };
              });
            }
            
            // 合并并按時間排序所有交易
            const allTransactions = [...ethTransactions, ...tokenTransactions]
              .sort((a, b) => new Date(b.timeStamp).getTime() - new Date(a.timeStamp).getTime());
              
            setTransactions(allTransactions);
          } catch (err) {
            setMessage({ text: '查詢交易歷史失敗', type: 'error' });
            console.error(err);
          } finally {
            setTransactionsLoading(false);
          }
        };
      
        // 發(fā)送交易 (ETH或ERC-20代幣)
        const sendTransaction = async () => {
          if (!privateKey || !recipient || !amount || !provider || isLocked) {
            setMessage({ text: '請?zhí)顚懲暾畔⒒蚪怄i錢包', type: 'error' });
            return;
          }
      
          // 地址校驗
          if (!ethers.isAddress(recipient)) {
            setMessage({ text: '無效的接收地址', type: 'error' });
            return;
          }
      
          // 金額校驗
          const numAmount = parseFloat(amount);
          if (isNaN(numAmount) || numAmount <= 0 || numAmount > 10000) {
            setMessage({ text: '請輸入有效的金額(正數(shù)且不超過10000)', type: 'error' });
            return;
          }
      
          setLoading(true);
          try {
            const wallet = new ethers.Wallet(privateKey, provider);
            
            if (selectedToken === 'ETH') {
              // 發(fā)送ETH
              // 余額檢查
              if (numAmount > parseFloat(ethBalance)) {
                setMessage({ text: 'ETH余額不足', type: 'error' });
                setLoading(false);
                return;
              }
              
              const tx = {
                to: recipient,
                value: ethers.parseEther(amount),
                gasLimit: 21000
              };
      
              const txResponse = await wallet.sendTransaction(tx);
              setMessage({ text: `ETH交易已發(fā)送,哈希: ${txResponse.hash}`, type: 'success' });
            } else {
              // 發(fā)送ERC-20代幣
              const token = tokens.find(t => t.symbol === selectedToken);
              if (!token) {
                setMessage({ text: '未找到選中的代幣', type: 'error' });
                setLoading(false);
                return;
              }
              
              // 余額檢查
              if (numAmount > parseFloat(token.balance)) {
                setMessage({ text: `${token.symbol}余額不足`, type: 'error' });
                setLoading(false);
                return;
              }
              
              const contract = new ethers.Contract(token.address, ERC20_ABI, wallet);
              const amountWei = ethers.parseUnits(amount, token.decimals);
              
              const txResponse = await contract.transfer(recipient, amountWei);
              setMessage({ text: `${token.symbol}交易已發(fā)送,哈希: ${txResponse.hash}`, type: 'success' });
            }
            
            // 重置表單
            setRecipient('');
            setAmount('0.01');
            
            // 延遲刷新數(shù)據(jù),等待鏈上確認
            setTimeout(() => {
              loadTokens();
              fetchTransactionHistory();
            }, 10000);
          } catch (err) {
            setMessage({ 
              text: `交易失敗: ${err instanceof Error ? err.message : String(err)}`, 
              type: 'error' 
            });
            console.error(err);
          } finally {
            setLoading(false);
          }
        };
      
        // 渲染錢包鎖定狀態(tài)
        if (isLocked && localStorage.getItem('encrypted_eth_private_key')) {
          return (
            <Container>
              <h2 style={{ color: '#2c3e50', textAlign: 'center' }}>ETH錢包</h2>
              
              <Card title="解鎖錢包">
                <p>請輸入密碼解鎖你的錢包</p>
                <Input
                  type="password"
                  value={unlockPassword}
                  onChange={(e) => setUnlockPassword(e.target.value)}
                  placeholder="錢包密碼"
                />
                <div style={{ marginTop: '15px' }}>
                  <Button onClick={unlockWallet} primary>
                    解鎖錢包
                  </Button>
                </div>
              </Card>
              
              {message && <Alert message={message.text} type={message.type} />}
            </Container>
          );
        }
      
        // 渲染主界面
        return (
          <Container>
            <h2 style={{ color: '#2c3e50', textAlign: 'center' }}>ETH錢包</h2>
            
            {/* 錢包未創(chuàng)建時顯示創(chuàng)建/導(dǎo)入選項 */}
            {!address && !isLocked && (
              <>
                {/* 創(chuàng)建錢包 */}
                <Card title="創(chuàng)建新錢包">
                  <p>創(chuàng)建新錢包將生成符合BIP-39標準的12個單詞助記詞,請務(wù)必妥善保管。</p>
                  
                  <div style={{ marginBottom: '10px' }}>
                    <Input
                      type="password"
                      value={password}
                      onChange={(e) => setPassword(e.target.value)}
                      placeholder="設(shè)置錢包密碼(用于加密私鑰)"
                    />
                  </div>
                  
                  <div style={{ marginBottom: '10px' }}>
                    <Input
                      type="password"
                      value={confirmPassword}
                      onChange={(e) => setConfirmPassword(e.target.value)}
                      placeholder="確認密碼"
                    />
                  </div>
                  
                  <Button onClick={createWallet} primary>
                    創(chuàng)建新錢包
                  </Button>
                  
                  <Alert message="密碼用于加密存儲私鑰,請牢記你的密碼和助記詞!" type="info" />
                </Card>
                
                {/* 導(dǎo)入錢包 */}
                <Card title="導(dǎo)入已有錢包">
                  <div style={{ marginBottom: '10px' }}>
                    <label style={{ marginRight: '15px' }}>
                      <input
                        type="radio"
                        checked={importType === 'privateKey'}
                        onChange={() => setImportType('privateKey')}
                      />
                      私鑰導(dǎo)入
                    </label>
                    <label>
                      <input
                        type="radio"
                        checked={importType === 'mnemonic'}
                        onChange={() => setImportType('mnemonic')}
                      />
                      助記詞導(dǎo)入 (BIP-39)
                    </label>
                  </div>
                  
                  <div style={{ marginBottom: '10px' }}>
                    <Input
                      value={importValue}
                      onChange={(e) => setImportValue(e.target.value)}
                      placeholder={importType === 'privateKey' 
                        ? '輸入私鑰(以0x開頭)' 
                        : '輸入12, 15, 18, 21或24個單詞的助記詞,空格分隔'
                      }
                      multiline={importType === 'mnemonic'}
                    />
                  </div>
                  
                  <div style={{ marginBottom: '10px' }}>
                    <Input
                      type="password"
                      value={password}
                      onChange={(e) => setPassword(e.target.value)}
                      placeholder="設(shè)置錢包密碼(用于加密私鑰)"
                    />
                  </div>
                  
                  <Button onClick={importWallet} primary>
                    導(dǎo)入錢包
                  </Button>
                </Card>
              </>
            )}
            
            {/* 錢包已創(chuàng)建時顯示賬戶信息 */}
            {address && !isLocked && (
              <>
                {/* 賬戶信息 */}
                <Card title="賬戶信息">
                  <div style={{ marginBottom: '10px', wordBreak: 'break-all' }}>
                    <strong>地址:</strong> {address}
                  </div>
                  
                  <div style={{ marginBottom: '15px', wordBreak: 'break-all' }}>
                    <strong>私鑰:</strong> 
                    {showPrivateKey ? privateKey : '********************'}
                    <Button 
                      onClick={() => setShowPrivateKey(!showPrivateKey)}
                      style={{ padding: '2px 8px', fontSize: '12px', marginLeft: '10px' }}
                    >
                      {showPrivateKey ? '隱藏' : '顯示'}
                    </Button>
                  </div>
                  
                  <div>
                    <Button onClick={loadTokens} disabled={loading}>
                      {loading ? '刷新中...' : '刷新資產(chǎn)'}
                    </Button>
                    <Button onClick={() => setTransactionHistoryVisible(true)}>
                      查看交易歷史
                    </Button>
                  </div>
                </Card>
                
                {/* 資產(chǎn)列表 */}
                <Card title="資產(chǎn)列表">
                  <div style={{ marginBottom: '10px', padding: '10px', backgroundColor: '#f9f9f9', borderRadius: '4px' }}>
                    <strong>ETH</strong>: {ethBalance}
                  </div>
                  
                  {tokens.length > 0 ? (
                    tokens.map((token) => (
                      <div 
                        key={token.address} 
                        style={{ 
                          marginBottom: '10px', 
                          padding: '10px', 
                          backgroundColor: '#f9f9f9', 
                          borderRadius: '4px' 
                        }}
                      >
                        <strong>{token.symbol}</strong> ({token.name}): {token.balance}
                      </div>
                    ))
                  ) : (
                    <Alert message="沒有檢測到ERC-20代幣" type="info" />
                  )}
                  
                  <Button onClick={() => setAddTokenModalVisible(true)}>
                    添加ERC-20代幣
                  </Button>
                </Card>
                
                {/* 發(fā)送交易 */}
                <Card title="發(fā)送資產(chǎn)">
                  <div style={{ marginBottom: '15px' }}>
                    <label style={{ display: 'block', marginBottom: '5px' }}>選擇資產(chǎn):</label>
                    <select
                      value={selectedToken}
                      onChange={(e) => setSelectedToken(e.target.value)}
                      style={{ 
                        padding: '10px', 
                        width: '100%',
                        borderRadius: '4px',
                        border: '1px solid #ddd',
                        fontSize: '14px'
                      }}
                    >
                      <option value="ETH">ETH</option>
                      {tokens.map((token) => (
                        <option key={token.address} value={token.symbol}>
                          {token.symbol} ({token.name})
                        </option>
                      ))}
                    </select>
                  </div>
                  
                  <div style={{ marginBottom: '15px' }}>
                    <label style={{ display: 'block', marginBottom: '5px' }}>接收地址:</label>
                    <Input
                      value={recipient}
                      onChange={(e) => setRecipient(e.target.value)}
                      placeholder="0x..."
                    />
                    {recipient && !ethers.isAddress(recipient) && (
                      <Alert message="地址格式無效" type="error" />
                    )}
                  </div>
                  
                  <div style={{ marginBottom: '15px' }}>
                    <label style={{ display: 'block', marginBottom: '5px' }}>
                      金額 ({selectedToken}):
                    </label>
                    <Input
                      value={amount}
                      onChange={(e) => setAmount(e.target.value)}
                      placeholder="0.01"
                    />
                    {amount && isNaN(parseFloat(amount)) && (
                      <Alert message="請輸入有效的金額" type="error" />
                    )}
                  </div>
                  
                  <Button onClick={sendTransaction} disabled={loading} primary>
                    {loading ? '處理中...' : `發(fā)送 ${selectedToken}`}
                  </Button>
                </Card>
              </>
            )}
            
            {/* 消息提示 */}
            {message && <Alert message={message.text} type={message.type} />}
            
            {/* 助記詞彈窗 */}
            <Modal
              visible={mnemonicModalVisible}
              onClose={() => !mnemonicConfirmed && window.confirm('助記詞未備份,確定要關(guān)閉嗎?') && setMnemonicModalVisible(false)}
              title="你的助記詞 - 請務(wù)必備份!"
              footer={
                mnemonicConfirmed ? (
                  <Button onClick={() => setMnemonicModalVisible(false)}>完成</Button>
                ) : (
                  <Button onClick={verifyMnemonic} primary>驗證助記詞</Button>
                )
              }
            >
              <Alert message="這是你錢包的唯一備份,請在安全的地方離線記錄下來!切勿截圖或在網(wǎng)上分享!" type="error" />
              
              <div style={{ 
                backgroundColor: '#f5f5f5', 
                padding: '15px', 
                borderRadius: '4px', 
                margin: '15px 0',
                wordBreak: 'break-all',
                lineHeight: '1.6'
              }}>
                {mnemonic.split(' ').map((word, i) => (
                  <span key={i} style={{ 
                    display: 'inline-block', 
                    width: '80px', 
                    padding: '5px',
                    margin: '2px',
                    backgroundColor: '#eee',
                    borderRadius: '2px',
                    textAlign: 'center'
                  }}>
                    {i+1}. {word}
                  </span>
                ))}
              </div>
              
              {!mnemonicConfirmed && (
                <>
                  <p>請重新輸入上面的助記詞以確認備份:</p>
                  <Input
                    value={confirmMnemonic}
                    onChange={(e) => setConfirmMnemonic(e.target.value)}
                    placeholder="輸入完整的助記詞,用空格分隔"
                    multiline
                  />
                </>
              )}
            </Modal>
            
            {/* 保存提醒彈窗 */}
            <Modal
              visible={saveReminderVisible}
              onClose={() => setSaveReminderVisible(false)}
              title="重要提醒"
              footer={
                <Button onClick={() => setSaveReminderVisible(false)} primary>
                  我已了解并保存
                </Button>
              }
            >
              <div style={{ textAlign: 'center', padding: '20px 0' }}>
                <div style={{ fontSize: '48px', marginBottom: '20px' }}>??</div>
                <h4 style={{ color: '#b71c1c', marginBottom: '15px' }}>請務(wù)必牢記你的助記詞!</h4>
                <p style={{ marginBottom: '10px', textAlign: 'left' }}>1. 助記詞是恢復(fù)錢包的唯一方式</p>
                <p style={{ marginBottom: '10px', textAlign: 'left' }}>2. 丟失助記詞將導(dǎo)致資產(chǎn)永久丟失</p>
                <p style={{ marginBottom: '10px', textAlign: 'left' }}>3. 不要向任何人透露你的助記詞</p>
                <p style={{ textAlign: 'left' }}>4. 建議手寫備份并保存在安全的地方</p>
              </div>
            </Modal>
            
            {/* 添加代幣彈窗 */}
            <Modal
              visible={addTokenModalVisible}
              onClose={() => setAddTokenModalVisible(false)}
              title="添加ERC-20代幣"
              footer={
                <Button onClick={addCustomToken} primary>
                  添加代幣
                </Button>
              }
            >
              <p>請輸入ERC-20代幣合約地址:</p>
              <Input
                value={tokenAddress}
                onChange={(e) => setTokenAddress(e.target.value)}
                placeholder="0x..."
              />
              <Alert message="請確保合約地址正確,添加錯誤的合約可能導(dǎo)致資產(chǎn)顯示異常" type="info" />
              <p style={{ fontSize: '12px', color: '#777' }}>
                提示:合約地址通常可以在代幣的官方網(wǎng)站或區(qū)塊瀏覽器上找到
              </p>
            </Modal>
            
            {/* 交易歷史彈窗 */}
            <Modal
              visible={transactionHistoryVisible}
              onClose={() => setTransactionHistoryVisible(false)}
              title="交易歷史"
              footer={
                <Button onClick={fetchTransactionHistory} disabled={transactionsLoading}>
                  {transactionsLoading ? '刷新中...' : '刷新'}
                </Button>
              }
            >
              {transactionsLoading ? (
                <div style={{ textAlign: 'center', padding: '20px' }}>加載交易歷史中...</div>
              ) : transactions.length === 0 ? (
                <div style={{ textAlign: 'center', padding: '20px' }}>沒有找到交易歷史</div>
              ) : (
                <div style={{ maxHeight: '500px', overflowY: 'auto' }}>
                  {transactions.map((tx, index) => (
                    <div key={index} style={{ 
                      padding: '12px', 
                      borderBottom: '1px solid #eee',
                      marginBottom: '10px'
                    }}>
                      <div style={{ marginBottom: '5px', fontSize: '13px' }}>
                        <strong>哈希:</strong> {tx.hash.substring(0, 10)}...{tx.hash.substring(tx.hash.length - 10)}
                      </div>
                      <div style={{ marginBottom: '5px', fontSize: '13px' }}>
                        <strong>從:</strong> {tx.from}
                      </div>
                      <div style={{ marginBottom: '5px', fontSize: '13px' }}>
                        <strong>到:</strong> {tx.to}
                      </div>
                      <div style={{ marginBottom: '5px', fontSize: '13px' }}>
                        <strong>金額:</strong> {tx.value} {tx.tokenSymbol || 'ETH'}
                      </div>
                      <div style={{ marginBottom: '5px', fontSize: '13px' }}>
                        <strong>時間:</strong> {tx.timeStamp}
                      </div>
                      <div style={{ fontSize: '13px' }}>
                        <strong>確認數(shù):</strong> {tx.confirmations}
                      </div>
                    </div>
                  ))}
                </div>
              )}
            </Modal>
          </Container>
        );
      };
      
      export default Wallet;
      
      

      運行入口

      import React from 'react';
      import Wallet from './Wallet';
      
      function App() {
        return (
          <div className="App">
            <Wallet />
          </div>
        );
      }
      
      export default App;
      
      

      運行步驟

      1. 安裝依賴:
      npm install ethers crypto-js axios
      
      1. 替換代碼中的API密鑰:

        • Infura API密鑰:用于連接以太坊網(wǎng)絡(luò)
        • Etherscan API密鑰:用于查詢交易歷史
      2. 啟動應(yīng)用:

      npm start
      
      1. 在瀏覽器中訪問http://localhost:3000

      5.效果圖

      image

      安全特性

      • 私鑰全程在客戶端處理,不經(jīng)過網(wǎng)絡(luò)傳輸
      • 助記詞強制備份驗證,降低資產(chǎn)丟失風(fēng)險
      • 交易前進行余額檢查和地址驗證
      • 密碼強度驗證和加密存儲保護私鑰
      posted @ 2025-08-04 10:08  ffffox  閱讀(16)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 厨房与子乱在线观看| 国产日韩入口一区二区| 久久中文字幕日韩无码视频| 最新国产精品亚洲| 丰满少妇在线观看网站| 不卡一区二区国产精品| 国产果冻豆传媒麻婆| 亚洲高清有码中文字| 超碰成人人人做人人爽| 久久99国产精品久久99| 一区二区三区在线 | 欧洲 | 国产精品国产三级国产午| 久操热在线视频免费观看| 国产不卡精品视频男人的天堂| аⅴ天堂中文在线网| 九色综合国产一区二区三区| 国产日韩精品欧美一区灰 | 大陆精大陆国产国语精品| 人妻色综合网站| 日韩av一区二区精品不卡| 白丝乳交内射一二三区| 无码人妻精品一区二区三区东京热| av中文字幕一区人妻| 午夜福利看片在线观看| 国产精品美女乱子伦高| 视频一区二区三区四区不卡| 国内精品久久久久影院网站| 天堂中文在线资源| 久久自己只精产国品| 丰满少妇被猛烈进出69影院| 日本丰满护士bbw| 亚洲第一区二区快射影院| 亚洲AV午夜电影在线观看| 国产无遮挡又黄又爽免费网站| 亚洲高清国产拍精品熟女| 国产精品久久国产精麻豆99网站| 18禁免费无码无遮挡不卡网站| 欧美亚洲日本国产其他| 韩国无码AV片午夜福利| 一本一本久久aa综合精品| 加勒比亚洲天堂午夜中文|