Add Monero send page with transfer RPC and account selection

This commit is contained in:
2026-05-24 19:43:32 +08:00
parent 30d8abf26b
commit cc32fdb20f
7 changed files with 280 additions and 1 deletions
@@ -219,6 +219,9 @@
{/if}
Refresh
</Button>
<Button variant="outline" onclick={() => navigate('/monero/send')}>
Send
</Button>
<Button variant="outline" onclick={() => navigate('/monero/settings')}>
<SettingsIcon size={16} />
</Button>
+209
View File
@@ -0,0 +1,209 @@
<script lang="ts">
import {
moneroWallet,
atomicToXmr,
} from '$lib/states/monero-wallet.svelte.js'
import { Button } from '$lib/components/ui/button/index.js'
import { Input } from '$lib/components/ui/input/index.js'
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
} from '$lib/components/ui/card/index.js'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from '$lib/components/ui/select/index.js'
import { navigate } from 'sv-router/generated'
const w = moneroWallet
let recipient = $state('')
let amountXmr = $state('')
let priority = $state('0')
let accountIndex = $state(
w.accounts.length > 0 ? String(w.accounts[0].index) : '0',
)
let sending = $state(false)
let sentResult = $state<{ txHash: string; fee: string } | null>(
null,
)
let sendError = $state('')
const selectedAccount = $derived(
w.accounts.find((a) => String(a.index) === accountIndex),
)
const unlockedAtomic = $derived(
selectedAccount?.unlockedBalance ?? w.unlockedAtomic,
)
const balanceAtomic = $derived(
selectedAccount?.balance ?? w.balAtomic,
)
const amountAtomic = $derived.by(() => {
if (!amountXmr) return 0n
const num = parseFloat(amountXmr)
if (isNaN(num) || num <= 0) return 0n
return BigInt(Math.round(num * 1e12))
})
const exceedsBalance = $derived(
amountAtomic > BigInt(unlockedAtomic),
)
const canSend = $derived(
recipient.startsWith('4') &&
recipient.length >= 95 &&
amountAtomic > 0n &&
!exceedsBalance &&
!sending,
)
const handleSend = async () => {
sending = true
sendError = ''
sentResult = null
try {
const result = await w.send(
recipient,
amountAtomic.toString(),
parseInt(priority),
parseInt(accountIndex),
)
sentResult = { txHash: result.txHash, fee: result.fee }
recipient = ''
amountXmr = ''
} catch (e) {
sendError = e instanceof Error ? e.message : 'Send failed'
} finally {
sending = false
}
}
</script>
<div class="mx-auto mt-16 max-w-md">
<Button
variant="outline"
size="sm"
onclick={() => navigate('/monero')}
class="mb-4">
&larr; Back
</Button>
<Card>
<CardHeader>
<CardTitle>Send XMR</CardTitle>
<CardDescription>
Balance: {atomicToXmr(unlockedAtomic)} XMR
{#if unlockedAtomic !== balanceAtomic}
(locked: {atomicToXmr(
(
BigInt(balanceAtomic) - BigInt(unlockedAtomic)
).toString(),
)} XMR)
{/if}
</CardDescription>
</CardHeader>
<CardContent>
<form
onsubmit={(e) => {
e.preventDefault()
handleSend()
}}
class="space-y-4">
<div class="space-y-1.5">
<label class="text-xs font-medium">From Account</label>
<Select type="single" bind:value={accountIndex}>
<SelectTrigger>
{accountIndex}
</SelectTrigger>
<SelectContent>
{#each w.accounts as acct}
<SelectItem value={String(acct.index)}>
Account {acct.index}{acct.label
? ` - ${acct.label}`
: ''}
({atomicToXmr(acct.unlockedBalance)} XMR)
</SelectItem>
{/each}
</SelectContent>
</Select>
</div>
<div class="space-y-1.5">
<label class="text-xs font-medium">Recipient Address</label>
<Input
placeholder="Monero address starting with 4..."
bind:value={recipient} />
</div>
<div class="space-y-1.5">
<label class="text-xs font-medium">Amount (XMR)</label>
<div class="relative">
<Input
type="number"
step="0.000000000001"
min="0"
placeholder="0.0"
bind:value={amountXmr} />
<span
class="absolute right-3 top-1/2 -translate-y-1/2 text-xs text-muted-foreground">
XMR
</span>
</div>
{#if exceedsBalance}
<p class="text-xs text-red-500">
Amount exceeds unlocked balance
</p>
{/if}
<p class="text-xs text-muted-foreground">
{new Intl.NumberFormat().format(Number(amountAtomic))} atomic
units
</p>
</div>
<div class="space-y-1.5">
<label class="text-xs font-medium">Priority</label>
<Select type="single" bind:value={priority}>
<SelectTrigger>
{priority}
</SelectTrigger>
<SelectContent>
<SelectItem value="0">Default</SelectItem>
<SelectItem value="1">Unimportant</SelectItem>
<SelectItem value="2">Normal</SelectItem>
<SelectItem value="3">Elevated</SelectItem>
<SelectItem value="4">Priority</SelectItem>
</SelectContent>
</Select>
</div>
<Button type="submit" disabled={!canSend} class="w-full">
{sending ? 'Sending...' : 'Send'}
</Button>
</form>
{#if sendError}
<p class="mt-3 text-xs text-red-500">{sendError}</p>
{/if}
{#if sentResult}
<div
class="mt-4 space-y-2 rounded-md border border-green-500/30 bg-green-500/5 p-3">
<p class="text-xs font-medium text-green-600">
Transaction Sent
</p>
<p class="font-mono text-xs break-all">
Hash: {sentResult.txHash}
</p>
<p class="font-mono text-xs">
Fee: {atomicToXmr(sentResult.fee)} XMR
</p>
</div>
{/if}
</CardContent>
</Card>
</div>