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
+2
View File
@@ -2,3 +2,5 @@
- Never commit changes unless explicitly told to do so. Always ask first or wait for the user's command. - Never commit changes unless explicitly told to do so. Always ask first or wait for the user's command.
- Never manually edit `.router/router.ts` — sv-router auto-generates it from the filesystem. Just create the route file in the correct filesystem location. - Never manually edit `.router/router.ts` — sv-router auto-generates it from the filesystem. Just create the route file in the correct filesystem location.
- Only commit when explicitly told to do so. When asked to commit, always write a single simple line commit message.
- At the end of every response, always surface a "Pending from you" section listing anything you need from the user (decisions, permissions, credentials, confirming a plan, etc.). If nothing is pending, say "Nothing pending."
@@ -82,6 +82,10 @@ export const MoneroWallet = () => {
await checkStatus() await checkStatus()
} }
await listWallets() await listWallets()
if (wallets.length > 0) {
const pw = await moneroGetStoredPassword(wallets[0])
if (pw) await openExistingWallet(wallets[0], pw)
}
} }
const logout = async () => { const logout = async () => {
@@ -80,7 +80,7 @@ export const XrpWallet = () => {
) )
if (walletList) { if (walletList) {
wallets = walletList wallets = walletList
if (walletList.length === 1 && !currentWalletId) { if (walletList.length > 0 && !currentWalletId) {
currentWalletId = walletList[0].id currentWalletId = walletList[0].id
} }
} }
-33
View File
@@ -269,39 +269,6 @@
</button> </button>
</div> </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 <Button
onclick={() => navigate('/xrp')} onclick={() => navigate('/xrp')}
+31 -124
View File
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { moneroWallet } from '$lib/states/monero-wallet.svelte.js' 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 { Button } from '$lib/components/ui/button/index.js'
import { Textarea } from '$lib/components/ui/textarea/index.js' import { Textarea } from '$lib/components/ui/textarea/index.js'
import { Input } from '$lib/components/ui/input/index.js' import { Input } from '$lib/components/ui/input/index.js'
@@ -11,28 +10,21 @@
CardHeader, CardHeader,
CardTitle, CardTitle,
} from '$lib/components/ui/card/index.js' } from '$lib/components/ui/card/index.js'
import Fingerprint from '@lucide/svelte/icons/fingerprint'
import Loader from '$lib/components/loader.svelte' import Loader from '$lib/components/loader.svelte'
import { navigate } from 'sv-router/generated' import { navigate } from 'sv-router/generated'
const w = moneroWallet const w = moneroWallet
let moneroWalletName = $state('') let moneroWalletName = $state('')
let moneroWalletPass = $state('')
let moneroMnemonic = $state('') let moneroMnemonic = $state('')
let moneroRestoreHeight = $state<number | undefined>(undefined) let moneroRestoreHeight = $state<number | undefined>(undefined)
let moneroSelectedWallet = $state('')
let moneroSelectedWalletPass = $state('')
let moneroUseBiometric = $state(false)
let initStarted = false let initStarted = false
$effect(() => { $effect(() => {
if (w.walletOpen && w.walletName) { if (w.walletOpen && w.walletName) {
navigate('/monero/wallet/:name', { navigate('/monero/wallet/:name', {
params: { params: { name: w.walletName },
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> </script>
<div class="mx-auto max-w-xl"> <div class="mx-auto max-w-xl">
@@ -101,123 +99,41 @@
</CardContent> </CardContent>
</Card> </Card>
{:else if !w.walletOpen} {:else if !w.walletOpen}
{#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> <Card>
<CardHeader> <CardHeader>
<CardTitle>Monero Wallet</CardTitle> <CardTitle>Monero Wallet</CardTitle>
<CardDescription> <CardDescription>
{w.wallets.length > 0 Create a new wallet or restore from seed
? `${w.wallets.length} wallet${w.wallets.length > 1 ? 's' : ''} found on disk`
: 'No existing wallets found'}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
{#if w.wallets.length > 0}
<p class="text-xs font-medium">
Select a wallet to open:
</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"> <p class="text-xs text-muted-foreground">
Or create a new wallet: Create a new wallet:
</p> </p>
<Input <Input
placeholder="Wallet name" placeholder="Wallet name"
bind:value={moneroWalletName} /> 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">
<Button <Button
onclick={async () => { onclick={() =>
const pw = moneroUseBiometric w.createWallet(
? crypto.randomUUID()
: moneroWalletPass
await w.createWallet(
moneroWalletName, moneroWalletName,
pw, crypto.randomUUID(),
moneroUseBiometric, true,
) )}
}} disabled={w.loading || !moneroWalletName}>
disabled={w.loading ||
!moneroWalletName ||
(!moneroUseBiometric && !moneroWalletPass)}>
Create Wallet Create Wallet
</Button> </Button>
</div>
<hr class="border-muted" /> <hr class="border-muted" />
<p class="text-xs text-muted-foreground"> <p class="text-xs text-muted-foreground">
Restore from seed Restore from seed
@@ -229,27 +145,17 @@
type="number" type="number"
placeholder="Restore height (optional)" placeholder="Restore height (optional)"
bind:value={moneroRestoreHeight} /> bind:value={moneroRestoreHeight} />
{#if !moneroUseBiometric}
<Input
type="password"
placeholder="Password"
bind:value={moneroWalletPass} />
{/if}
<Button <Button
onclick={async () => { onclick={() =>
await w.restoreWallet( w.restoreWallet(
moneroWalletName, moneroWalletName,
moneroUseBiometric crypto.randomUUID(),
? crypto.randomUUID()
: moneroWalletPass,
moneroMnemonic, moneroMnemonic,
moneroRestoreHeight, moneroRestoreHeight,
moneroUseBiometric, true,
) )}
}}
disabled={w.loading || disabled={w.loading ||
!moneroWalletName || !moneroWalletName ||
(!moneroUseBiometric && !moneroWalletPass) ||
!moneroMnemonic.trim()}> !moneroMnemonic.trim()}>
Restore Wallet Restore Wallet
</Button> </Button>
@@ -258,5 +164,6 @@
</Card> </Card>
{/if} {/if}
{/if} {/if}
{/if}
</div> </div>
</div> </div>
+2 -51
View File
@@ -9,14 +9,10 @@
CardTitle, CardTitle,
} from '$lib/components/ui/card/index.js' } from '$lib/components/ui/card/index.js'
import { navigate } from 'sv-router/generated' 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 const w = xrpWallet
let confirm = $state(false) let confirm = $state(false)
let resetting = $state(false) let resetting = $state(false)
let deletingWalletId = $state<string | null>(null)
async function handleReset() { async function handleReset() {
resetting = true resetting = true
@@ -25,53 +21,9 @@
resetting = false resetting = false
confirm = false confirm = false
} }
async function handleDeleteWallet(id: string) {
await w.deleteWallet(id)
deletingWalletId = null
if (w.wallets.length === 0) navigate('/xrp')
}
</script> </script>
<div class="mx-auto max-w-xl"> <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> <Card>
<CardHeader> <CardHeader>
<CardTitle>Settings</CardTitle> <CardTitle>Settings</CardTitle>
@@ -81,8 +33,8 @@
<div> <div>
<p class="font-medium text-xs">Reset App</p> <p class="font-medium text-xs">Reset App</p>
<p class="text-muted-foreground text-xs mt-1"> <p class="text-muted-foreground text-xs mt-1">
Removes all XRP wallets and seeds from the keychain. You Removes all wallets and seeds from the keychain. You can
can then set up the wallet fresh. then set up the wallet fresh.
</p> </p>
<div class="mt-3"> <div class="mt-3">
{#if confirm} {#if confirm}
@@ -104,5 +56,4 @@
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div>
</div> </div>
+2 -71
View File
@@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { xrpWallet } from '$lib/states/xrp-wallet.svelte.js' 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 { Button } from '$lib/components/ui/button/index.js'
import { Input } from '$lib/components/ui/input/index.js' import { Input } from '$lib/components/ui/input/index.js'
import { Textarea } from '$lib/components/ui/textarea/index.js' import { Textarea } from '$lib/components/ui/textarea/index.js'
@@ -12,9 +11,6 @@
CardTitle, CardTitle,
} from '$lib/components/ui/card/index.js' } from '$lib/components/ui/card/index.js'
import Fingerprint from '@lucide/svelte/icons/fingerprint' 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 Loader from '$lib/components/loader.svelte'
import { navigate } from 'sv-router/generated' import { navigate } from 'sv-router/generated'
@@ -24,7 +20,6 @@
let inputSeed = $state('') let inputSeed = $state('')
let inputPassword = $state('') let inputPassword = $state('')
let inputUnlockPassword = $state('') let inputUnlockPassword = $state('')
let showCreateForm = $state(false)
let newSeed = $state<string | null>(null) let newSeed = $state<string | null>(null)
let initStarted = false let initStarted = false
@@ -108,12 +103,10 @@
</Button> </Button>
</CardContent> </CardContent>
</Card> </Card>
{:else if w.wallets.length === 0 || showCreateForm} {:else if w.wallets.length === 0}
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle> <CardTitle>Create / Import Wallet</CardTitle>
{showCreateForm ? 'Add Another Wallet' : 'Create / Import Wallet'}
</CardTitle>
<CardDescription> <CardDescription>
Generate a new XRP wallet or import an existing seed Generate a new XRP wallet or import an existing seed
</CardDescription> </CardDescription>
@@ -139,7 +132,6 @@
type="password" type="password"
placeholder="Set a password (optional)" placeholder="Set a password (optional)"
bind:value={inputPassword} /> bind:value={inputPassword} />
<div class="flex gap-2">
<Button <Button
variant="outline" variant="outline"
onclick={handleImport} onclick={handleImport}
@@ -148,68 +140,12 @@
!inputWalletName.trim()}> !inputWalletName.trim()}>
{w.loading ? 'Importing...' : 'Import Wallet'} {w.loading ? 'Importing...' : 'Import Wallet'}
</Button> </Button>
{#if showCreateForm}
<Button
variant="outline"
onclick={() => (showCreateForm = false)}>
Cancel
</Button>
{/if}
</div>
</div> </div>
{#if w.error} {#if w.error}
<p class="mt-3 text-xs text-red-500">{w.error}</p> <p class="mt-3 text-xs text-red-500">{w.error}</p>
{/if} {/if}
</CardContent> </CardContent>
</Card> </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} {:else if w.isLocked}
<Card> <Card>
<CardHeader> <CardHeader>
@@ -254,11 +190,6 @@
</Button> </Button>
</div> </div>
{/if} {/if}
<Button
variant="outline"
onclick={() => w.clearSelection()}>
Pick another wallet
</Button>
</div> </div>
{#if w.error} {#if w.error}
<p class="mt-3 text-xs text-red-500">{w.error}</p> <p class="mt-3 text-xs text-red-500">{w.error}</p>