remove multiple wallets + monero auto-open + agents rules

This commit is contained in:
2026-08-01 19:12:32 +08:00
parent bd8ba8e3e5
commit 5ac8c93027
7 changed files with 114 additions and 352 deletions
@@ -82,6 +82,10 @@ export const MoneroWallet = () => {
await checkStatus()
}
await listWallets()
if (wallets.length > 0) {
const pw = await moneroGetStoredPassword(wallets[0])
if (pw) await openExistingWallet(wallets[0], pw)
}
}
const logout = async () => {
@@ -80,7 +80,7 @@ export const XrpWallet = () => {
)
if (walletList) {
wallets = walletList
if (walletList.length === 1 && !currentWalletId) {
if (walletList.length > 0 && !currentWalletId) {
currentWalletId = walletList[0].id
}
}
+1 -34
View File
@@ -269,40 +269,7 @@
</button>
</div>
{#if xrpWallet.wallets.length > 1}
<Card>
<CardHeader class="pb-3">
<CardTitle class="text-sm">XRP Wallets</CardTitle>
</CardHeader>
<CardContent>
<div class="space-y-1">
{#each xrpWallet.wallets as wal}
<button
onclick={() => {
xrpWallet.selectAndUnlockWallet(wal.id)
navigate('/xrp')
}}
class="flex w-full items-center gap-3 rounded-lg p-2 text-left hover:bg-muted transition-colors">
<WalletIcon size={16} class="shrink-0 text-muted-foreground" />
<div class="flex-1 min-w-0">
<p class="text-sm font-medium">{wal.name}</p>
<p class="text-xs text-muted-foreground truncate font-mono">{wal.address}</p>
</div>
{#if xrpBalances[wal.address]?.funded === false}
<span class="text-xs text-muted-foreground">unfunded</span>
{:else if xrpBalances[wal.address]}
<span class="font-mono text-sm tabular-nums">
{fmtBal(xrpBalances[wal.address].balance)}
</span>
{/if}
</button>
{/each}
</div>
</CardContent>
</Card>
{/if}
<div class="flex gap-3">
<div class="flex gap-3">
<Button
onclick={() => navigate('/xrp')}
variant="outline"
+65 -158
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import { moneroWallet } from '$lib/states/monero-wallet.svelte.js'
import { session } from '$lib/states/session.svelte.js'
import { Button } from '$lib/components/ui/button/index.js'
import { Textarea } from '$lib/components/ui/textarea/index.js'
import { Input } from '$lib/components/ui/input/index.js'
@@ -11,28 +10,21 @@
CardHeader,
CardTitle,
} from '$lib/components/ui/card/index.js'
import Fingerprint from '@lucide/svelte/icons/fingerprint'
import Loader from '$lib/components/loader.svelte'
import { navigate } from 'sv-router/generated'
const w = moneroWallet
let moneroWalletName = $state('')
let moneroWalletPass = $state('')
let moneroMnemonic = $state('')
let moneroRestoreHeight = $state<number | undefined>(undefined)
let moneroSelectedWallet = $state('')
let moneroSelectedWalletPass = $state('')
let moneroUseBiometric = $state(false)
let initStarted = false
$effect(() => {
if (w.walletOpen && w.walletName) {
navigate('/monero/wallet/:name', {
params: {
name: w.walletName,
},
params: { name: w.walletName },
})
}
})
@@ -46,6 +38,12 @@
})
}
})
$effect(() => {
if (w.running && !w.walletOpen && w.wallets.length > 0 && !w.loading) {
w.autoUnlock(w.wallets[0])
}
})
</script>
<div class="mx-auto max-w-xl">
@@ -101,162 +99,71 @@
</CardContent>
</Card>
{:else if !w.walletOpen}
<Card>
<CardHeader>
<CardTitle>Monero Wallet</CardTitle>
<CardDescription>
{w.wallets.length > 0
? `${w.wallets.length} wallet${w.wallets.length > 1 ? 's' : ''} found on disk`
: 'No existing wallets found'}
</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-3">
{#if w.wallets.length > 0}
<p class="text-xs font-medium">
Select a wallet to open:
{#if w.wallets.length > 0}
<Card>
<CardContent class="flex items-center justify-center py-8">
<Loader />
<span class="ml-2 text-sm text-muted-foreground">
Opening wallet...
</span>
</CardContent>
</Card>
{:else}
<Card>
<CardHeader>
<CardTitle>Monero Wallet</CardTitle>
<CardDescription>
Create a new wallet or restore from seed
</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-3">
<p class="text-xs text-muted-foreground">
Create a new wallet:
</p>
<div class="flex flex-wrap gap-2">
{#each w.wallets as name}
<Button
variant={moneroSelectedWallet === name
? 'default'
: 'outline'}
onclick={() => {
moneroSelectedWallet = name
moneroSelectedWalletPass = ''
}}>
{name}
</Button>
{/each}
</div>
{#if moneroSelectedWallet}
<div class="flex flex-col gap-2">
{#if session.unlocked}
<Button
onclick={() =>
w.autoUnlock(
moneroSelectedWallet,
)}
disabled={w.loading}>
{#if w.loading}
<Loader />
{/if}
Open
</Button>
{:else if w.biometricAvailable}
<Button
onclick={async () => {
const ok = await w.biometricAuth()
if (ok) {
await w.openExistingWallet(
moneroSelectedWallet,
)
}
}}
disabled={w.loading}>
<Fingerprint size={14} />
Open with Touch ID
</Button>
{/if}
<div class="flex gap-2 items-end">
<Input
type="password"
placeholder="Enter password"
bind:value={moneroSelectedWalletPass} />
<Button
onclick={() =>
w.openExistingWallet(
moneroSelectedWallet,
moneroSelectedWalletPass,
)}
disabled={w.loading ||
!moneroSelectedWalletPass}>
Open
</Button>
</div>
</div>
{/if}
<hr class="border-muted" />
{/if}
<p class="text-xs text-muted-foreground">
Or create a new wallet:
</p>
<Input
placeholder="Wallet name"
bind:value={moneroWalletName} />
{#if w.biometricAvailable}
<label
class="flex items-center gap-2 text-xs cursor-pointer">
<input
type="checkbox"
bind:checked={moneroUseBiometric} />
Save password with Touch ID
</label>
{/if}
{#if !moneroUseBiometric}
<Input
type="password"
placeholder="Password"
bind:value={moneroWalletPass} />
{/if}
<div class="flex gap-2">
placeholder="Wallet name"
bind:value={moneroWalletName} />
<Button
onclick={async () => {
const pw = moneroUseBiometric
? crypto.randomUUID()
: moneroWalletPass
await w.createWallet(
onclick={() =>
w.createWallet(
moneroWalletName,
pw,
moneroUseBiometric,
)
}}
disabled={w.loading ||
!moneroWalletName ||
(!moneroUseBiometric && !moneroWalletPass)}>
crypto.randomUUID(),
true,
)}
disabled={w.loading || !moneroWalletName}>
Create Wallet
</Button>
</div>
<hr class="border-muted" />
<p class="text-xs text-muted-foreground">
Restore from seed
</p>
<Textarea
placeholder="Enter your Monero seed phrase (16 or 25 words)"
bind:value={moneroMnemonic} />
<Input
type="number"
placeholder="Restore height (optional)"
bind:value={moneroRestoreHeight} />
{#if !moneroUseBiometric}
<hr class="border-muted" />
<p class="text-xs text-muted-foreground">
Restore from seed
</p>
<Textarea
placeholder="Enter your Monero seed phrase (16 or 25 words)"
bind:value={moneroMnemonic} />
<Input
type="password"
placeholder="Password"
bind:value={moneroWalletPass} />
{/if}
<Button
onclick={async () => {
await w.restoreWallet(
moneroWalletName,
moneroUseBiometric
? crypto.randomUUID()
: moneroWalletPass,
moneroMnemonic,
moneroRestoreHeight,
moneroUseBiometric,
)
}}
disabled={w.loading ||
!moneroWalletName ||
(!moneroUseBiometric && !moneroWalletPass) ||
!moneroMnemonic.trim()}>
Restore Wallet
</Button>
</div>
</CardContent>
</Card>
type="number"
placeholder="Restore height (optional)"
bind:value={moneroRestoreHeight} />
<Button
onclick={() =>
w.restoreWallet(
moneroWalletName,
crypto.randomUUID(),
moneroMnemonic,
moneroRestoreHeight,
true,
)}
disabled={w.loading ||
!moneroWalletName ||
!moneroMnemonic.trim()}>
Restore Wallet
</Button>
</div>
</CardContent>
</Card>
{/if}
{/if}
{/if}
</div>
</div>
</div>
+31 -80
View File
@@ -9,14 +9,10 @@
CardTitle,
} from '$lib/components/ui/card/index.js'
import { navigate } from 'sv-router/generated'
import ArrowLeft from '@lucide/svelte/icons/arrow-left'
import Trash2 from '@lucide/svelte/icons/trash-2'
import WalletIcon from '@lucide/svelte/icons/wallet'
const w = xrpWallet
let confirm = $state(false)
let resetting = $state(false)
let deletingWalletId = $state<string | null>(null)
async function handleReset() {
resetting = true
@@ -25,84 +21,39 @@
resetting = false
confirm = false
}
async function handleDeleteWallet(id: string) {
await w.deleteWallet(id)
deletingWalletId = null
if (w.wallets.length === 0) navigate('/xrp')
}
</script>
<div class="mx-auto max-w-xl">
<div class="flex flex-col gap-4">
{#if w.wallets.length > 0}
<Card>
<CardHeader>
<CardTitle>Wallets</CardTitle>
<CardDescription>Manage your XRP wallets</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-2">
{#each w.wallets as wal}
<div class="flex items-center gap-3 rounded-md border border-input p-3">
<WalletIcon size={18} class="shrink-0 text-muted-foreground" />
<div class="flex-1 min-w-0">
<p class="font-medium text-sm">{wal.name}</p>
<p class="text-xs text-muted-foreground truncate font-mono">
{wal.address}
</p>
</div>
{#if deletingWalletId === wal.id}
<div class="flex items-center gap-1.5">
<Button size="sm" variant="destructive" onclick={() => handleDeleteWallet(wal.id)}>
Delete
</Button>
<Button size="sm" variant="outline" onclick={() => (deletingWalletId = null)}>
Cancel
</Button>
</div>
{:else}
<Button size="sm" variant="outline" onclick={() => (deletingWalletId = wal.id)}>
<Trash2 size={14} />
</Button>
{/if}
</div>
{/each}
</div>
</CardContent>
</Card>
{/if}
<Card>
<CardHeader>
<CardTitle>Settings</CardTitle>
<CardDescription>App preferences and data management</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<div>
<p class="font-medium text-xs">Reset App</p>
<p class="text-muted-foreground text-xs mt-1">
Removes all XRP wallets and seeds from the keychain. You
can then set up the wallet fresh.
</p>
<div class="mt-3">
{#if confirm}
<div class="flex gap-2 items-center">
<p class="text-xs text-red-500">Are you sure?</p>
<Button size="sm" variant="destructive" onclick={handleReset} disabled={resetting}>
{resetting ? 'Resetting...' : 'Yes, reset everything'}
</Button>
<Button size="sm" variant="outline" onclick={() => (confirm = false)}>
Cancel
</Button>
</div>
{:else}
<Button variant="outline" onclick={() => (confirm = true)}>
Reset App
<Card>
<CardHeader>
<CardTitle>Settings</CardTitle>
<CardDescription>App preferences and data management</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<div>
<p class="font-medium text-xs">Reset App</p>
<p class="text-muted-foreground text-xs mt-1">
Removes all wallets and seeds from the keychain. You can
then set up the wallet fresh.
</p>
<div class="mt-3">
{#if confirm}
<div class="flex gap-2 items-center">
<p class="text-xs text-red-500">Are you sure?</p>
<Button size="sm" variant="destructive" onclick={handleReset} disabled={resetting}>
{resetting ? 'Resetting...' : 'Yes, reset everything'}
</Button>
{/if}
</div>
<Button size="sm" variant="outline" onclick={() => (confirm = false)}>
Cancel
</Button>
</div>
{:else}
<Button variant="outline" onclick={() => (confirm = true)}>
Reset App
</Button>
{/if}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
+10 -79
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import { xrpWallet } from '$lib/states/xrp-wallet.svelte.js'
import { session } from '$lib/states/session.svelte.js'
import { Button } from '$lib/components/ui/button/index.js'
import { Input } from '$lib/components/ui/input/index.js'
import { Textarea } from '$lib/components/ui/textarea/index.js'
@@ -12,9 +11,6 @@
CardTitle,
} from '$lib/components/ui/card/index.js'
import Fingerprint from '@lucide/svelte/icons/fingerprint'
import LockKeyhole from '@lucide/svelte/icons/lock-keyhole'
import Plus from '@lucide/svelte/icons/plus'
import WalletIcon from '@lucide/svelte/icons/wallet'
import Loader from '$lib/components/loader.svelte'
import { navigate } from 'sv-router/generated'
@@ -24,7 +20,6 @@
let inputSeed = $state('')
let inputPassword = $state('')
let inputUnlockPassword = $state('')
let showCreateForm = $state(false)
let newSeed = $state<string | null>(null)
let initStarted = false
@@ -108,12 +103,10 @@
</Button>
</CardContent>
</Card>
{:else if w.wallets.length === 0 || showCreateForm}
{:else if w.wallets.length === 0}
<Card>
<CardHeader>
<CardTitle>
{showCreateForm ? 'Add Another Wallet' : 'Create / Import Wallet'}
</CardTitle>
<CardTitle>Create / Import Wallet</CardTitle>
<CardDescription>
Generate a new XRP wallet or import an existing seed
</CardDescription>
@@ -139,77 +132,20 @@
type="password"
placeholder="Set a password (optional)"
bind:value={inputPassword} />
<div class="flex gap-2">
<Button
variant="outline"
onclick={handleImport}
disabled={w.loading ||
!inputSeed.trim() ||
!inputWalletName.trim()}>
{w.loading ? 'Importing...' : 'Import Wallet'}
</Button>
{#if showCreateForm}
<Button
variant="outline"
onclick={() => (showCreateForm = false)}>
Cancel
</Button>
{/if}
</div>
<Button
variant="outline"
onclick={handleImport}
disabled={w.loading ||
!inputSeed.trim() ||
!inputWalletName.trim()}>
{w.loading ? 'Importing...' : 'Import Wallet'}
</Button>
</div>
{#if w.error}
<p class="mt-3 text-xs text-red-500">{w.error}</p>
{/if}
</CardContent>
</Card>
{:else if !w.currentWalletId}
<Card>
<CardHeader>
<CardTitle>Wallets</CardTitle>
<CardDescription>Select a wallet to use</CardDescription>
</CardHeader>
<CardContent>
<div class="flex flex-col gap-2">
{#each w.wallets as wal}
<button
onclick={async () => {
if (session.unlocked) {
await w.autoUnlock(wal.id)
} else if (wal.hasPassword) {
await w.selectWallet(wal.id)
} else {
await w.selectAndUnlockWallet(wal.id)
}
}}
class="flex w-full cursor-pointer items-center gap-3 rounded-md border border-input p-3 text-left hover:bg-muted transition-colors">
<WalletIcon
size={20}
class="shrink-0 text-muted-foreground" />
<div class="flex-1 min-w-0">
<p class="font-medium text-sm">{wal.name}</p>
<p class="text-xs text-muted-foreground truncate font-mono">
{wal.address}
</p>
</div>
<div class="flex items-center gap-1">
{#if wal.hasPassword}
<LockKeyhole
size={14}
class="text-muted-foreground" />
{/if}
</div>
</button>
{/each}
<Button
variant="outline"
onclick={() => (showCreateForm = true)}
class="mt-2">
<Plus size={16} />
Add Wallet
</Button>
</div>
</CardContent>
</Card>
{:else if w.isLocked}
<Card>
<CardHeader>
@@ -254,11 +190,6 @@
</Button>
</div>
{/if}
<Button
variant="outline"
onclick={() => w.clearSelection()}>
Pick another wallet
</Button>
</div>
{#if w.error}
<p class="mt-3 text-xs text-red-500">{w.error}</p>