add sweepAll for Monero send-all; add copy buttons for accounts/subaddresses

This commit is contained in:
2026-05-26 13:09:18 +08:00
parent 915f6f36f6
commit 34602a4bfa
7 changed files with 113 additions and 10 deletions
+1
View File
@@ -16,6 +16,7 @@ export {
getFeeEstimate,
getDaemonHeight,
transfer,
sweepAll,
isWalletOpen,
isConnected,
listWallets,
+27
View File
@@ -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,
+25
View File
@@ -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)
+1
View File
@@ -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 }
}
@@ -293,6 +293,21 @@ export const MoneroWallet = () => {
return result
}
const sendAll = async (
address: string,
priority?: number,
accountIndex?: number,
): Promise<MoneroSendResult> => {
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,
}
}
@@ -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}
</p>
<button
onclick={() => copyToClipboard(account.primaryAddress)}
class="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1 transition-colors">
<Copy size={12} /> Copy address
</button>
{#if account.subaddresses.length > 1}
<div class="space-y-1 border-t border-border pt-2">
<p class="text-xs text-muted-foreground mb-1">
@@ -490,6 +502,11 @@
class="font-mono text-muted-foreground/50 truncate mt-0.5">
{sub.address}
</p>
<button
onclick={() => copyToClipboard(sub.address)}
class="mt-1 text-xs text-muted-foreground hover:text-foreground flex items-center gap-1 transition-colors">
<Copy size={12} /> Copy
</button>
</div>
</div>
</div>
+26 -10
View File
@@ -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} />
<span
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
XMR
</span>
</div>
{#if exceedsBalance}
<label class="flex items-center gap-2 text-xs">
<input
type="checkbox"
bind:checked={sendAll}
class="h-3.5 w-3.5" />
Send all unlocked XMR (fee deducted automatically)
</label>
{#if exceedsBalance && !sendAll}
<p class="text-xs text-red-500">
Amount exceeds unlocked balance
</p>