From 34602a4bfaa052eac0e2fedd2439a3926bd3f971 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Tue, 26 May 2026 13:09:18 +0800 Subject: [PATCH] add sweepAll for Monero send-all; add copy buttons for accounts/subaddresses --- apps/desktop/src/bun/lib/monero/index.ts | 1 + apps/desktop/src/bun/lib/monero/wallet.ts | 27 ++++++++++++++ apps/desktop/src/bun/rpc/monero/index.ts | 25 +++++++++++++ apps/desktop/src/lib/rpc-schema.ts | 1 + .../src/lib/states/monero-wallet.svelte.ts | 16 +++++++++ apps/desktop/src/routes/monero/index.svelte | 17 +++++++++ apps/desktop/src/routes/monero/send.svelte | 36 +++++++++++++------ 7 files changed, 113 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/bun/lib/monero/index.ts b/apps/desktop/src/bun/lib/monero/index.ts index 9621ea3..fe4ccae 100644 --- a/apps/desktop/src/bun/lib/monero/index.ts +++ b/apps/desktop/src/bun/lib/monero/index.ts @@ -16,6 +16,7 @@ export { getFeeEstimate, getDaemonHeight, transfer, + sweepAll, isWalletOpen, isConnected, listWallets, diff --git a/apps/desktop/src/bun/lib/monero/wallet.ts b/apps/desktop/src/bun/lib/monero/wallet.ts index 27e0f6b..f68be4f 100644 --- a/apps/desktop/src/bun/lib/monero/wallet.ts +++ b/apps/desktop/src/bun/lib/monero/wallet.ts @@ -423,6 +423,33 @@ export async function getDaemonHeight( } } +export async function sweepAll( + state: MoneroWalletState, + address: string, + priority: number = 0, + accountIndex: number = 0, +): Promise<{ txHash: string; fee: string; amount: string }> { + if (!state.wallet) throw new Error('Wallet RPC not started') + console.log( + `[monero] sweepAll: ${address} priority=${priority} account=${accountIndex}`, + ) + const result = await rawRpc(state, 'sweep_all', { + address, + priority, + account_index: accountIndex, + get_tx_keys: true, + get_tx_hex: false, + get_tx_metadata: false, + }) + const txHash = result.tx_hash_list?.[0] ?? '' + const fee = result.fee_list?.[0]?.toString() ?? '0' + const amount = result.amount_list?.[0]?.toString() ?? '0' + console.log( + `[monero] sweepAll result: tx_hash=${txHash} fee=${fee} amount=${amount}`, + ) + return { txHash, fee, amount } +} + export async function transfer( state: MoneroWalletState, address: string, diff --git a/apps/desktop/src/bun/rpc/monero/index.ts b/apps/desktop/src/bun/rpc/monero/index.ts index 655fb17..fe43338 100644 --- a/apps/desktop/src/bun/rpc/monero/index.ts +++ b/apps/desktop/src/bun/rpc/monero/index.ts @@ -17,6 +17,7 @@ import { getDaemonHeight as walletGetDaemonHeight, getFeeEstimate as walletGetFeeEstimate, transfer as walletTransfer, + sweepAll as walletSweepAll, isWalletOpen, isConnected, } from '../../lib/monero' @@ -231,6 +232,30 @@ export function createMoneroHandlers(state: { console.log('[rpc] moneroTransfer complete:', result.txHash) return result }, + moneroSweepAll: async ({ + address, + priority, + accountIndex, + }: { + address: string + priority?: number + accountIndex?: number + }) => { + console.log('[rpc] moneroSweepAll:', { + address, + priority, + }) + if (!state.manager) + throw new Error('Monero wallet RPC not started') + const result = await walletSweepAll( + state.manager, + address, + priority ?? 0, + accountIndex ?? 0, + ) + console.log('[rpc] moneroSweepAll complete:', result.txHash) + return result + }, moneroGetTransferDetails: async ({ txid }: { txid: string }) => { console.log('[rpc] moneroGetTransferDetails:', txid) if (!state.manager) diff --git a/apps/desktop/src/lib/rpc-schema.ts b/apps/desktop/src/lib/rpc-schema.ts index e1bec27..8b56554 100644 --- a/apps/desktop/src/lib/rpc-schema.ts +++ b/apps/desktop/src/lib/rpc-schema.ts @@ -179,6 +179,7 @@ export type MoneroRpcRequests = { moneroGetAccounts: { params: {}; response: MoneroAccountEntry[] } moneroListWallets: { params: {}; response: string[] } moneroTransfer: { params: { address: string; amount: string; priority?: number; accountIndex?: number }; response: MoneroSendResult } + moneroSweepAll: { params: { address: string; priority?: number; accountIndex?: number }; response: MoneroSendResult } moneroGetTransferDetails: { params: { txid: string }; response: MoneroTransferDetails | null } moneroGetFeeEstimate: { params: {}; response: MoneroFeeEstimate | null } } diff --git a/apps/desktop/src/lib/states/monero-wallet.svelte.ts b/apps/desktop/src/lib/states/monero-wallet.svelte.ts index 62a1bae..004c14f 100644 --- a/apps/desktop/src/lib/states/monero-wallet.svelte.ts +++ b/apps/desktop/src/lib/states/monero-wallet.svelte.ts @@ -293,6 +293,21 @@ export const MoneroWallet = () => { return result } + const sendAll = async ( + address: string, + priority?: number, + accountIndex?: number, + ): Promise => { + if (!electrobun.rpc) throw new Error('RPC not available') + const result = await electrobun.rpc.request.moneroSweepAll({ + address, + priority, + accountIndex, + }) + await refresh() + return result + } + return { get accountType() { return accountType @@ -396,6 +411,7 @@ export const MoneroWallet = () => { openWallet, refresh, send, + sendAll, } } diff --git a/apps/desktop/src/routes/monero/index.svelte b/apps/desktop/src/routes/monero/index.svelte index 580cee2..d0b0348 100644 --- a/apps/desktop/src/routes/monero/index.svelte +++ b/apps/desktop/src/routes/monero/index.svelte @@ -18,12 +18,19 @@ import ArrowUp from '@lucide/svelte/icons/arrow-up' import Fingerprint from '@lucide/svelte/icons/fingerprint' import SettingsIcon from '@lucide/svelte/icons/settings' + import Copy from '@lucide/svelte/icons/copy' import Loader from '$lib/components/loader.svelte' import QrCode from '$lib/components/qr.svelte' import { navigate } from 'sv-router/generated' const w = moneroWallet + const copyToClipboard = async (text: string) => { + try { + await navigator.clipboard.writeText(text) + } catch {} + } + let moneroWalletName = $state('') let moneroWalletPass = $state('') let moneroMnemonic = $state('') @@ -464,6 +471,11 @@ class="mb-2 font-mono text-xs text-muted-foreground break-all"> {account.primaryAddress}

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

@@ -490,6 +502,11 @@ class="font-mono text-muted-foreground/50 truncate mt-0.5"> {sub.address}

+
diff --git a/apps/desktop/src/routes/monero/send.svelte b/apps/desktop/src/routes/monero/send.svelte index e378c30..7ff1a83 100644 --- a/apps/desktop/src/routes/monero/send.svelte +++ b/apps/desktop/src/routes/monero/send.svelte @@ -24,6 +24,7 @@ let recipient = $state('') let amountXmr = $state('') + let sendAll = $state(false) let priority = $state('0') let accountIndex = $state( w.accounts.length > 0 ? String(w.accounts[0].index) : '0', @@ -57,8 +58,8 @@ const canSend = $derived( recipient.startsWith('4') && recipient.length >= 95 && - amountAtomic > 0n && - !exceedsBalance && + (sendAll || amountAtomic > 0n) && + (sendAll || !exceedsBalance) && !sending, ) @@ -67,15 +68,22 @@ sendError = '' sentResult = null try { - const result = await w.send( - recipient, - amountAtomic.toString(), - parseInt(priority), - parseInt(accountIndex), - ) + const result = sendAll + ? await w.sendAll( + recipient, + parseInt(priority), + parseInt(accountIndex), + ) + : await w.send( + recipient, + amountAtomic.toString(), + parseInt(priority), + parseInt(accountIndex), + ) sentResult = { txHash: result.txHash, fee: result.fee } recipient = '' amountXmr = '' + sendAll = false } catch (e) { sendError = e instanceof Error ? e.message : 'Send failed' } finally { @@ -148,13 +156,21 @@ step="0.000000000001" min="0" placeholder="0.0" - bind:value={amountXmr} /> + bind:value={amountXmr} + disabled={sendAll} /> XMR - {#if exceedsBalance} + + {#if exceedsBalance && !sendAll}

Amount exceeds unlocked balance