add sweepAll for Monero send-all; add copy buttons for accounts/subaddresses
This commit is contained in:
@@ -16,6 +16,7 @@ export {
|
|||||||
getFeeEstimate,
|
getFeeEstimate,
|
||||||
getDaemonHeight,
|
getDaemonHeight,
|
||||||
transfer,
|
transfer,
|
||||||
|
sweepAll,
|
||||||
isWalletOpen,
|
isWalletOpen,
|
||||||
isConnected,
|
isConnected,
|
||||||
listWallets,
|
listWallets,
|
||||||
|
|||||||
@@ -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(
|
export async function transfer(
|
||||||
state: MoneroWalletState,
|
state: MoneroWalletState,
|
||||||
address: string,
|
address: string,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
getDaemonHeight as walletGetDaemonHeight,
|
getDaemonHeight as walletGetDaemonHeight,
|
||||||
getFeeEstimate as walletGetFeeEstimate,
|
getFeeEstimate as walletGetFeeEstimate,
|
||||||
transfer as walletTransfer,
|
transfer as walletTransfer,
|
||||||
|
sweepAll as walletSweepAll,
|
||||||
isWalletOpen,
|
isWalletOpen,
|
||||||
isConnected,
|
isConnected,
|
||||||
} from '../../lib/monero'
|
} from '../../lib/monero'
|
||||||
@@ -231,6 +232,30 @@ export function createMoneroHandlers(state: {
|
|||||||
console.log('[rpc] moneroTransfer complete:', result.txHash)
|
console.log('[rpc] moneroTransfer complete:', result.txHash)
|
||||||
return result
|
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 }) => {
|
moneroGetTransferDetails: async ({ txid }: { txid: string }) => {
|
||||||
console.log('[rpc] moneroGetTransferDetails:', txid)
|
console.log('[rpc] moneroGetTransferDetails:', txid)
|
||||||
if (!state.manager)
|
if (!state.manager)
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export type MoneroRpcRequests = {
|
|||||||
moneroGetAccounts: { params: {}; response: MoneroAccountEntry[] }
|
moneroGetAccounts: { params: {}; response: MoneroAccountEntry[] }
|
||||||
moneroListWallets: { params: {}; response: string[] }
|
moneroListWallets: { params: {}; response: string[] }
|
||||||
moneroTransfer: { params: { address: string; amount: string; priority?: number; accountIndex?: number }; response: MoneroSendResult }
|
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 }
|
moneroGetTransferDetails: { params: { txid: string }; response: MoneroTransferDetails | null }
|
||||||
moneroGetFeeEstimate: { params: {}; response: MoneroFeeEstimate | null }
|
moneroGetFeeEstimate: { params: {}; response: MoneroFeeEstimate | null }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -293,6 +293,21 @@ export const MoneroWallet = () => {
|
|||||||
return result
|
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 {
|
return {
|
||||||
get accountType() {
|
get accountType() {
|
||||||
return accountType
|
return accountType
|
||||||
@@ -396,6 +411,7 @@ export const MoneroWallet = () => {
|
|||||||
openWallet,
|
openWallet,
|
||||||
refresh,
|
refresh,
|
||||||
send,
|
send,
|
||||||
|
sendAll,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,19 @@
|
|||||||
import ArrowUp from '@lucide/svelte/icons/arrow-up'
|
import ArrowUp from '@lucide/svelte/icons/arrow-up'
|
||||||
import Fingerprint from '@lucide/svelte/icons/fingerprint'
|
import Fingerprint from '@lucide/svelte/icons/fingerprint'
|
||||||
import SettingsIcon from '@lucide/svelte/icons/settings'
|
import SettingsIcon from '@lucide/svelte/icons/settings'
|
||||||
|
import Copy from '@lucide/svelte/icons/copy'
|
||||||
import Loader from '$lib/components/loader.svelte'
|
import Loader from '$lib/components/loader.svelte'
|
||||||
import QrCode from '$lib/components/qr.svelte'
|
import QrCode from '$lib/components/qr.svelte'
|
||||||
import { navigate } from 'sv-router/generated'
|
import { navigate } from 'sv-router/generated'
|
||||||
|
|
||||||
const w = moneroWallet
|
const w = moneroWallet
|
||||||
|
|
||||||
|
const copyToClipboard = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
let moneroWalletName = $state('')
|
let moneroWalletName = $state('')
|
||||||
let moneroWalletPass = $state('')
|
let moneroWalletPass = $state('')
|
||||||
let moneroMnemonic = $state('')
|
let moneroMnemonic = $state('')
|
||||||
@@ -464,6 +471,11 @@
|
|||||||
class="mb-2 font-mono text-xs text-muted-foreground break-all">
|
class="mb-2 font-mono text-xs text-muted-foreground break-all">
|
||||||
{account.primaryAddress}
|
{account.primaryAddress}
|
||||||
</p>
|
</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}
|
{#if account.subaddresses.length > 1}
|
||||||
<div class="space-y-1 border-t border-border pt-2">
|
<div class="space-y-1 border-t border-border pt-2">
|
||||||
<p class="text-xs text-muted-foreground mb-1">
|
<p class="text-xs text-muted-foreground mb-1">
|
||||||
@@ -490,6 +502,11 @@
|
|||||||
class="font-mono text-muted-foreground/50 truncate mt-0.5">
|
class="font-mono text-muted-foreground/50 truncate mt-0.5">
|
||||||
{sub.address}
|
{sub.address}
|
||||||
</p>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
let recipient = $state('')
|
let recipient = $state('')
|
||||||
let amountXmr = $state('')
|
let amountXmr = $state('')
|
||||||
|
let sendAll = $state(false)
|
||||||
let priority = $state('0')
|
let priority = $state('0')
|
||||||
let accountIndex = $state(
|
let accountIndex = $state(
|
||||||
w.accounts.length > 0 ? String(w.accounts[0].index) : '0',
|
w.accounts.length > 0 ? String(w.accounts[0].index) : '0',
|
||||||
@@ -57,8 +58,8 @@
|
|||||||
const canSend = $derived(
|
const canSend = $derived(
|
||||||
recipient.startsWith('4') &&
|
recipient.startsWith('4') &&
|
||||||
recipient.length >= 95 &&
|
recipient.length >= 95 &&
|
||||||
amountAtomic > 0n &&
|
(sendAll || amountAtomic > 0n) &&
|
||||||
!exceedsBalance &&
|
(sendAll || !exceedsBalance) &&
|
||||||
!sending,
|
!sending,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,15 +68,22 @@
|
|||||||
sendError = ''
|
sendError = ''
|
||||||
sentResult = null
|
sentResult = null
|
||||||
try {
|
try {
|
||||||
const result = await w.send(
|
const result = sendAll
|
||||||
recipient,
|
? await w.sendAll(
|
||||||
amountAtomic.toString(),
|
recipient,
|
||||||
parseInt(priority),
|
parseInt(priority),
|
||||||
parseInt(accountIndex),
|
parseInt(accountIndex),
|
||||||
)
|
)
|
||||||
|
: await w.send(
|
||||||
|
recipient,
|
||||||
|
amountAtomic.toString(),
|
||||||
|
parseInt(priority),
|
||||||
|
parseInt(accountIndex),
|
||||||
|
)
|
||||||
sentResult = { txHash: result.txHash, fee: result.fee }
|
sentResult = { txHash: result.txHash, fee: result.fee }
|
||||||
recipient = ''
|
recipient = ''
|
||||||
amountXmr = ''
|
amountXmr = ''
|
||||||
|
sendAll = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendError = e instanceof Error ? e.message : 'Send failed'
|
sendError = e instanceof Error ? e.message : 'Send failed'
|
||||||
} finally {
|
} finally {
|
||||||
@@ -148,13 +156,21 @@
|
|||||||
step="0.000000000001"
|
step="0.000000000001"
|
||||||
min="0"
|
min="0"
|
||||||
placeholder="0.0"
|
placeholder="0.0"
|
||||||
bind:value={amountXmr} />
|
bind:value={amountXmr}
|
||||||
|
disabled={sendAll} />
|
||||||
<span
|
<span
|
||||||
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
|
||||||
XMR
|
XMR
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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">
|
<p class="text-xs text-red-500">
|
||||||
Amount exceeds unlocked balance
|
Amount exceeds unlocked balance
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Reference in New Issue
Block a user