From 2cbf3bb33bb20b58ee79ac383446b96e5219af02 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Wed, 27 May 2026 13:19:23 +0800 Subject: [PATCH] extract monero wallet dashboard into dedicated /monero/wallet/[name] route --- AGENTS.md | 4 + .../src/lib/states/monero-wallet.svelte.ts | 22 +- apps/desktop/src/routes/monero/index.svelte | 570 +----------------- .../src/routes/monero/wallet/[name].svelte | 559 +++++++++++++++++ 4 files changed, 588 insertions(+), 567 deletions(-) create mode 100644 AGENTS.md create mode 100644 apps/desktop/src/routes/monero/wallet/[name].svelte diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..46ba803 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,4 @@ +# Instructions for AI assistants + +- Never commit changes unless explicitly told to do so. Always ask first or wait for the user's command. +- Never manually edit `.router/router.ts` — sv-router auto-generates it from the filesystem. Just create the route file in the correct filesystem location. diff --git a/apps/desktop/src/lib/states/monero-wallet.svelte.ts b/apps/desktop/src/lib/states/monero-wallet.svelte.ts index 9928ffb..828e551 100644 --- a/apps/desktop/src/lib/states/monero-wallet.svelte.ts +++ b/apps/desktop/src/lib/states/monero-wallet.svelte.ts @@ -25,10 +25,8 @@ export const atomicToXmr = (atomic: string): string => { return frac ? `${whole}.${frac}` : `${whole}` } -export type AccountType = 'multi' | 'monero' - export const MoneroWallet = () => { - let accountType = $state(null) + let accountType = $state<'monero' | null>(null) let ready = $state(false) let biometricAvailable = $state(false) let running = $state(false) @@ -310,17 +308,23 @@ export const MoneroWallet = () => { const createAccount = async (label?: string) => { if (!electrobun.rpc) throw new Error('RPC not available') - const result = await electrobun.rpc.request.moneroCreateAccount({ label }) + const result = await electrobun.rpc.request.moneroCreateAccount({ + label, + }) await refresh() return result } - const createSubaddress = async (accountIndex: number, label?: string) => { + const createSubaddress = async ( + accountIndex: number, + label?: string, + ) => { if (!electrobun.rpc) throw new Error('RPC not available') - const result = await electrobun.rpc.request.moneroCreateSubaddress({ - accountIndex, - label, - }) + const result = + await electrobun.rpc.request.moneroCreateSubaddress({ + accountIndex, + label, + }) await refresh() return result } diff --git a/apps/desktop/src/routes/monero/index.svelte b/apps/desktop/src/routes/monero/index.svelte index 11ff176..b77e588 100644 --- a/apps/desktop/src/routes/monero/index.svelte +++ b/apps/desktop/src/routes/monero/index.svelte @@ -1,19 +1,8 @@
@@ -204,9 +139,6 @@ await w.openExistingWallet( moneroSelectedWallet, ) - if (!w.walletOpen) - w.error = - 'No password saved for this wallet' } }} disabled={w.loading}> @@ -261,12 +193,11 @@ const pw = moneroUseBiometric ? crypto.randomUUID() : moneroWalletPass - const result = await w.createWallet( + await w.createWallet( moneroWalletName, pw, moneroUseBiometric, ) - moneroMnemonic = result.mnemonic }} disabled={w.loading || !moneroWalletName || @@ -292,8 +223,8 @@ bind:value={moneroWalletPass} /> {/if}
- {:else} - - {@const a = chunkAddress(w.address)} - - -
- Monero Wallet -
-
-
- - {w.connected ? 'Connected' : 'Disconnected'} - -
-
- - {#if w.height < w.daemonHeight} - Scanning: {w.height.toLocaleString()} / {w.daemonHeight.toLocaleString()} - {:else} - Height: {w.height.toLocaleString()} - {/if} - -
- -
-
-
-

Address

-

- - {a.start} - - {a.mid} - ... - - {a.end} - -

-
- - - - - -
- -

- {w.address} -

-
-
-
- - -
-
-
-

Balance

-

{w.balance} XMR

- {#if w.unlockedAtomic !== w.balAtomic} -

- Unlocked: {w.unlocked} XMR -

- {/if} - {#if feeEstimate} - {@const labels = [ - 'Unimportant', - 'Normal', - 'Elevated', - 'Priority', - ]} -
- {#each feeEstimate.fees as f, i} - {@const feeAtomic = (BigInt(f) * 2500n) / 1000n} -

- {labels[i]}: ~{atomicToXmr( - feeAtomic.toString(), - )} XMR -

- {/each} -
- {/if} -
- - {#if moneroMnemonic} -
-

- Seed phrase (save this!) -

-

- {moneroMnemonic} -

-
- {/if} - -
- - - -
-
-
- - - - - Transactions - {#if w.accounts.length > 0} - -
- {#each w.accounts as acct} - - {/each} -
-
- {/if} -
- - {#if w.loading} -
- -
- {:else if w.txs.length === 0} -

- No transactions found -

- {:else} -
- {#each w.txs as tx} - - {/each} -
- {/if} -
-
- - - - - Accounts & Addresses - - {w.accounts.length} account{w.accounts.length !== 1 - ? 's' - : ''} - - - -
- - - - - - - Create Account - - Optional label for the new account - - -
- -
- - -
-
-
-
- - - - - - - Create Subaddress - - Account index and optional label - - -
-
- - -
- -
- - -
-
-
-
-
- {#each w.accounts as account} - {@const a = chunkAddress(account.primaryAddress)} -
-
-

- Account {account.index} -

-

- {atomicToXmr(account.balance)} XMR -

-
-

- - {a.start} - - {a.mid} - ... - {a.end} -

-
- { - subQrDialogOpen = v - ? account.primaryAddress - : null - }}> - - - - -
- -

- {account.primaryAddress} -

-
-
-
- - - -
- {#if account.subaddresses.length > 1} -
-

- Subaddresses -

- {#each account.subaddresses as sub} - {@const a = chunkAddress(sub.address)} -
-
-
-
-

- #{sub.index}{sub.label - ? ` - ${sub.label}` - : ''} -

-

- {atomicToXmr(sub.balance)} XMR -

-
-

- - {a.start} - - - {a.mid} - - - ... - - - {a.end} - -

-
- { - subQrDialogOpen = v - ? sub.address - : null - }}> - - - - -
- -

- {sub.address} -

-
-
-
- - -
-
-
-
- {/each} -
- {/if} -
- {/each} -
-
{/if} {/if} diff --git a/apps/desktop/src/routes/monero/wallet/[name].svelte b/apps/desktop/src/routes/monero/wallet/[name].svelte new file mode 100644 index 0000000..11ccd4e --- /dev/null +++ b/apps/desktop/src/routes/monero/wallet/[name].svelte @@ -0,0 +1,559 @@ + + +
+
+ + {@const a = chunkAddress(w.address)} + +
+ Monero Wallet +
+
+
+ + {w.connected ? 'Connected' : 'Disconnected'} + +
+
+ + {#if w.height < w.daemonHeight} + Scanning: {w.height.toLocaleString()} / {w.daemonHeight.toLocaleString()} + {:else} + Height: {w.height.toLocaleString()} + {/if} + +
+ +
+
+
+

Address

+

+ + {a.start} + + {a.mid} + ... + + {a.end} + +

+
+ + + + + +
+ +

+ {w.address} +

+
+
+
+ + +
+
+
+

Balance

+

{w.balance} XMR

+ {#if w.unlockedAtomic !== w.balAtomic} +

+ Unlocked: {w.unlocked} XMR +

+ {/if} + {#if feeEstimate} + {@const labels = [ + 'Unimportant', + 'Normal', + 'Elevated', + 'Priority', + ]} +
+ {#each feeEstimate.fees as f, i} + {@const feeAtomic = (BigInt(f) * 2500n) / 1000n} +

+ {labels[i]}: ~{atomicToXmr(feeAtomic.toString())} XMR +

+ {/each} +
+ {/if} +
+ + {#if moneroMnemonic} +
+

+ Seed phrase (save this!) +

+

+ {moneroMnemonic} +

+
+ {/if} + +
+ + + +
+
+
+ + + + + Transactions + {#if w.accounts.length > 0} + +
+ {#each w.accounts as acct} + + {/each} +
+
+ {/if} +
+ + {#if w.loading} +
+ +
+ {:else if w.txs.length === 0} +

+ No transactions found +

+ {:else} +
+ {#each w.txs as tx} + + {/each} +
+ {/if} +
+
+ + + + + Accounts & Addresses + + {w.accounts.length} account{w.accounts.length !== 1 + ? 's' + : ''} + + + +
+ + + + + + + Create Account + + Optional label for the new account + + +
+ +
+ + +
+
+
+
+ + + + + + + Create Subaddress + + Account index and optional label + + +
+
+ + +
+ +
+ + +
+
+
+
+
+ {#each w.accounts as account} + {@const a = chunkAddress(account.primaryAddress)} +
+
+

+ Account {account.index} +

+

+ {atomicToXmr(account.balance)} XMR +

+
+

+ + {a.start} + + {a.mid} + ... + {a.end} +

+
+ { + subQrDialogOpen = v ? account.primaryAddress : null + }}> + + + + +
+ +

+ {account.primaryAddress} +

+
+
+
+ + + +
+ {#if account.subaddresses.length > 1} +
+

+ Subaddresses +

+ {#each account.subaddresses as sub} + {@const a = chunkAddress(sub.address)} +
+
+
+
+

+ #{sub.index}{sub.label + ? ` - ${sub.label}` + : ''} +

+

+ {atomicToXmr(sub.balance)} XMR +

+
+

+ + {a.start} + + + {a.mid} + + + ... + + + {a.end} + +

+
+ { + subQrDialogOpen = v ? sub.address : null + }}> + + + + +
+ +

+ {sub.address} +

+
+
+
+ + +
+
+
+
+ {/each} +
+ {/if} +
+ {/each} +
+
+
+