import { useState, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import styles from './Sign.module.css'; interface ShareItem { id: string; walletName: string; publicKey: string; threshold: { t: number; n: number }; createdAt: string; metadata: { participants: Array<{ partyId: string; name: string }>; }; } interface SigningSession { sessionId: string; walletName: string; messageHash: string; threshold: { t: number; n: number }; currentParticipants: number; initiator: string; } export default function Sign() { const { sessionId: urlSessionId } = useParams<{ sessionId?: string }>(); const navigate = useNavigate(); const [step, setStep] = useState<'select' | 'join' | 'signing' | 'completed' | 'failed'>('select'); const [shares, setShares] = useState([]); const [selectedShare, setSelectedShare] = useState(null); const [sessionCode, setSessionCode] = useState(urlSessionId || ''); const [password, setPassword] = useState(''); const [signingSession, setSigningSession] = useState(null); const [signature, setSignature] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const [progress, setProgress] = useState({ current: 0, total: 0 }); // 加载本地保存的 shares useEffect(() => { loadShares(); }, []); // 如果 URL 中有 sessionId,自动进入加入流程 useEffect(() => { if (urlSessionId && shares.length > 0) { setStep('join'); } }, [urlSessionId, shares]); const loadShares = async () => { try { if (window.electronAPI) { const result = await window.electronAPI.storage.listShares(); if (result.success && result.data) { setShares(result.data as ShareItem[]); } } } catch (error) { console.error('Failed to load shares:', error); } }; const handleImportShare = async () => { try { if (window.electronAPI) { const filePath = await window.electronAPI.dialog.selectFile([ { name: 'Share Backup', extensions: ['dat', 'json'] } ]); if (filePath) { const importPassword = window.prompt('请输入备份文件的密码:'); if (!importPassword) return; const result = await window.electronAPI.storage.importShare(filePath, importPassword); if (result.success && result.share) { await loadShares(); setSelectedShare(result.share as ShareItem); } else { setError(result.error || '导入失败'); } } } } catch (err) { setError('导入失败: ' + (err as Error).message); } }; const handleValidateSession = async () => { if (!sessionCode.trim()) { setError('请输入签名会话码'); return; } setIsLoading(true); setError(null); try { const result = await window.electronAPI.grpc.validateSigningSession(sessionCode); if (result.success && result.session) { setSigningSession(result.session); setStep('join'); } else { setError(result.error || '无效的签名会话码'); } } catch (err) { setError('验证失败: ' + (err as Error).message); } finally { setIsLoading(false); } }; const handleJoinSigning = async () => { if (!selectedShare) { setError('请选择要使用的钱包份额'); return; } // 密码是可选的,如果创建时没有设置密码,这里也不需要输入 setIsLoading(true); setError(null); setStep('signing'); try { // 获取解密后的 share const shareData = await window.electronAPI.storage.getShare(selectedShare.id, password); if (!shareData) { setError('密码错误或份额数据损坏'); setStep('join'); setIsLoading(false); return; } // 加入签名会话 const result = await window.electronAPI.grpc.joinSigningSession({ sessionId: signingSession!.sessionId, shareId: selectedShare.id, password: password, }); if (result.success) { // 订阅签名进度 window.electronAPI.grpc.subscribeSigningProgress( signingSession!.sessionId, (event) => { if (event.type === 'progress') { setProgress({ current: event.current || 0, total: event.total || 0 }); } else if (event.type === 'completed') { setSignature(event.signature || ''); setStep('completed'); setIsLoading(false); } else if (event.type === 'failed') { setError(event.error || '签名失败'); setStep('failed'); setIsLoading(false); } } ); } else { setError(result.error || '加入签名会话失败'); setStep('join'); setIsLoading(false); } } catch (err) { setError('签名过程出错: ' + (err as Error).message); setStep('failed'); setIsLoading(false); } }; const handleCopySignature = async () => { if (signature) { try { await navigator.clipboard.writeText(signature); } catch (err) { console.error('Failed to copy:', err); } } }; const handlePaste = async () => { try { const text = await navigator.clipboard.readText(); setSessionCode(text.trim()); } catch (err) { console.error('Failed to read clipboard:', err); } }; return (

参与签名

使用您的钱包份额参与多方签名

{step === 'select' && (
{/* 选择本地 Share */}

选择钱包份额

{shares.length === 0 ? (

暂无本地保存的钱包份额

) : (
{shares.map((share) => (
setSelectedShare(share)} >
{share.walletName} {share.threshold.t}-of-{share.threshold.n}
{share.publicKey.slice(0, 8)}...{share.publicKey.slice(-8)}
))}
)}
{/* 输入签名会话码 */}
setSessionCode(e.target.value)} placeholder="粘贴签名会话码或扫描二维码" className={styles.input} disabled={isLoading} />
{error &&
{error}
}
)} {step === 'join' && signingSession && (
{/* 显示签名会话信息 */}

签名会话信息

钱包名称 {signingSession.walletName}
发起者 {signingSession.initiator}
阈值 {signingSession.threshold.t}-of-{signingSession.threshold.n}
当前参与者 {signingSession.currentParticipants} / {signingSession.threshold.t}
待签名消息哈希 {signingSession.messageHash}
{/* 已选择的 Share */}
使用的钱包份额
{selectedShare?.walletName}
{/* 输入密码 */}
setPassword(e.target.value)} placeholder="输入您的份额解锁密码" className={styles.input} disabled={isLoading} />
{error &&
{error}
}
)} {step === 'signing' && (

签名进行中

正在与其他参与方协同完成签名...

{progress.total > 0 && (
轮次 {progress.current} / {progress.total}
)}
)} {step === 'completed' && signature && (

签名完成

{signature}
)} {step === 'failed' && (
!

签名失败

{error}

)}
); }