diff --git a/apps/desktop/src/bun/index.ts b/apps/desktop/src/bun/index.ts index 8be7c6c..f6ebbb4 100644 --- a/apps/desktop/src/bun/index.ts +++ b/apps/desktop/src/bun/index.ts @@ -16,6 +16,7 @@ import { getTransactionDetails, } from './lib/transactions' import { migrate } from 'drizzle-orm/bun-sqlite/migrator' +import { eq } from 'drizzle-orm' import { db } from './lib/db' import { join } from 'path' import { getENV } from './lib/get-env' @@ -34,6 +35,7 @@ import { transactions, transactionReceipts, txHistory, + evmWallets, } from './lib/db/schema' const env = getENV() @@ -265,6 +267,22 @@ type RPC = { connected: boolean } } + evmCreateWallet: { + params: { name: string; phrase: string; passwordHash?: string } + response: { id: string; name: string; createdAt: string } + } + evmListWallets: { + params: {} + response: Array<{ id: string; name: string; hasPassword: boolean; vaultKey: string; createdAt: string }> + } + evmGetSeed: { + params: { vaultKey: string } + response: string + } + evmDeleteWallet: { + params: { id: string } + response: void + } } messages: {} }> @@ -379,15 +397,19 @@ const rpc = BrowserView.defineRPC({ requests: { resetApp: async () => { try { + const wallets = db.select().from(evmWallets).all() await Promise.all([ - Bun.secrets.delete({ service: 'koins', name: 'vault' }), - Bun.secrets.delete({ service: 'koins', name: 'vault_password' }), Bun.secrets.delete({ service: 'koins', name: 'alchemy_key' }), + ...wallets.flatMap((w) => [ + Bun.secrets.delete({ service: 'koins', name: w.vaultKey }), + Bun.secrets.delete({ service: 'koins', name: `evm_auth_${w.id}` }), + ]), ]) sqlite.run('DELETE FROM token_metadata') sqlite.run('DELETE FROM transactions') sqlite.run('DELETE FROM transaction_receipts') sqlite.run('DELETE FROM tx_history') + sqlite.run('DELETE FROM evm_wallets') console.log('[rpc] resetApp complete') return true } catch (e) { @@ -749,6 +771,49 @@ const rpc = BrowserView.defineRPC({ ) return wallets }, + evmCreateWallet: async ({ name, phrase, passwordHash }) => { + console.log('[rpc] evmCreateWallet:', name) + const id = crypto.randomUUID() + const vaultKey = `evm_seed_${id}` + await Bun.secrets.set({ service: 'koins', name: vaultKey, value: phrase }) + await db.insert(evmWallets).values({ + id, + name, + passwordHash: passwordHash ?? null, + vaultKey, + createdAt: new Date().toISOString(), + }) + console.log('[rpc] evmCreateWallet complete:', id) + return { id, name, createdAt: new Date().toISOString() } + }, + evmListWallets: async () => { + console.log('[rpc] evmListWallets') + const wallets = db.select().from(evmWallets).all() + return wallets.map((w) => ({ + id: w.id, + name: w.name, + hasPassword: !!w.passwordHash, + vaultKey: w.vaultKey, + createdAt: w.createdAt, + })) + }, + evmGetSeed: async ({ vaultKey }) => { + console.log('[rpc] evmGetSeed:', vaultKey) + const seed = await Bun.secrets.get({ service: 'koins', name: vaultKey }) + if (!seed) throw new Error('Seed not found in keychain') + return seed + }, + evmDeleteWallet: async ({ id }) => { + console.log('[rpc] evmDeleteWallet:', id) + const wallet = db.select().from(evmWallets).where(eq(evmWallets.id, id)).get() + if (!wallet) throw new Error('Wallet not found') + await Promise.all([ + Bun.secrets.delete({ service: 'koins', name: wallet.vaultKey }), + Bun.secrets.delete({ service: 'koins', name: `evm_auth_${id}` }), + ]) + db.delete(evmWallets).where(eq(evmWallets.id, id)).run() + console.log('[rpc] evmDeleteWallet complete') + }, }, messages: {}, }, diff --git a/apps/desktop/src/bun/lib/db/schema.ts b/apps/desktop/src/bun/lib/db/schema.ts index ac21d02..7f90634 100644 --- a/apps/desktop/src/bun/lib/db/schema.ts +++ b/apps/desktop/src/bun/lib/db/schema.ts @@ -72,6 +72,14 @@ export const transactionReceipts = sqliteTable( }), ) +export const evmWallets = sqliteTable('evm_wallets', { + id: text('id').primaryKey(), + name: text('name').notNull(), + passwordHash: text('password_hash'), + vaultKey: text('vault_key').notNull(), + createdAt: text('created_at').notNull(), +}) + export const txHistory = sqliteTable( 'tx_history', { diff --git a/apps/desktop/src/lib/components/EvmDashboard.svelte b/apps/desktop/src/lib/components/EvmDashboard.svelte index 3dac769..d7bcbea 100644 --- a/apps/desktop/src/lib/components/EvmDashboard.svelte +++ b/apps/desktop/src/lib/components/EvmDashboard.svelte @@ -19,6 +19,10 @@ import QrCodeIcon from '@lucide/svelte/icons/qr-code' import SettingsIcon from '@lucide/svelte/icons/settings' import Fingerprint from '@lucide/svelte/icons/fingerprint' + import Trash2 from '@lucide/svelte/icons/trash-2' + import Plus from '@lucide/svelte/icons/plus' + import WalletIcon from '@lucide/svelte/icons/wallet' + import LockKeyhole from '@lucide/svelte/icons/lock-keyhole' import * as Dialog from '$lib/components/ui/dialog/index.js' import QRCode from 'qrcode' import Loader from './loader.svelte' @@ -57,9 +61,11 @@ let inputSeed = $state('') let inputPassword = $state('') let inputUnlockPassword = $state('') + let inputWalletName = $state('') let apiKeyInput = $state('') let qrDataUrl = $state('') let qrDialogOpen = $state(false) + let showCreateForm = $state(false) $effect(() => { if (w.address) { QRCode.toDataURL(w.address, { width: 256, margin: 1 }).then( @@ -91,43 +97,100 @@ class="mx-auto mt-16 max-w-md text-center text-muted-foreground text-sm"> Loading... -{:else if !w.vaultExists} +{:else if w.wallets.length === 0 || showCreateForm} - Import Wallet + + {#if showCreateForm} + {@const cur = w.currentWallet} + {cur ? `Add Another Wallet` : `Import Wallet`} + {:else} + Import Wallet + {/if} + - Enter your seed phrase to save it securely in your system - keychain + Name your wallet and enter your seed phrase to save it securely
+