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