import { ipcMain, dialog, app } from 'electron' import fs from 'fs' import path from 'path' import { encrypt, fromEncryptedJson } from './utils/keystore' const neptuneNative = require('@neptune/native') // Create keystore into default wallets directory ipcMain.handle('wallet:createKeystore', async (_event, seed, password) => { try { const keystore = await encrypt(seed, password) const savePath = path.join(process.cwd(), 'wallets') fs.mkdirSync(savePath, { recursive: true }) // Use timestamp for filename const timestamp = Date.now() const fileName = `neptune-wallet-${timestamp}.json` const filePath = path.join(savePath, fileName) fs.writeFileSync(filePath, keystore) return { filePath } } catch (error) { console.error('Error creating keystore:', error) throw error } }) // New handler: let user choose folder and filename to save keystore ipcMain.handle('wallet:saveKeystoreAs', async (_event, seed: string, password: string) => { try { const keystore = await encrypt(seed, password) // Use timestamp for default filename const timestamp = Date.now() const defaultName = `neptune-wallet-${timestamp}.json` const { canceled, filePath } = await dialog.showSaveDialog({ title: 'Save Keystore File', defaultPath: path.join(app.getPath('documents'), defaultName), filters: [{ name: 'JSON', extensions: ['json'] }], }) if (canceled || !filePath) return { filePath: null } fs.mkdirSync(path.dirname(filePath), { recursive: true }) fs.writeFileSync(filePath, keystore) return { filePath } } catch (error) { console.error('Error saving keystore (Save As):', error) throw error } }) ipcMain.handle('wallet:decryptKeystore', async (_event, filePath, password) => { try { const json = fs.readFileSync(filePath, 'utf-8') const phrase = await fromEncryptedJson(json, password) return { phrase } } catch (error) { console.error('Error decrypting keystore ipc:', error) throw error } }) ipcMain.handle('wallet:checkKeystore', async () => { try { const walletDir = path.join(process.cwd(), 'wallets') if (!fs.existsSync(walletDir)) return { exists: false, filePath: null } const newestFile = fs .readdirSync(walletDir) .filter((f) => f.endsWith('.json')) .sort( (a, b) => fs.statSync(path.join(walletDir, b)).mtime.getTime() - fs.statSync(path.join(walletDir, a)).mtime.getTime() )[0] if (!newestFile) return { exists: false, filePath: null } const resolvedPath = path.join(walletDir, newestFile) let minBlockHeight: number | null = null try { const json = fs.readFileSync(resolvedPath, 'utf-8') const data = JSON.parse(json) const height = data?.minBlockHeight if (typeof height === 'number' && Number.isFinite(height)) { minBlockHeight = height } } catch (error) { console.warn('Unable to read minBlockHeight from keystore:', error) } return { exists: true, filePath: resolvedPath, minBlockHeight } } catch (error) { console.error('Error checking keystore ipc:', error) return { exists: false, filePath: null, minBlockHeight: null, error: String(error) } } }) ipcMain.handle( 'wallet:updateMinBlockHeight', async (_event, filePath: string | null, minBlockHeight: number | null) => { if (!filePath) { return { success: false, error: 'No keystore file path provided.' } } try { const normalizedPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath) if (!fs.existsSync(normalizedPath)) { return { success: false, error: 'Keystore file not found.' } } const fileContents = fs.readFileSync(normalizedPath, 'utf-8') const walletJson = JSON.parse(fileContents) if (minBlockHeight === null || Number.isNaN(minBlockHeight)) { walletJson.minBlockHeight = null } else { walletJson.minBlockHeight = minBlockHeight } fs.writeFileSync(normalizedPath, JSON.stringify(walletJson, null, 2), 'utf-8') return { success: true, minBlockHeight } } catch (error) { console.error('Error updating min block height:', error) return { success: false, error: String(error) } } } ) ipcMain.handle( 'wallet:getMinBlockHeight', async (_event, filePath: string | null) => { if (!filePath) { return { success: false, error: 'No keystore file path provided.', minBlockHeight: null } } try { const normalizedPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath) if (!fs.existsSync(normalizedPath)) { return { success: false, error: 'Keystore file not found.', minBlockHeight: null } } const fileContents = fs.readFileSync(normalizedPath, 'utf-8') const walletJson = JSON.parse(fileContents) const height = walletJson?.minBlockHeight if (typeof height === 'number' && Number.isFinite(height)) { return { success: true, minBlockHeight: height } } return { success: true, minBlockHeight: null } } catch (error) { console.error('Error reading min block height:', error) return { success: false, error: String(error), minBlockHeight: null } } } ) ipcMain.handle('wallet:generateKeysFromSeed', async (_event, seedPhrase: string[]) => { try { const wallet = new neptuneNative.WalletManager() return wallet.generateKeysFromSeed(seedPhrase) } catch (error) { console.error('Error generating keys from seed ipc:', error) throw error } }) ipcMain.handle('wallet:buildTransaction', async (_event, args) => { const { spendingKeyHex, inputAdditionRecords, outputAddresses, outputAmounts, fee } = args try { const builder = new neptuneNative.SimpleTransactionBuilder() const result = await builder.buildTransaction( import.meta.env.VITE_APP_API, spendingKeyHex, inputAdditionRecords, // pass minBlockHeight from args if provided, default 0 typeof args?.minBlockHeight === 'number' && Number.isFinite(args.minBlockHeight) ? args.minBlockHeight : 0, outputAddresses, outputAmounts, fee ) return JSON.parse(result) } catch (error) { console.error('Error building transaction with primitive proof ipc:', error) throw error } })