import { app, BrowserWindow, ipcMain, shell, dialog } from 'electron'; import * as path from 'path'; import * as fs from 'fs'; import * as crypto from 'crypto'; import express from 'express'; import { GrpcClient } from './modules/grpc-client'; import { DatabaseManager } from './modules/database'; import { addressDerivationService, CHAIN_CONFIGS } from './modules/address-derivation'; import { KavaTxService, KAVA_MAINNET_TX_CONFIG } from './modules/kava-tx-service'; import { AccountClient } from './modules/account-client'; // 内置 HTTP 服务器端口 const HTTP_PORT = 3456; let mainWindow: BrowserWindow | null = null; let grpcClient: GrpcClient | null = null; let database: DatabaseManager | null = null; let httpServer: ReturnType | null = null; let kavaTxService: KavaTxService | null = null; let accountClient: AccountClient | null = null; // 创建主窗口 function createWindow() { mainWindow = new BrowserWindow({ width: 1200, height: 800, minWidth: 800, minHeight: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, }, titleBarStyle: 'hiddenInset', show: false, }); // 开发模式下加载 Vite 开发服务器 if (process.env.NODE_ENV === 'development') { mainWindow.loadURL('http://localhost:5173'); mainWindow.webContents.openDevTools(); } else { // 生产模式下加载打包后的文件 mainWindow.loadFile(path.join(__dirname, '../dist/index.html')); } mainWindow.once('ready-to-show', () => { mainWindow?.show(); }); mainWindow.on('closed', () => { mainWindow = null; }); // 处理外部链接 mainWindow.webContents.setWindowOpenHandler(({ url }) => { shell.openExternal(url); return { action: 'deny' }; }); } // 启动内置 HTTP 服务器 function startHttpServer() { const expressApp = express(); expressApp.use(express.json()); expressApp.use(express.static(path.join(__dirname, '../dist'))); // API 路由 expressApp.get('/api/status', (_req, res) => { res.json({ connected: grpcClient?.isConnected() ?? false, partyId: grpcClient?.getPartyId() ?? null, }); }); // 所有其他路由返回 index.html (SPA) expressApp.get('*', (_req, res) => { res.sendFile(path.join(__dirname, '../dist/index.html')); }); httpServer = expressApp.listen(HTTP_PORT, '127.0.0.1', () => { console.log(`HTTP server running at http://127.0.0.1:${HTTP_PORT}`); }); } // 生成或获取持久化的 partyId function getOrCreatePartyId(db: DatabaseManager): string { const settings = db.getAllSettings(); let partyId = settings['party_id']; if (!partyId) { // 生成一个新的 UUID 作为 partyId partyId = crypto.randomUUID(); db.setSetting('party_id', partyId); console.log('Generated new partyId:', partyId); } return partyId; } // 初始化服务 async function initServices() { // 初始化数据库 (必须首先初始化) database = new DatabaseManager(); // 初始化 gRPC 客户端 grpcClient = new GrpcClient(); // 初始化 Kava 交易服务 kavaTxService = new KavaTxService(KAVA_MAINNET_TX_CONFIG); // 初始化 Account 服务 HTTP 客户端 // 从数据库读取 Account 服务 URL,默认使用生产环境地址 const settings = database.getAllSettings(); const accountServiceUrl = settings['account_service_url'] || 'https://api.szaiai.com'; accountClient = new AccountClient(accountServiceUrl); // 设置 IPC 处理器 setupIpcHandlers(); // 启动时自动连接并注册到 Message Router (非阻塞) connectAndRegisterToMessageRouter().catch((err) => { console.error('Background connection failed:', err.message); }); } // 连接并注册到 Message Router async function connectAndRegisterToMessageRouter() { if (!grpcClient || !database) { console.error('gRPC client or database not initialized'); return; } try { const settings = database.getAllSettings(); const routerUrl = settings['message_router_url'] || 'mpc-grpc.szaiai.com:443'; const partyId = getOrCreatePartyId(database); const role = 'temporary'; // Service-Party-App 使用 temporary 角色 console.log(`Connecting to Message Router: ${routerUrl}...`); await grpcClient.connect(routerUrl); console.log('Connected to Message Router'); console.log(`Registering as party: ${partyId} (role: ${role})...`); await grpcClient.registerParty(partyId, role); console.log('Registered to Message Router successfully'); // 订阅会话事件 grpcClient.subscribeSessionEvents(partyId); console.log('Subscribed to session events'); } catch (error) { console.error('Failed to connect/register to Message Router:', (error as Error).message); // 不抛出错误,允许应用继续启动,用户可以稍后手动重试 } } // 设置 IPC 通信处理器 function setupIpcHandlers() { // =========================================================================== // gRPC 相关 // =========================================================================== // gRPC 连接 ipcMain.handle('grpc:connect', async (_event, { url }) => { try { await grpcClient?.connect(url); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 注册为参与方 ipcMain.handle('grpc:register', async (_event, { partyId, role }) => { try { await grpcClient?.registerParty(partyId, role); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 加入会话 ipcMain.handle('grpc:joinSession', async (_event, { sessionId, partyId, joinToken }) => { try { const result = await grpcClient?.joinSession(sessionId, partyId, joinToken); return { success: true, data: result }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // gRPC - 创建会话 (通过 Account 服务 HTTP API) ipcMain.handle('grpc:createSession', async (_event, params) => { try { const result = await accountClient?.createKeygenSession({ wallet_name: params.walletName, threshold_t: params.thresholdT, threshold_n: params.thresholdN, persistent_count: 0, // 服务端 party 数量,共管钱包模式下为 0 external_count: params.thresholdN, // 所有参与方都是外部 party expires_in_seconds: 86400, // 24 小时 }); return { success: true, sessionId: result?.session_id, inviteCode: result?.invite_code, walletName: result?.wallet_name, expiresAt: result?.expires_at, joinTokens: result?.join_tokens, }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // gRPC - 验证邀请码 (通过 Account 服务 HTTP API) ipcMain.handle('grpc:validateInviteCode', async (_event, { code }) => { try { const result = await accountClient?.getSessionByInviteCode(code); return { success: true, sessionInfo: { sessionId: result?.session_id, walletName: result?.wallet_name, threshold: { t: result?.threshold_t, n: result?.threshold_n, }, status: result?.status, currentParticipants: result?.joined_parties || 0, }, }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // gRPC - 获取会话状态 (通过 Account 服务 HTTP API) ipcMain.handle('grpc:getSessionStatus', async (_event, { sessionId }) => { try { const result = await accountClient?.getSessionStatus(sessionId); return { success: true, session: { sessionId: result?.session_id, status: result?.status, completedParties: result?.completed_parties, totalParties: result?.total_parties, sessionType: result?.session_type, publicKey: result?.public_key, }, }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // gRPC - 测试连接 ipcMain.handle('grpc:testConnection', async (_event, { url }) => { try { // 地址格式: host:port (例如 mpc-grpc.szaiai.com:443) await grpcClient?.connect(url); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // gRPC - 获取已注册的参与方列表 ipcMain.handle('grpc:getRegisteredParties', async (_event, { roleFilter, onlyOnline }) => { try { const parties = await grpcClient?.getRegisteredParties(roleFilter, onlyOnline); return { success: true, parties: parties || [] }; } catch (error) { return { success: false, error: (error as Error).message, parties: [] }; } }); // gRPC - 获取当前 partyId ipcMain.handle('grpc:getPartyId', async () => { try { const partyId = grpcClient?.getPartyId(); return { success: true, partyId: partyId || null }; } catch (error) { return { success: false, error: (error as Error).message, partyId: null }; } }); // gRPC - 检查连接状态 ipcMain.handle('grpc:isConnected', async () => { try { const connected = grpcClient?.isConnected() || false; return { success: true, connected }; } catch (error) { return { success: false, error: (error as Error).message, connected: false }; } }); // gRPC - 验证签名会话 (通过 Account 服务 HTTP API) ipcMain.handle('grpc:validateSigningSession', async (_event, { code }) => { try { const result = await accountClient?.getSignSessionByInviteCode(code); return { success: true, session: { sessionId: result?.session_id, keygenSessionId: result?.keygen_session_id, walletName: result?.wallet_name, messageHash: result?.message_hash, threshold: { t: result?.threshold_t, n: result?.parties?.length || 0, }, currentParticipants: result?.joined_count || 0, status: result?.status, parties: result?.parties, }, }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // gRPC - 加入签名会话 ipcMain.handle('grpc:joinSigningSession', async (_event, params) => { try { // 从本地 SQLite 获取 share 数据 const share = database?.getShare(params.shareId, params.password); if (!share) { return { success: false, error: 'Share not found or incorrect password' }; } // 加入签名会话 (通过 gRPC) // TODO: 实际加入会话逻辑需要使用 gRPC client // 这里先返回成功,表示验证通过 return { success: true, partyId: share.party_id, partyIndex: share.party_index, }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // =========================================================================== // Account 服务相关 (HTTP API) // =========================================================================== // 创建签名会话 ipcMain.handle('account:createSignSession', async (_event, params) => { try { // 从本地 SQLite 获取 share 数据 const share = database?.getShare(params.shareId, params.password); if (!share) { return { success: false, error: 'Share not found or incorrect password' }; } // 解析 participants_json 获取参与方列表 const participants = JSON.parse(share.participants_json || '[]'); const parties = participants.map((p: { partyId: string }, index: number) => ({ party_id: p.partyId, party_index: index, })); const result = await accountClient?.createSignSession({ keygen_session_id: share.session_id, wallet_name: share.wallet_name, message_hash: params.messageHash, parties: parties, threshold_t: share.threshold_t, initiator_name: params.initiatorName, }); return { success: true, sessionId: result?.session_id, inviteCode: result?.invite_code, expiresAt: result?.expires_at, joinToken: result?.join_token, }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 获取签名会话状态 (通过邀请码) ipcMain.handle('account:getSignSessionByInviteCode', async (_event, { inviteCode }) => { try { const result = await accountClient?.getSignSessionByInviteCode(inviteCode); return { success: true, session: { sessionId: result?.session_id, keygenSessionId: result?.keygen_session_id, walletName: result?.wallet_name, messageHash: result?.message_hash, thresholdT: result?.threshold_t, status: result?.status, inviteCode: result?.invite_code, expiresAt: result?.expires_at, parties: result?.parties, joinedCount: result?.joined_count, }, }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // Account 服务健康检查 ipcMain.handle('account:healthCheck', async () => { try { const result = await accountClient?.healthCheck(); return { success: true, data: result }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 测试 Account 服务连接 ipcMain.handle('account:testConnection', async () => { try { const connected = await accountClient?.testConnection(); return { success: connected }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 更新 Account 服务 URL ipcMain.handle('account:updateUrl', async (_event, { url }) => { try { accountClient?.setBaseUrl(url); database?.setSetting('account_service_url', url); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 获取 Account 服务 URL ipcMain.handle('account:getUrl', async () => { return accountClient?.getBaseUrl() || 'https://api.szaiai.com'; }); // =========================================================================== // Share 存储相关 (SQLite) // =========================================================================== // 保存 share ipcMain.handle('storage:saveShare', async (_event, { share, password }) => { try { const saved = database?.saveShare({ sessionId: share.sessionId, walletName: share.walletName, partyId: share.partyId, partyIndex: share.partyIndex, thresholdT: share.threshold.t, thresholdN: share.threshold.n, publicKeyHex: share.publicKey, rawShare: share.rawShare, participants: share.participants || [], }, password); // 自动派生 Kava 地址 if (saved && share.publicKey) { try { const kavaAddress = addressDerivationService.deriveAddress(share.publicKey, 'kava'); database?.saveDerivedAddress({ shareId: saved.id, chain: 'kava', derivationPath: kavaAddress.derivationPath, address: kavaAddress.address, publicKeyHex: share.publicKey, }); } catch (err) { console.error('Failed to derive Kava address:', err); } } return { success: true, data: saved }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 获取 share 列表 ipcMain.handle('storage:listShares', async () => { try { const shares = database?.listShares() ?? []; // 转换为前端期望的格式 const formatted = shares.map(share => ({ id: share.id, sessionId: share.session_id, walletName: share.wallet_name, partyId: share.party_id, partyIndex: share.party_index, threshold: { t: share.threshold_t, n: share.threshold_n, }, publicKey: share.public_key_hex, createdAt: share.created_at, lastUsedAt: share.last_used_at, participants: JSON.parse(share.participants_json || '[]'), })); return { success: true, data: formatted }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 获取单个 share (解密) ipcMain.handle('storage:getShare', async (_event, { id, password }) => { try { const share = database?.getShare(id, password); if (!share) return null; return { id: share.id, sessionId: share.session_id, walletName: share.wallet_name, partyId: share.party_id, partyIndex: share.party_index, threshold: { t: share.threshold_t, n: share.threshold_n, }, publicKey: share.public_key_hex, rawShare: share.raw_share, createdAt: share.created_at, lastUsedAt: share.last_used_at, participants: JSON.parse(share.participants_json || '[]'), }; } catch (error) { return null; } }); // 删除 share ipcMain.handle('storage:deleteShare', async (_event, { id }) => { try { database?.deleteShare(id); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 导出 share ipcMain.handle('storage:exportShare', async (_event, { id, password }) => { try { const data = database?.exportShare(id, password); return { success: true, data }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 导入 share ipcMain.handle('storage:importShare', async (_event, { filePath, password }) => { try { const data = fs.readFileSync(filePath); const share = database?.importShare(data, password); return { success: true, share }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // =========================================================================== // 地址派生相关 // =========================================================================== // 派生地址 ipcMain.handle('address:derive', async (_event, { shareId, chain, password }) => { try { const share = database?.getShare(shareId, password); if (!share) { return { success: false, error: 'Share not found' }; } const derived = addressDerivationService.deriveAddress(share.public_key_hex, chain); // 保存到数据库 const saved = database?.saveDerivedAddress({ shareId, chain, derivationPath: derived.derivationPath, address: derived.address, publicKeyHex: share.public_key_hex, }); return { success: true, data: { ...derived, id: saved?.id } }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 派生所有支持的链地址 ipcMain.handle('address:deriveAll', async (_event, { shareId, password }) => { try { const share = database?.getShare(shareId, password); if (!share) { return { success: false, error: 'Share not found' }; } const addresses = addressDerivationService.deriveAllAddresses(share.public_key_hex); // 保存所有地址 for (const addr of addresses) { database?.saveDerivedAddress({ shareId, chain: addr.chain, derivationPath: addr.derivationPath, address: addr.address, publicKeyHex: share.public_key_hex, }); } return { success: true, data: addresses }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 获取 share 的所有派生地址 ipcMain.handle('address:list', async (_event, { shareId }) => { try { const addresses = database?.getAddressesByShare(shareId) ?? []; return { success: true, data: addresses.map(addr => ({ id: addr.id, shareId: addr.share_id, chain: addr.chain, chainName: CHAIN_CONFIGS[addr.chain]?.name || addr.chain, derivationPath: addr.derivation_path, address: addr.address, publicKeyHex: addr.public_key_hex, createdAt: addr.created_at, })), }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 获取支持的链列表 ipcMain.handle('address:getSupportedChains', async () => { return addressDerivationService.getSupportedChains(); }); // =========================================================================== // 签名历史相关 // =========================================================================== // 获取签名历史 ipcMain.handle('signing:getHistory', async (_event, { shareId }) => { try { const history = database?.getSigningHistoryByShare(shareId) ?? []; return { success: true, data: history.map(h => ({ id: h.id, shareId: h.share_id, sessionId: h.session_id, messageHash: h.message_hash, signature: h.signature, status: h.status, errorMessage: h.error_message, createdAt: h.created_at, completedAt: h.completed_at, })), }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // =========================================================================== // 设置相关 // =========================================================================== // 获取设置 ipcMain.handle('storage:getSettings', async () => { try { const settings = database?.getAllSettings() ?? {}; return { messageRouterUrl: settings['message_router_url'] || 'mpc-grpc.szaiai.com:443', autoBackup: settings['auto_backup'] === 'true', backupPath: settings['backup_path'] || '', }; } catch (error) { return null; } }); // 保存设置 ipcMain.handle('storage:saveSettings', async (_event, { settings }) => { try { if (settings.messageRouterUrl !== undefined) { database?.setSetting('message_router_url', settings.messageRouterUrl); } if (settings.autoBackup !== undefined) { database?.setSetting('auto_backup', settings.autoBackup ? 'true' : 'false'); } if (settings.backupPath !== undefined) { database?.setSetting('backup_path', settings.backupPath); } return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // =========================================================================== // Kava 区块链相关 // =========================================================================== // 查询 Kava 余额 ipcMain.handle('kava:getBalance', async (_event, { address }) => { try { const balance = await kavaTxService?.getKavaBalance(address); return { success: true, data: balance }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 查询所有余额 ipcMain.handle('kava:getAllBalances', async (_event, { address }) => { try { const balances = await kavaTxService?.getAllBalances(address); return { success: true, data: balances }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 查询账户信息 ipcMain.handle('kava:getAccountInfo', async (_event, { address }) => { try { const info = await kavaTxService?.getAccountInfo(address); return { success: true, data: info }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 构建转账交易 (待签名) ipcMain.handle('kava:buildSendTx', async (_event, { fromAddress, toAddress, amount, publicKeyHex, memo }) => { try { const unsignedTx = await kavaTxService?.buildSendTx( fromAddress, toAddress, amount, publicKeyHex, memo || '' ); return { success: true, data: unsignedTx }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 完成签名并广播交易 ipcMain.handle('kava:completeTxAndBroadcast', async (_event, { unsignedTx, signatureHex }) => { try { // 完成交易签名 const signedTx = await kavaTxService?.completeTx(unsignedTx, signatureHex); if (!signedTx) { return { success: false, error: 'Failed to complete transaction' }; } // 广播交易 const result = await kavaTxService?.broadcastTx(signedTx); return { success: true, data: result }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 查询交易状态 ipcMain.handle('kava:getTxStatus', async (_event, { txHash }) => { try { const status = await kavaTxService?.getTxStatus(txHash); return { success: true, data: status }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // 获取 Kava 配置 ipcMain.handle('kava:getConfig', async () => { return kavaTxService?.getConfig(); }); // 更新 Kava 配置 ipcMain.handle('kava:updateConfig', async (_event, { config }) => { try { kavaTxService?.updateConfig(config); return { success: true }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // Kava 健康检查 ipcMain.handle('kava:healthCheck', async () => { try { const result = await kavaTxService?.healthCheck(); return { success: result?.ok ?? false, data: result }; } catch (error) { return { success: false, error: (error as Error).message }; } }); // =========================================================================== // 对话框相关 // =========================================================================== // 选择目录 ipcMain.handle('dialog:selectDirectory', async () => { const result = await dialog.showOpenDialog(mainWindow!, { properties: ['openDirectory'], }); return result.canceled ? null : result.filePaths[0]; }); // 选择文件 ipcMain.handle('dialog:selectFile', async (_event, { filters }) => { const result = await dialog.showOpenDialog(mainWindow!, { properties: ['openFile'], filters: filters || [], }); return result.canceled ? null : result.filePaths[0]; }); // 保存文件 ipcMain.handle('dialog:saveFile', async (_event, { defaultPath, filters }) => { const result = await dialog.showSaveDialog(mainWindow!, { defaultPath, filters: filters || [], }); return result.canceled ? null : result.filePath; }); } // 应用生命周期 app.whenReady().then(async () => { await initServices(); // HTTP 服务器仅在开发模式下启动 if (process.env.NODE_ENV === 'development') { startHttpServer(); } createWindow(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { createWindow(); } }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } }); app.on('before-quit', () => { // 清理资源 grpcClient?.disconnect(); database?.close(); httpServer?.close(); kavaTxService?.disconnect(); });