diff --git a/electron/main.js b/electron/main.js new file mode 100644 index 0000000..80474e2 --- /dev/null +++ b/electron/main.js @@ -0,0 +1,27 @@ +const { app, BrowserWindow } = require('electron'); +const path = require('path'); + +function createWindow() { + const win = new BrowserWindow({ + width: 900, + height: 700, + webPreferences: { + preload: path.join(__dirname, 'preload.js'), + contextIsolation: true, + nodeIntegration: false + } + }); + + win.loadFile(path.join(__dirname, 'renderer', 'index.html')); +} + +app.whenReady().then(() => { + createWindow(); + app.on('activate', function () { + if (BrowserWindow.getAllWindows().length === 0) createWindow(); + }); +}); + +app.on('window-all-closed', function () { + if (process.platform !== 'darwin') app.quit(); +}); \ No newline at end of file diff --git a/electron/package.json b/electron/package.json new file mode 100644 index 0000000..f1b11fb --- /dev/null +++ b/electron/package.json @@ -0,0 +1,13 @@ +{ + "name": "cryptec-electron", + "version": "0.1.0", + "private": true, + "main": "main.js", + "scripts": { + "start": "electron .", + "test": "node ./test/electron-smoke.js" + }, + "devDependencies": { + "electron": "^25.0.0" + } +} diff --git a/electron/preload.js b/electron/preload.js new file mode 100644 index 0000000..f8c82c8 --- /dev/null +++ b/electron/preload.js @@ -0,0 +1,56 @@ +const { contextBridge } = require('electron'); + +let nativeBindings = null; +try { + // Try to require the Node bindings package (napi). If not available, fall back to a JS shim. + nativeBindings = require(pathJoin(__dirname, '..', 'bindings')); +} catch (e) { + // ignore +} + +function pathJoin(...parts) { + return parts.join('/'); +} + +const shim = { + generate_mnemonic: (strength = 128) => { + // simple in-process fallback + const crypto = require('crypto'); + // use 128-bit entropy => 12 words via bip39 library if available + try { + const bip39 = require('bip39'); + return bip39.generateMnemonic(strength); + } catch (e) { + return crypto.randomBytes(16).toString('hex'); + } + }, + first_receive_address: (mnemonic) => { + try { + const bip39 = require('bip39'); + const bitcoin = require('bitcoinjs-lib'); + const seed = bip39.mnemonicToSeedSync(mnemonic); + const root = bitcoin.bip32.fromSeed(seed, bitcoin.networks.testnet); + const child = root.derivePath("m/84'/1'/0'/0/0"); + const { address } = bitcoin.payments.p2wpkh({ pubkey: child.publicKey, network: bitcoin.networks.testnet }); + return address || ''; + } catch (e) { + return ''; + } + }, + create_psbt: (_mnemonic, toAddress, satoshis) => { + return Buffer.from(JSON.stringify({ type: 'psbt-mock', to: toAddress, sats: satoshis })).toString('base64'); + }, + sign_psbt: (_mnemonic, psbtB64) => { + const decoded = Buffer.from(psbtB64, 'base64').toString('utf8'); + return Buffer.from(JSON.stringify({ signed: true, original: decoded })).toString('base64'); + } +}; + +const api = nativeBindings ? nativeBindings : shim; + +contextBridge.exposeInMainWorld('cryptec', { + generateMnemonic: (strength) => api.generate_mnemonic ? api.generate_mnemonic(strength) : api.generate_mnemonic(strength), + firstReceiveAddress: (mnemonic) => api.first_receive_address ? api.first_receive_address(mnemonic) : api.first_receive_address(mnemonic), + createPsbt: (mnemonic, to, sats) => api.create_psbt ? api.create_psbt(mnemonic, to, sats) : api.create_psbt(mnemonic, to, sats), + signPsbt: (mnemonic, psbtB64) => api.sign_psbt ? api.sign_psbt(mnemonic, psbtB64) : api.sign_psbt(mnemonic, psbtB64) +}); \ No newline at end of file diff --git a/electron/renderer/index.html b/electron/renderer/index.html new file mode 100644 index 0000000..3028634 --- /dev/null +++ b/electron/renderer/index.html @@ -0,0 +1,39 @@ + + +
+ +