import { useState, useEffect, useCallback } from 'react'; import { useNavigate } from 'react-router-dom'; import { QRCodeSVG } from 'qrcode.react'; import styles from './Home.module.css'; import { deriveEvmAddress, formatAddress, getKavaExplorerUrl } from '../utils/address'; interface ShareItem { id: string; walletName: string; publicKey: string; threshold: { t: number; n: number }; createdAt: string; lastUsedAt?: string; participants?: Array<{ partyId: string; name: string }>; } interface ShareWithAddress extends ShareItem { evmAddress?: string; kavaBalance?: string; balanceLoading?: boolean; } // Kava Testnet EVM RPC endpoint const KAVA_TESTNET_RPC = 'https://evm.testnet.kava.io'; /** * 获取 KAVA 代币余额 * @param address EVM 地址 * @returns 余额字符串 (格式化后的 KAVA 数量) */ async function fetchKavaBalance(address: string): Promise { try { const response = await fetch(KAVA_TESTNET_RPC, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_getBalance', params: [address, 'latest'], id: 1, }), }); const data = await response.json(); if (data.result) { // 将 wei 转换为 KAVA (18 位小数) const balanceWei = BigInt(data.result); const balanceKava = Number(balanceWei) / 1e18; // 格式化显示,最多 6 位小数 return balanceKava.toFixed(balanceKava < 0.000001 && balanceKava > 0 ? 8 : 6).replace(/\.?0+$/, '') || '0'; } return '0'; } catch (error) { console.error('Failed to fetch KAVA balance:', error); return '0'; } } export default function Home() { const navigate = useNavigate(); const [shares, setShares] = useState([]); const [loading, setLoading] = useState(true); const [selectedShare, setSelectedShare] = useState(null); const [showQrModal, setShowQrModal] = useState(false); const deriveAddresses = useCallback(async (shareList: ShareItem[]): Promise => { const sharesWithAddresses: ShareWithAddress[] = []; for (const share of shareList) { try { const evmAddress = await deriveEvmAddress(share.publicKey); sharesWithAddresses.push({ ...share, evmAddress, balanceLoading: true }); } catch (error) { console.warn(`Failed to derive address for share ${share.id}:`, error); sharesWithAddresses.push({ ...share }); } } return sharesWithAddresses; }, []); // 单独获取所有钱包的余额 const fetchAllBalances = useCallback(async (sharesWithAddrs: ShareWithAddress[]) => { const updatedShares = await Promise.all( sharesWithAddrs.map(async (share) => { if (share.evmAddress) { const kavaBalance = await fetchKavaBalance(share.evmAddress); return { ...share, kavaBalance, balanceLoading: false }; } return { ...share, balanceLoading: false }; }) ); setShares(updatedShares); }, []); useEffect(() => { loadShares(); }, []); const loadShares = async () => { try { let shareList: ShareItem[] = []; // 检测是否在 Electron 环境中 if (window.electronAPI) { const result = await window.electronAPI.storage.listShares(); if (result.success && result.data) { shareList = result.data as ShareItem[]; } } else { // 浏览器环境,使用 localStorage 或 API const stored = localStorage.getItem('shares'); if (stored) { shareList = JSON.parse(stored); } } // 为每个 share 派生 EVM 地址 const sharesWithAddresses = await deriveAddresses(shareList); setShares(sharesWithAddresses); // 异步获取所有余额 (不阻塞 UI) fetchAllBalances(sharesWithAddresses); } catch (error) { console.error('Failed to load shares:', error); } finally { setLoading(false); } }; const handleExport = async (id: string) => { const password = window.prompt('请输入密码以导出备份文件:'); if (!password) return; try { if (window.electronAPI) { // 先让用户选择保存位置 const savePath = await window.electronAPI.dialog.saveFile( `share-backup-${id.slice(0, 8)}.dat`, [{ name: 'Share Backup', extensions: ['dat'] }] ); if (!savePath) return; const result = await window.electronAPI.storage.exportShare(id, password); if (result.success && result.data) { // 使用 file:write IPC 写入文件到用户选择的路径 const dataArray = result.data instanceof ArrayBuffer ? new Uint8Array(result.data) : result.data; const writeResult = await window.electronAPI.file.write(savePath, dataArray); if (writeResult.success) { alert('备份文件导出成功!'); } else { alert('写入文件失败: ' + (writeResult.error || '未知错误')); } } else { alert('导出失败: ' + (result.error || '未知错误')); } } } catch (error) { alert('导出失败: ' + (error as Error).message); } }; const handleDelete = async (id: string) => { const confirmed = window.confirm('确定要删除这个钱包吗?删除后无法恢复,请确保已备份。'); if (!confirmed) return; try { if (window.electronAPI) { const result = await window.electronAPI.storage.deleteShare(id); if (result.success) { setShares(shares.filter((s) => s.id !== id)); } else { alert('删除失败: ' + (result.error || '未知错误')); } } } catch (error) { alert('删除失败: ' + (error as Error).message); } }; const handleShowQr = (share: ShareWithAddress) => { setSelectedShare(share); setShowQrModal(true); }; const handleCopyAddress = (address: string) => { navigator.clipboard.writeText(address); alert('地址已复制到剪贴板'); }; const formatDate = (dateStr: string) => { return new Date(dateStr).toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }); }; if (loading) { return (

加载中...

); } return (

我的共管钱包

管理您参与的多方共管钱包 (3-of-5 混合托管)

{shares.length === 0 ? (
🔐

暂无共管钱包

您可以创建新的共管钱包,或加入他人发起的钱包创建

) : (
{shares.map((share) => (

{share.walletName}

{share.threshold.t}-of-{share.threshold.n}
{/* 地址区域 - 可点击显示二维码 */} {share.evmAddress && (
handleShowQr(share)} >
Kava EVM 地址 {formatAddress(share.evmAddress)} 点击查看完整二维码
)} {/* KAVA 余额显示 */} {share.evmAddress && (
KAVA 余额 {share.balanceLoading ? ( 加载中... ) : ( <>{share.kavaBalance || '0'} KAVA )}
)}
参与方 {(share.participants || []).length || share.threshold.n} 人
创建时间 {formatDate(share.createdAt)}
{share.lastUsedAt && (
上次使用 {formatDate(share.lastUsedAt)}
)}
))}
)} {/* 二维码弹窗 */} {showQrModal && selectedShare && (
setShowQrModal(false)}>
e.stopPropagation()}>

{selectedShare.walletName}

Kava EVM 地址 {selectedShare.evmAddress}
在区块浏览器查看
)}
); }