From bd8ba8e3e586b2457bd9e12fd03985238700361b Mon Sep 17 00:00:00 2001 From: TZGyn Date: Sat, 1 Aug 2026 19:07:49 +0800 Subject: [PATCH] native ui + touch id unlock + portfolio dashboard --- apps/desktop/src/app.css | 154 +++++---- apps/desktop/src/bun/lib/monero/wallet.ts | 46 ++- apps/desktop/src/bun/native/biometric-helper | Bin 72832 -> 54880 bytes .../src/lib/states/monero-wallet.svelte.ts | 17 + apps/desktop/src/lib/states/session.svelte.ts | 59 ++++ .../src/lib/states/xrp-wallet.svelte.ts | 23 ++ apps/desktop/src/routes/index.svelte | 305 +++++++++++++++--- apps/desktop/src/routes/layout.svelte | 154 +++++++++ apps/desktop/src/routes/monero/index.svelte | 20 +- apps/desktop/src/routes/monero/send.svelte | 10 +- .../desktop/src/routes/monero/settings.svelte | 11 +- .../src/routes/monero/tx/[hash].svelte | 10 +- .../src/routes/monero/wallet/[name].svelte | 2 +- apps/desktop/src/routes/settings.svelte | 18 +- apps/desktop/src/routes/xrp/index.svelte | 12 +- apps/desktop/src/routes/xrp/send.svelte | 13 +- apps/desktop/src/routes/xrp/tx/[hash].svelte | 13 +- .../desktop/src/routes/xrp/wallet/[id].svelte | 2 +- 18 files changed, 675 insertions(+), 194 deletions(-) create mode 100644 apps/desktop/src/lib/states/session.svelte.ts create mode 100644 apps/desktop/src/routes/layout.svelte diff --git a/apps/desktop/src/app.css b/apps/desktop/src/app.css index e19f266..5081455 100644 --- a/apps/desktop/src/app.css +++ b/apps/desktop/src/app.css @@ -6,77 +6,78 @@ @custom-variant dark (&:is(.dark *)); :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); + --background: oklch(0.99 0 0); + --foreground: oklch(0.16 0 0); --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); + --card-foreground: oklch(0.16 0 0); --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.553 0.195 38.402); - --primary-foreground: oklch(0.98 0.016 73.684); - --secondary: oklch(0.967 0.001 286.375); - --secondary-foreground: oklch(0.21 0.006 285.885); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.553 0.195 38.402); - --accent-foreground: oklch(0.98 0.016 73.684); + --popover-foreground: oklch(0.16 0 0); + --primary: oklch(0.4 0 0); + --primary-foreground: oklch(0.99 0 0); + --secondary: oklch(0.96 0 0); + --secondary-foreground: oklch(0.2 0 0); + --muted: oklch(0.965 0 0); + --muted-foreground: oklch(0.52 0 0); + --accent: oklch(0.94 0 0); + --accent-foreground: oklch(0.2 0 0); --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.837 0.128 66.29); - --chart-2: oklch(0.705 0.213 47.604); - --chart-3: oklch(0.646 0.222 41.116); - --chart-4: oklch(0.553 0.195 38.402); - --chart-5: oklch(0.47 0.157 37.304); - --radius: 0rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.646 0.222 41.116); - --sidebar-primary-foreground: oklch(0.98 0.016 73.684); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --border: oklch(0.905 0 0); + --input: oklch(0.905 0 0); + --ring: oklch(0.6 0 0); + --chart-1: oklch(0.4 0 0); + --chart-2: oklch(0.55 0 0); + --chart-3: oklch(0.68 0 0); + --chart-4: oklch(0.78 0 0); + --chart-5: oklch(0.88 0 0); + --radius: 0.625rem; + --sidebar: oklch(0.97 0 0); + --sidebar-foreground: oklch(0.2 0 0); + --sidebar-primary: oklch(0.35 0 0); + --sidebar-primary-foreground: oklch(0.99 0 0); + --sidebar-accent: oklch(0.92 0 0); + --sidebar-accent-foreground: oklch(0.2 0 0); + --sidebar-border: oklch(0.905 0 0); + --sidebar-ring: oklch(0.6 0 0); } .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.47 0.157 37.304); - --primary-foreground: oklch(0.98 0.016 73.684); - --secondary: oklch(0.274 0.006 286.033); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.47 0.157 37.304); - --accent-foreground: oklch(0.98 0.016 73.684); + --background: oklch(0.21 0 0); + --foreground: oklch(0.96 0 0); + --card: oklch(0.235 0 0); + --card-foreground: oklch(0.96 0 0); + --popover: oklch(0.25 0 0); + --popover-foreground: oklch(0.96 0 0); + --primary: oklch(0.5 0 0); + --primary-foreground: oklch(0.99 0 0); + --secondary: oklch(0.29 0 0); + --secondary-foreground: oklch(0.96 0 0); + --muted: oklch(0.27 0 0); + --muted-foreground: oklch(0.66 0 0); + --accent: oklch(0.3 0 0); + --accent-foreground: oklch(0.96 0 0); --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.837 0.128 66.29); - --chart-2: oklch(0.705 0.213 47.604); - --chart-3: oklch(0.646 0.222 41.116); - --chart-4: oklch(0.553 0.195 38.402); - --chart-5: oklch(0.47 0.157 37.304); + --border: oklch(1 0 0 / 12%); + --input: oklch(1 0 0 / 16%); + --ring: oklch(0.55 0 0); + --chart-1: oklch(0.7 0 0); + --chart-2: oklch(0.6 0 0); + --chart-3: oklch(0.5 0 0); + --chart-4: oklch(0.4 0 0); + --chart-5: oklch(0.3 0 0); --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.705 0.213 47.604); - --sidebar-primary-foreground: oklch(0.98 0.016 73.684); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.92 0 0); + --sidebar-primary: oklch(0.7 0 0); + --sidebar-primary-foreground: oklch(0.1 0 0); + --sidebar-accent: oklch(0.28 0 0); + --sidebar-accent-foreground: oklch(0.96 0 0); --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --sidebar-ring: oklch(0.55 0 0); } @theme inline { - --font-mono: 'Geist Mono Variable', monospace; - --font-heading: 'Geist Mono Variable', monospace; + --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Segoe UI", system-ui, sans-serif; + --font-mono: 'Geist Mono Variable', ui-monospace, SFMono-Regular, Menlo, monospace; + --font-heading: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", system-ui, sans-serif; --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); @@ -121,10 +122,41 @@ * { @apply border-border outline-ring/50; } + html { + @apply font-sans; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + font-feature-settings: "ss01", "cv01"; + } body { @apply bg-background text-foreground; } - html { - @apply font-mono; + ::selection { + background: oklch(0.5 0 0 / 25%); } } + +@layer base { + * { + scrollbar-width: thin; + scrollbar-color: oklch(0.5 0 0 / 25%) transparent; + } + ::-webkit-scrollbar { + width: 8px; + height: 8px; + } + ::-webkit-scrollbar-track { + background: transparent; + } + ::-webkit-scrollbar-thumb { + background: oklch(0.5 0 0 / 22%); + border-radius: 9999px; + border: 2px solid transparent; + background-clip: content-box; + } + ::-webkit-scrollbar-thumb:hover { + background: oklch(0.5 0 0 / 38%); + background-clip: content-box; + } +} \ No newline at end of file diff --git a/apps/desktop/src/bun/lib/monero/wallet.ts b/apps/desktop/src/bun/lib/monero/wallet.ts index 29d8637..4eb2739 100644 --- a/apps/desktop/src/bun/lib/monero/wallet.ts +++ b/apps/desktop/src/bun/lib/monero/wallet.ts @@ -353,16 +353,44 @@ export async function getTransferDetails( txid: string, ): Promise { if (!state.wallet) throw new Error('Wallet RPC not started') - const result = await rawRpc(state, 'get_transfer_by_txid', { txid }) - if (!result?.transfer) return null - const t = result.transfer + let t: any = null + let dests: any[] = [] + try { + const result = await rawRpc(state, 'get_transfer_by_txid', { txid }) + if (result?.transfer) { + t = result.transfer + dests = + t.destinations?.map((d: any) => ({ + address: d.address, + amount: d.amount?.toString() ?? '0', + })) ?? [] + } + } catch { + // get_transfer_by_txid can fail for various reasons; fall back + } + if (!t) { + const params: Record = { + in: true, + out: true, + pending: true, + pool: true, + } + const result = await rawRpc(state, 'get_transfers', params) + let found: any = null + for (const dir of ['in', 'out', 'pending', 'pool'] as const) { + const transfers = result[dir] ?? [] + for (const tr of transfers) { + if (tr.txid === txid) { + found = { ...tr, type: dir === 'in' ? 'in' : 'out' } + break + } + } + if (found) break + } + if (!found) return null + t = found + } const isIn = t.type === 'in' - const isOut = t.type === 'out' - const dests = - t.destinations?.map((d: any) => ({ - address: d.address, - amount: d.amount?.toString() ?? '0', - })) ?? [] const subaddrIndices = isIn ? [ { diff --git a/apps/desktop/src/bun/native/biometric-helper b/apps/desktop/src/bun/native/biometric-helper index cf4ea97d7a33cdb61de8d14e88495805db6c58c2..d6875f9ba759315c9c349342807009e480d9f300 100755 GIT binary patch delta 585 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 MTHIew6#J(5KV>9r6#xJL diff --git a/apps/desktop/src/lib/states/monero-wallet.svelte.ts b/apps/desktop/src/lib/states/monero-wallet.svelte.ts index 054ff79..7b9c0fc 100644 --- a/apps/desktop/src/lib/states/monero-wallet.svelte.ts +++ b/apps/desktop/src/lib/states/monero-wallet.svelte.ts @@ -246,6 +246,22 @@ export const MoneroWallet = () => { await refresh() } + const autoUnlock = async (name?: string) => { + if (!electrobun.rpc) return false + const target = name ?? wallets[0] + if (!target) return false + const pw = await moneroGetStoredPassword(target) + if (!pw) return false + await electrobun.rpc.request.moneroOpenWallet({ + name: target, + password: pw, + }) + walletName = target + walletOpen = true + await refresh() + return true + } + const refresh = async () => { if (!electrobun.rpc) return loading = true @@ -430,6 +446,7 @@ export const MoneroWallet = () => { createWallet, restoreWallet, openWallet, + autoUnlock, refresh, send, sendAll, diff --git a/apps/desktop/src/lib/states/session.svelte.ts b/apps/desktop/src/lib/states/session.svelte.ts new file mode 100644 index 0000000..a8cfe37 --- /dev/null +++ b/apps/desktop/src/lib/states/session.svelte.ts @@ -0,0 +1,59 @@ +import { electrobun } from '$lib/electrobun' + +let unlocked = $state(false) +let biometricAvailable = $state(false) +let unlocking = $state(false) +let error = $state('') + +const checkBiometric = async () => { + if (!electrobun.rpc) return + try { + biometricAvailable = await electrobun.rpc.request.biometricCanAuth({}) + } catch { /* ignore */ } +} + +const unlock = async (): Promise => { + if (!electrobun.rpc) return false + unlocking = true + error = '' + try { + const ok = await electrobun.rpc.request.biometricAuth({ + reason: 'Unlock Koins', + }) + if (ok) { + unlocked = true + } else { + error = 'Authentication failed' + } + return ok + } catch (e) { + error = e instanceof Error ? e.message : 'Unlock failed' + return false + } finally { + unlocking = false + } +} + +const lock = () => { + unlocked = false + error = '' +} + +checkBiometric() + +export const session = { + get unlocked() { + return unlocked + }, + get biometricAvailable() { + return biometricAvailable + }, + get unlocking() { + return unlocking + }, + get error() { + return error + }, + unlock, + lock, +} \ No newline at end of file diff --git a/apps/desktop/src/lib/states/xrp-wallet.svelte.ts b/apps/desktop/src/lib/states/xrp-wallet.svelte.ts index a5f4b7d..eaa33b5 100644 --- a/apps/desktop/src/lib/states/xrp-wallet.svelte.ts +++ b/apps/desktop/src/lib/states/xrp-wallet.svelte.ts @@ -179,6 +179,28 @@ export const XrpWallet = () => { return false } + const autoUnlock = async (walletId?: string) => { + const id = walletId ?? currentWalletId ?? wallets[0]?.id + if (!id) return false + currentWalletId = id + const wallet = wallets.find((w) => w.id === id) + if (!wallet) return false + if (electrobun.rpc && wallet.hasPassword) { + const [ph] = await tryCatch( + electrobun.rpc.request.getSecret({ + service: 'koins', + name: `xrp_auth_${id}`, + }), + ) + if (ph) currentPasswordHash = ph + } else { + currentPasswordHash = null + } + const ok = await loadSeedForWallet(id) + if (ok) accountType = 'xrp' + return ok + } + const saveWallet = async ( kind: 'create' | 'import', name: string, @@ -412,6 +434,7 @@ export const XrpWallet = () => { hashPassword, selectWallet, selectAndUnlockWallet, + autoUnlock, loadSeedForWallet, deleteWallet, clearSelection, diff --git a/apps/desktop/src/routes/index.svelte b/apps/desktop/src/routes/index.svelte index 888bf36..1f5a505 100644 --- a/apps/desktop/src/routes/index.svelte +++ b/apps/desktop/src/routes/index.svelte @@ -1,31 +1,125 @@ + +{#if !session.unlocked} +
+
+
+ +
+
+

Koins

+

+ Use Touch ID to unlock +

+
+
+ + {#if session.biometricAvailable} + + {:else} +

+ Biometric authentication isn't available on this device. +

+ {/if} + + {#if session.error} +

{session.error}

+ {/if} +
+{:else} +
+ + +
+
+
+ {title} +
+
+ +
+
+ {@render children?.()} +
+
+
+
+{/if} \ No newline at end of file diff --git a/apps/desktop/src/routes/monero/index.svelte b/apps/desktop/src/routes/monero/index.svelte index 73e4fd7..7d488bc 100644 --- a/apps/desktop/src/routes/monero/index.svelte +++ b/apps/desktop/src/routes/monero/index.svelte @@ -1,5 +1,6 @@ -
+
{#if !w.ready}

@@ -59,9 +60,6 @@ to get started

{:else if w.accountType === 'monero'} - - ← Back to home - {#if w.downloading} @@ -134,7 +132,19 @@
{#if moneroSelectedWallet}
- {#if w.biometricAvailable} + {#if session.unlocked} + + {:else if w.biometricAvailable} - +
Send XMR diff --git a/apps/desktop/src/routes/monero/settings.svelte b/apps/desktop/src/routes/monero/settings.svelte index 8a00758..a5b025d 100644 --- a/apps/desktop/src/routes/monero/settings.svelte +++ b/apps/desktop/src/routes/monero/settings.svelte @@ -21,7 +21,7 @@ }) -
+
@@ -82,14 +82,7 @@

{/if} -
- -
+
diff --git a/apps/desktop/src/routes/monero/tx/[hash].svelte b/apps/desktop/src/routes/monero/tx/[hash].svelte index cf433cf..9fadf77 100644 --- a/apps/desktop/src/routes/monero/tx/[hash].svelte +++ b/apps/desktop/src/routes/monero/tx/[hash].svelte @@ -35,15 +35,7 @@ }) -
- - +
Transaction Details diff --git a/apps/desktop/src/routes/monero/wallet/[name].svelte b/apps/desktop/src/routes/monero/wallet/[name].svelte index 22a7f9d..feba7f4 100644 --- a/apps/desktop/src/routes/monero/wallet/[name].svelte +++ b/apps/desktop/src/routes/monero/wallet/[name].svelte @@ -146,7 +146,7 @@ }) -
+
{@const a = chunkAddress(w.address)} diff --git a/apps/desktop/src/routes/settings.svelte b/apps/desktop/src/routes/settings.svelte index c1c3e19..2b348c4 100644 --- a/apps/desktop/src/routes/settings.svelte +++ b/apps/desktop/src/routes/settings.svelte @@ -33,23 +33,9 @@ } -
+
-
- -
- {#if w.wallets.length > 0} +{#if w.wallets.length > 0} Wallets diff --git a/apps/desktop/src/routes/xrp/index.svelte b/apps/desktop/src/routes/xrp/index.svelte index ee25b52..fbeed31 100644 --- a/apps/desktop/src/routes/xrp/index.svelte +++ b/apps/desktop/src/routes/xrp/index.svelte @@ -1,5 +1,6 @@ -
+
{#if !w.ready}

@@ -75,11 +76,6 @@ to get started

{:else} - - ← Back to home - {#if newSeed} @@ -177,7 +173,9 @@ {#each w.wallets as wal} - +
Send XRP diff --git a/apps/desktop/src/routes/xrp/tx/[hash].svelte b/apps/desktop/src/routes/xrp/tx/[hash].svelte index 3c7e22a..3c41faa 100644 --- a/apps/desktop/src/routes/xrp/tx/[hash].svelte +++ b/apps/desktop/src/routes/xrp/tx/[hash].svelte @@ -35,18 +35,7 @@ }) -
- - +
Transaction Details diff --git a/apps/desktop/src/routes/xrp/wallet/[id].svelte b/apps/desktop/src/routes/xrp/wallet/[id].svelte index 3002344..61d92c7 100644 --- a/apps/desktop/src/routes/xrp/wallet/[id].svelte +++ b/apps/desktop/src/routes/xrp/wallet/[id].svelte @@ -55,7 +55,7 @@ }) -
+