add biometric support for monero wallet open/create/restore, add monero settings card, fix monero logout to stop rpc

- Added moneroGetStoredPassword / moneroStorePassword keychain helpers
- moneroCreateWallet and moneroRestoreWallet accept storePw flag to save password to keychain
- moneroOpenExistingWallet accepts optional password (falls back to keychain lookup)
- Monero UI: 'Save password with Touch ID' checkbox hides password field, auto-generates random password, saves to keychain
- Monero UI: 'Open with Touch ID' button uses biometric auth + retrieves stored password
- Monero Settings card: enter wallet password to save with Touch ID for existing wallets
- logout() now async, calls moneroStop() to kill RPC process so re-login requires explicit wallet open
This commit is contained in:
2026-05-18 05:31:41 +08:00
parent 9846e6464d
commit 9396ffb1e0
3 changed files with 98 additions and 31 deletions
@@ -261,7 +261,7 @@
<Button variant="outline" onclick={() => navigate('/settings')}>
<SettingsIcon size={16} />
</Button>
<Button variant="outline" onclick={() => { w.logout(); navigate('/') }}>
<Button variant="outline" onclick={async () => { await w.logout(); navigate('/') }}>
Logout
</Button>
</div>
+25 -12
View File
@@ -142,20 +142,27 @@ export const Wallet = () => {
}
}
const logout = () => {
const logout = async () => {
await moneroStop()
accountType = null
seed = ''
address = ''
balance = '0'
tokenBalances = []
transactions = []
moneroWalletOpen = false
moneroBalAtomic = '0'
moneroUnlockedAtomic = '0'
moneroAddress = ''
moneroTxs = []
moneroWalletName = ''
moneroAccounts = []
}
const moneroGetStoredPassword = async (name: string): Promise<string | null> => {
if (!electrobun.rpc) return null
const [raw] = await tryCatch(
electrobun.rpc.request.getSecret({ service: 'koins', name: `monero_pw_${name}` }),
)
return raw ?? null
}
const moneroStorePassword = async (name: string, password: string) => {
if (!electrobun.rpc) return
await electrobun.rpc.request.setSecret({ service: 'koins', name: `monero_pw_${name}`, value: password })
}
const switchNetwork = async (id: NetworkId) => {
@@ -334,9 +341,11 @@ export const Wallet = () => {
moneroWallets = wallets ?? []
}
const moneroOpenExistingWallet = async (name: string, password: string) => {
const moneroOpenExistingWallet = async (name: string, password?: string) => {
if (!electrobun.rpc) throw new Error('RPC not available')
await electrobun.rpc.request.moneroOpenWallet({ name, password })
const pw = password ?? await moneroGetStoredPassword(name)
if (!pw) throw new Error('Password required')
await electrobun.rpc.request.moneroOpenWallet({ name, password: pw })
moneroWalletName = name
moneroWalletOpen = true
await moneroRefresh()
@@ -415,17 +424,18 @@ export const Wallet = () => {
moneroWallets = []
}
const moneroCreateWallet = async (name: string, password: string) => {
const moneroCreateWallet = async (name: string, password: string, storePw?: boolean) => {
if (!electrobun.rpc) throw new Error('RPC not available')
const result = await electrobun.rpc.request.moneroCreateWallet({ name, password })
moneroWalletName = name
moneroWalletOpen = true
moneroAddress = result.address
if (storePw) await moneroStorePassword(name, password)
await moneroRefresh()
return result
}
const moneroRestoreWallet = async (name: string, password: string, mnemonic: string, restoreHeight?: number) => {
const moneroRestoreWallet = async (name: string, password: string, mnemonic: string, restoreHeight?: number, storePw?: boolean) => {
if (!electrobun.rpc) throw new Error('RPC not available')
const result = await electrobun.rpc.request.moneroRestoreWallet({
name, password, mnemonic, restoreHeight,
@@ -433,6 +443,7 @@ export const Wallet = () => {
moneroWalletName = name
moneroWalletOpen = true
moneroAddress = result.address
if (storePw) await moneroStorePassword(name, password)
await moneroRefresh()
}
@@ -536,6 +547,8 @@ export const Wallet = () => {
moneroFetchAccounts,
moneroListWallets,
moneroOpenExistingWallet,
moneroGetStoredPassword,
moneroStorePassword,
}
}