bump version to 0.0.5

This commit is contained in:
2026-08-02 14:15:39 +08:00
parent d9ca8a0047
commit 6eec1f998d
5 changed files with 91 additions and 34 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
app: { app: {
name: 'koins', name: 'koins',
identifier: 'koins.tzgyn.com', identifier: 'koins.tzgyn.com',
version: '0.0.4', version: '0.0.5',
}, },
build: { build: {
// Vite builds to dist/, we copy from there // Vite builds to dist/, we copy from there
+51 -28
View File
@@ -1,11 +1,7 @@
import { import { MoneroWalletRpc, MoneroRpcConnection, connectToWalletRpc, connectToDaemonRpc } from 'monero-ts'
MoneroWalletRpc,
MoneroRpcConnection,
connectToWalletRpc,
connectToDaemonRpc,
} from 'monero-ts'
import { getBinaryPath, getWalletDir } from './binary' import { getBinaryPath, getWalletDir } from './binary'
import { existsSync, readdirSync } from 'fs' import { existsSync, readdirSync } from 'fs'
import { log, logError } from '../logger'
const DEFAULT_DAEMON = 'xmr-node.cakewallet.com:18081' const DEFAULT_DAEMON = 'xmr-node.cakewallet.com:18081'
@@ -65,20 +61,13 @@ async function waitForRpc(
let lastErr = '' let lastErr = ''
while (Date.now() - start < timeoutMs) { while (Date.now() - start < timeoutMs) {
try { try {
await Bun.connect({ const conn = rpcConnection(state)
hostname: '127.0.0.1', const res = await conn.sendJsonRequest('get_version', {})
port: state.rpcPort, if (res?.result?.version) {
socket: { console.log(`[monero] RPC ready after ${Date.now() - start}ms`)
open(s) { return
s.end() }
}, lastErr = JSON.stringify(res)
close() {},
data() {},
error() {},
},
})
console.log(`[monero] RPC ready after ${Date.now() - start}ms`)
return
} catch (e) { } catch (e) {
lastErr = String(e) lastErr = String(e)
} }
@@ -89,6 +78,28 @@ async function waitForRpc(
) )
} }
async function connectClient(
state: MoneroWalletState,
): Promise<MoneroWalletRpc> {
let lastErr = ''
for (let attempt = 1; attempt <= 3; attempt++) {
try {
const client = (await connectToWalletRpc(
rpcUrl(state),
state.rpcUser,
state.rpcPassword,
)) as MoneroWalletRpc
console.log(`[monero] MoneroWalletRpc connected (attempt ${attempt})`)
return client
} catch (e) {
lastErr = String(e)
console.log(`[monero] connectToWalletRpc attempt ${attempt} failed:`, e)
if (attempt < 3) await new Promise((r) => setTimeout(r, 1000))
}
}
throw new Error(`Failed to connect to monero-wallet-rpc: ${lastErr}`)
}
export async function start( export async function start(
state: MoneroWalletState, state: MoneroWalletState,
daemonAddress: string = DEFAULT_DAEMON, daemonAddress: string = DEFAULT_DAEMON,
@@ -169,6 +180,9 @@ export async function start(
`[monero] wallet-rpc process exited with code ${exitCode}`, `[monero] wallet-rpc process exited with code ${exitCode}`,
error?.message ?? '', error?.message ?? '',
) )
log(
`[monero] wallet-rpc process exited with code ${exitCode} ${error?.message ?? ''}`,
)
if (state.saveTimer) { if (state.saveTimer) {
clearInterval(state.saveTimer) clearInterval(state.saveTimer)
state.saveTimer = null state.saveTimer = null
@@ -222,12 +236,9 @@ export async function start(
console.log(`[monero] RPC server is ready`) console.log(`[monero] RPC server is ready`)
console.log(`[monero] connecting MoneroWalletRpc client...`) console.log(`[monero] connecting MoneroWalletRpc client...`)
state.wallet = (await connectToWalletRpc( state.wallet = await connectClient(state)
rpcUrl(state),
state.rpcUser,
state.rpcPassword,
)) as MoneroWalletRpc
console.log(`[monero] MoneroWalletRpc connected successfully`) console.log(`[monero] MoneroWalletRpc connected successfully`)
log(`[monero] wallet-rpc started, RPC ready`)
} }
export async function stop(state: MoneroWalletState) { export async function stop(state: MoneroWalletState) {
@@ -241,6 +252,7 @@ export async function stop(state: MoneroWalletState) {
if (state.wallet) { if (state.wallet) {
await rawRpc(state, 'store') await rawRpc(state, 'store')
console.log(`[monero] wallet saved before stop`) console.log(`[monero] wallet saved before stop`)
log(`[monero] wallet saved before stop`)
} }
} catch (e) { } catch (e) {
console.log(`[monero] wallet save before stop failed:`, e) console.log(`[monero] wallet save before stop failed:`, e)
@@ -309,9 +321,20 @@ export async function openWallet(
password: string, password: string,
) { ) {
console.log(`[monero] opening wallet: ${name}`) console.log(`[monero] opening wallet: ${name}`)
if (!state.wallet) throw new Error('Wallet RPC not started') // Self-heal: the client connection may have been lost while the
await state.wallet.openWallet(name, password) // wallet-rpc process is still running (e.g. stale-process cleanup).
console.log(`[monero] wallet opened: ${name}`) if (!state.wallet) {
if (!state.process) throw new Error('Wallet RPC not started')
log(`[monero] reconnecting wallet client before open (${name})`)
state.wallet = await connectClient(state)
}
try {
await state.wallet.openWallet(name, password)
log(`[monero] wallet opened: ${name}`)
} catch (e) {
logError(`[monero] wallet open failed (${name})`, e)
throw e
}
} }
export async function closeWallet(state: MoneroWalletState) { export async function closeWallet(state: MoneroWalletState) {
+10 -1
View File
@@ -24,6 +24,7 @@ import {
isConnected, isConnected,
} from '../../lib/monero' } from '../../lib/monero'
import type { MoneroWalletState } from '../../lib/monero' import type { MoneroWalletState } from '../../lib/monero'
import { log, logError } from '../../lib/logger'
export function createMoneroHandlers(state: { export function createMoneroHandlers(state: {
manager: MoneroWalletState | null manager: MoneroWalletState | null
@@ -64,10 +65,12 @@ export function createMoneroHandlers(state: {
await walletStart(state.manager, daemonAddress) await walletStart(state.manager, daemonAddress)
} catch (e) { } catch (e) {
console.log('[rpc] moneroStart error:', e) console.log('[rpc] moneroStart error:', e)
logError('[rpc] moneroStart error', e)
return { running: false, walletOpen: false, connected: false } return { running: false, walletOpen: false, connected: false }
} }
const connected = await isConnected(state.manager) const connected = await isConnected(state.manager)
console.log('[rpc] moneroStart complete, connected:', connected) console.log('[rpc] moneroStart complete, connected:', connected)
log(`[rpc] moneroStart complete, connected: ${connected}`)
return { running: true, walletOpen: false, connected } return { running: true, walletOpen: false, connected }
}, },
moneroStop: async () => { moneroStop: async () => {
@@ -129,7 +132,13 @@ export function createMoneroHandlers(state: {
console.log('[rpc] moneroOpenWallet:', name) console.log('[rpc] moneroOpenWallet:', name)
if (!state.manager) if (!state.manager)
throw new Error('Monero wallet RPC not started') throw new Error('Monero wallet RPC not started')
await walletOpen(state.manager, name, password) try {
await walletOpen(state.manager, name, password)
log(`[rpc] moneroOpenWallet ok: ${name}`)
} catch (e) {
logError(`[rpc] moneroOpenWallet failed (${name})`, e)
throw e
}
}, },
moneroGetBalance: async () => { moneroGetBalance: async () => {
console.log('[rpc] moneroGetBalance') console.log('[rpc] moneroGetBalance')
@@ -95,6 +95,7 @@ export const MoneroWallet = () => {
if (loginInFlight) return loginInFlight if (loginInFlight) return loginInFlight
loginInFlight = (async () => { loginInFlight = (async () => {
accountType = 'monero' accountType = 'monero'
error = ''
beginOpening() beginOpening()
try { try {
if (installed && !running) { if (installed && !running) {
+28 -4
View File
@@ -22,6 +22,7 @@
let xrpBalances = $state<Record<string, { balance: number; funded: boolean }>>({}) let xrpBalances = $state<Record<string, { balance: number; funded: boolean }>>({})
let loadingXrp = $state(true) let loadingXrp = $state(true)
let loadingXmr = $state(true) let loadingXmr = $state(true)
let fetchXrpInFlight = false
let feError = $state<string | null>(null) let feError = $state<string | null>(null)
@@ -70,10 +71,13 @@
}) })
async function fetchXrp() { async function fetchXrp() {
if (fetchXrpInFlight) return
fetchXrpInFlight = true
loadingXrp = true loadingXrp = true
const rpc = electrobun.rpc const rpc = electrobun.rpc
if (!rpc) { if (!rpc) {
loadingXrp = false loadingXrp = false
fetchXrpInFlight = false
return return
} }
try { try {
@@ -92,6 +96,7 @@
}), }),
) )
loadingXrp = false loadingXrp = false
fetchXrpInFlight = false
} }
async function fetchXmr() { async function fetchXmr() {
@@ -111,12 +116,31 @@
onMount(() => { onMount(() => {
frontendLog('dashboard mounted') frontendLog('dashboard mounted')
moneroWallet.init().catch((e) => frontendLogError('moneroWallet.init() failed', e)) moneroWallet.init().catch((e) => frontendLogError('moneroWallet.init() failed', e))
xrpWallet.init().then(async () => { xrpWallet.init().catch((e) => frontendLogError('xrpWallet.init() failed', e))
await fetchXrp()
})
fetchXmr() fetchXmr()
}) })
// Wait for the XRP wallet list to load, then fetch balances.
$effect(() => {
if (xrpWallet.ready && xrpWallet.wallets.length > 0) {
fetchXrp()
}
})
// Self-heal: if the monero wallet list never got loaded (e.g. the server
// was starting while the dashboard rendered), retry login once.
$effect(() => {
if (
moneroWallet.ready &&
!moneroWallet.walletOpen &&
!moneroWallet.opening &&
moneroWallet.wallets.length === 0 &&
!moneroWallet.error
) {
void moneroWallet.login()
}
})
$effect(() => { $effect(() => {
if (moneroWallet.walletOpen) { if (moneroWallet.walletOpen) {
fetchXmr() fetchXmr()
@@ -205,7 +229,7 @@
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent class="flex flex-1 flex-col justify-center"> <CardContent class="flex flex-1 flex-col justify-center">
{#if loadingXrp} {#if !xrpWallet.ready || loadingXrp || (xrpWallet.wallets.length > 0 && Object.keys(xrpBalances).length === 0)}
<div class="h-14 flex items-center"><Loader /></div> <div class="h-14 flex items-center"><Loader /></div>
{:else} {:else}
<p class="font-mono text-xl tabular-nums">{fmtBal(xrpTotalBalance)}</p> <p class="font-mono text-xl tabular-nums">{fmtBal(xrpTotalBalance)}</p>