auto-detect existing wallets, show selectable list on startup
This commit is contained in:
@@ -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: {},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user