rwadurian/backend/mpc-system/services/service-party-app/electron/main.ts

801 lines
25 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { app, BrowserWindow, ipcMain, shell, dialog } from 'electron';
import * as path from 'path';
import * as fs from 'fs';
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<typeof express.application.listen> | 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}`);
});
}
// 初始化服务
async function initServices() {
// 初始化 gRPC 客户端
grpcClient = new GrpcClient();
// 初始化数据库
database = new DatabaseManager();
// 初始化 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();
}
// 设置 IPC 通信处理器
function setupIpcHandlers() {
// ===========================================================================
// gRPC 相关
// ===========================================================================
// gRPC 连接
ipcMain.handle('grpc:connect', async (_event, { host, port }) => {
try {
await grpcClient?.connect(host, port);
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 - 验证签名会话 (通过 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();
});