auto-detect existing wallets, show selectable list on startup

This commit is contained in:
2026-05-17 10:51:22 +08:00
parent 23b7447b92
commit 880199f096
5 changed files with 83 additions and 8 deletions
+7
View File
@@ -623,6 +623,13 @@ const rpc = BrowserView.defineRPC<RPC>({
console.log(`[rpc] moneroGetAccounts: ${accounts.length} accounts`) console.log(`[rpc] moneroGetAccounts: ${accounts.length} accounts`)
return accounts return accounts
}, },
moneroListWallets: async () => {
console.log('[rpc] moneroListWallets')
if (!moneroManager) throw new Error('Monero wallet RPC not started')
const wallets = moneroManager.listWallets()
console.log(`[rpc] moneroListWallets: ${wallets.join(', ') || 'none'}`)
return wallets
},
}, },
messages: {}, messages: {},
}, },
+13 -1
View File
@@ -1,6 +1,7 @@
import { MoneroWalletRpc, MoneroRpcConnection, connectToWalletRpc, connectToDaemonRpc } from 'monero-ts' import { MoneroWalletRpc, MoneroRpcConnection, connectToWalletRpc, connectToDaemonRpc } from 'monero-ts'
import { getBinaryPath, getWalletDir } from './binary' import { getBinaryPath, getWalletDir } from './binary'
import { existsSync } from 'fs' import { existsSync, readdirSync } from 'fs'
import { join } from 'path'
const DEFAULT_DAEMON = 'xmr-node.cakewallet.com:18081' const DEFAULT_DAEMON = 'xmr-node.cakewallet.com:18081'
@@ -18,6 +19,17 @@ export class MoneroWalletManager {
this.rpcPassword = crypto.randomUUID() this.rpcPassword = crypto.randomUUID()
} }
listWallets(): string[] {
const dir = getWalletDir()
if (!existsSync(dir)) return []
const files = readdirSync(dir)
const wallets = files
.filter(f => f.endsWith('.keys'))
.map(f => f.replace(/\.keys$/, ''))
console.log(`[monero] wallets on disk: ${wallets.join(', ') || 'none'}`)
return wallets
}
private get rpcUrl() { private get rpcUrl() {
return `http://127.0.0.1:${this.rpcPort}` return `http://127.0.0.1:${this.rpcPort}`
} }
+4
View File
@@ -196,6 +196,10 @@ type RPC = {
params: {} params: {}
response: MoneroAccountEntry[] response: MoneroAccountEntry[]
} }
moneroListWallets: {
params: {}
response: string[]
}
} }
messages: {} messages: {}
} }
@@ -79,6 +79,7 @@ export const Wallet = () => {
let moneroDownloading = $state(false) let moneroDownloading = $state(false)
let moneroWalletName = $state('') let moneroWalletName = $state('')
let moneroAccounts = $state<MoneroAccountEntry[]>([]) let moneroAccounts = $state<MoneroAccountEntry[]>([])
let moneroWallets = $state<string[]>([])
const net = () => networks.find((n) => n.id === network)! const net = () => networks.find((n) => n.id === network)!
@@ -132,6 +133,7 @@ export const Wallet = () => {
await moneroStart() await moneroStart()
await checkMoneroStatus() await checkMoneroStatus()
} }
await moneroListWallets()
} else if (seed) { } else if (seed) {
await refresh() await refresh()
} }
@@ -215,6 +217,22 @@ export const Wallet = () => {
return true return true
} }
const moneroListWallets = async () => {
if (!electrobun.rpc) return
const [wallets] = await tryCatch(
electrobun.rpc.request.moneroListWallets({}),
)
moneroWallets = wallets ?? []
}
const moneroOpenExistingWallet = async (name: string, password: string) => {
if (!electrobun.rpc) throw new Error('RPC not available')
await electrobun.rpc.request.moneroOpenWallet({ name, password })
moneroWalletName = name
moneroWalletOpen = true
await moneroRefresh()
}
const saveApiKey = async (key: string) => { const saveApiKey = async (key: string) => {
await electrobun.rpc?.request.setSecret({ await electrobun.rpc?.request.setSecret({
service: 'koins', service: 'koins',
@@ -285,6 +303,7 @@ export const Wallet = () => {
moneroUnlockedAtomic = '0' moneroUnlockedAtomic = '0'
moneroTxs = [] moneroTxs = []
moneroAccounts = [] moneroAccounts = []
moneroWallets = []
} }
const moneroCreateWallet = async (name: string, password: string) => { const moneroCreateWallet = async (name: string, password: string) => {
@@ -379,6 +398,7 @@ export const Wallet = () => {
get moneroDownloading() { return moneroDownloading }, get moneroDownloading() { return moneroDownloading },
get moneroWalletName() { return moneroWalletName }, get moneroWalletName() { return moneroWalletName },
get moneroAccounts() { return moneroAccounts }, get moneroAccounts() { return moneroAccounts },
get moneroWallets() { return moneroWallets },
refresh, refresh,
init, init,
lock, lock,
@@ -395,5 +415,7 @@ export const Wallet = () => {
moneroOpenWallet, moneroOpenWallet,
moneroRefresh, moneroRefresh,
moneroFetchAccounts, moneroFetchAccounts,
moneroListWallets,
moneroOpenExistingWallet,
} }
} }
+37 -7
View File
@@ -50,6 +50,8 @@
let moneroWalletPass = $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('')
$effect(() => { $effect(() => {
w.init() w.init()
@@ -137,10 +139,44 @@
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Monero Wallet</CardTitle> <CardTitle>Monero Wallet</CardTitle>
<CardDescription>Create a new wallet or restore an existing one</CardDescription> <CardDescription>
{w.moneroWallets.length > 0
? `${w.moneroWallets.length} wallet${w.moneroWallets.length > 1 ? 's' : ''} found on disk`
: 'No existing wallets found'}
</CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
{#if w.moneroWallets.length > 0}
<p class="text-xs font-medium">Select a wallet to open:</p>
<div class="flex flex-wrap gap-2">
{#each w.moneroWallets as name}
<Button
variant={moneroSelectedWallet === name ? 'default' : 'outline'}
onclick={() => { moneroSelectedWallet = name; moneroSelectedWalletPass = '' }}
>
{name}
</Button>
{/each}
</div>
{#if moneroSelectedWallet}
<div class="flex gap-2 items-end">
<Input
type="password"
placeholder="Enter password"
bind:value={moneroSelectedWalletPass}
/>
<Button
onclick={() => w.moneroOpenExistingWallet(moneroSelectedWallet, moneroSelectedWalletPass)}
disabled={w.loading || !moneroSelectedWalletPass}
>
Open
</Button>
</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} /> <Input placeholder="Wallet name" bind:value={moneroWalletName} />
<Input type="password" placeholder="Password" bind:value={moneroWalletPass} /> <Input type="password" placeholder="Password" bind:value={moneroWalletPass} />
<div class="flex gap-2"> <div class="flex gap-2">
@@ -152,12 +188,6 @@
disabled={w.loading || !moneroWalletName || !moneroWalletPass}> disabled={w.loading || !moneroWalletName || !moneroWalletPass}>
Create Wallet Create Wallet
</Button> </Button>
<Button
variant="outline"
onclick={() => w.moneroOpenWallet(moneroWalletName, moneroWalletPass)}
disabled={w.loading || !moneroWalletName || !moneroWalletPass}>
Open
</Button>
</div> </div>
<hr class="border-muted" /> <hr class="border-muted" />
<p class="text-xs text-muted-foreground">Restore from seed</p> <p class="text-xs text-muted-foreground">Restore from seed</p>