diff --git a/apps/desktop/src/bun/lib/monero/wallet.ts b/apps/desktop/src/bun/lib/monero/wallet.ts
index ab1b8bd..b6734af 100644
--- a/apps/desktop/src/bun/lib/monero/wallet.ts
+++ b/apps/desktop/src/bun/lib/monero/wallet.ts
@@ -1,4 +1,9 @@
-import { MoneroWalletRpc, MoneroRpcConnection, connectToWalletRpc, connectToDaemonRpc } from 'monero-ts'
+import {
+ MoneroWalletRpc,
+ MoneroRpcConnection,
+ connectToWalletRpc,
+ connectToDaemonRpc,
+} from 'monero-ts'
import { getBinaryPath, getWalletDir } from './binary'
import { existsSync, readdirSync } from 'fs'
import { join } from 'path'
@@ -24,9 +29,11 @@ export class MoneroWalletManager {
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'}`)
+ .filter((f) => f.endsWith('.keys'))
+ .map((f) => f.replace(/\.keys$/, ''))
+ console.log(
+ `[monero] wallets on disk: ${wallets.join(', ') || 'none'}`,
+ )
return wallets
}
@@ -43,7 +50,10 @@ export class MoneroWalletManager {
}
async start(daemonAddress: string = DEFAULT_DAEMON) {
- if (!existsSync(getBinaryPath())) throw new Error('monero-wallet-rpc not installed. Call downloadBinary() first.')
+ if (!existsSync(getBinaryPath()))
+ throw new Error(
+ 'monero-wallet-rpc not installed. Call downloadBinary() first.',
+ )
this.daemonAddress = daemonAddress
console.log(`[monero] starting wallet-rpc process...`)
@@ -52,11 +62,16 @@ export class MoneroWalletManager {
const cmd = [
getBinaryPath(),
- '--daemon-address', this.daemonAddress,
- '--rpc-bind-port', String(this.rpcPort),
- '--rpc-bind-ip', '127.0.0.1',
- '--wallet-dir', getWalletDir(),
- '--rpc-login', `${this.rpcUser}:${this.rpcPassword}`,
+ '--daemon-address',
+ this.daemonAddress,
+ '--rpc-bind-port',
+ String(this.rpcPort),
+ '--rpc-bind-ip',
+ '127.0.0.1',
+ '--wallet-dir',
+ getWalletDir(),
+ '--rpc-login',
+ `${this.rpcUser}:${this.rpcPassword}`,
'--trusted-daemon',
]
@@ -66,7 +81,10 @@ export class MoneroWalletManager {
stdout: 'pipe',
stderr: 'pipe',
onExit(_proc, exitCode, _signalCode, error) {
- console.log(`[monero] wallet-rpc process exited with code ${exitCode}`, error?.message ?? '')
+ console.log(
+ `[monero] wallet-rpc process exited with code ${exitCode}`,
+ error?.message ?? '',
+ )
},
})
console.log(`[monero] wallet-rpc pid: ${this.process.pid}`)
@@ -76,7 +94,9 @@ export class MoneroWalletManager {
while (true) {
const { done, value } = await stdout.read()
if (done) break
- console.log(`[monero:stdout] ${new TextDecoder().decode(value)}`)
+ console.log(
+ `[monero:stdout] ${new TextDecoder().decode(value)}`,
+ )
}
})()
@@ -85,23 +105,35 @@ export class MoneroWalletManager {
while (true) {
const { done, value } = await stderr.read()
if (done) break
- console.log(`[monero:stderr] ${new TextDecoder().decode(value)}`)
+ console.log(
+ `[monero:stderr] ${new TextDecoder().decode(value)}`,
+ )
}
})()
- console.log(`[monero] waiting for RPC server to become available...`)
+ console.log(
+ `[monero] waiting for RPC server to become available...`,
+ )
await this.waitForRpc()
console.log(`[monero] RPC server is ready`)
console.log(`[monero] connecting MoneroWalletRpc client...`)
- this.wallet = await connectToWalletRpc(this.rpcUrl, this.rpcUser, this.rpcPassword) as MoneroWalletRpc
+ this.wallet = (await connectToWalletRpc(
+ this.rpcUrl,
+ this.rpcUser,
+ this.rpcPassword,
+ )) as MoneroWalletRpc
console.log(`[monero] MoneroWalletRpc connected successfully`)
}
- private async rawRpc(method: string, params: Record = {}): Promise {
+ private async rawRpc(
+ method: string,
+ params: Record = {},
+ ): Promise {
const conn = this.rpcConnection
const res = await conn.sendJsonRequest(method, params)
- if (res?.error) throw new Error(res.error.message || JSON.stringify(res.error))
+ if (res?.error)
+ throw new Error(res.error.message || JSON.stringify(res.error))
return res?.result
}
@@ -113,22 +145,35 @@ export class MoneroWalletManager {
await Bun.connect({
hostname: '127.0.0.1',
port: this.rpcPort,
- socket: { open(s) { s.end() }, close() {}, data() {}, error() {} },
+ socket: {
+ open(s) {
+ s.end()
+ },
+ close() {},
+ data() {},
+ error() {},
+ },
})
- console.log(`[monero] RPC ready after ${Date.now() - start}ms`)
+ console.log(
+ `[monero] RPC ready after ${Date.now() - start}ms`,
+ )
return
} catch (e) {
lastErr = String(e)
}
- await new Promise(r => setTimeout(r, 500))
+ await new Promise((r) => setTimeout(r, 500))
}
- throw new Error(`monero-wallet-rpc failed to start within ${timeoutMs}ms: ${lastErr}`)
+ throw new Error(
+ `monero-wallet-rpc failed to start within ${timeoutMs}ms: ${lastErr}`,
+ )
}
async stop() {
console.log(`[monero] stopping wallet manager...`)
if (this.process) {
- console.log(`[monero] killing wallet-rpc process (pid ${this.process.pid})`)
+ console.log(
+ `[monero] killing wallet-rpc process (pid ${this.process.pid})`,
+ )
this.process.kill()
this.process = null
}
@@ -149,8 +194,15 @@ export class MoneroWalletManager {
return { mnemonic: result.mnemonic, address }
}
- async restoreWallet(name: string, password: string, mnemonic: string, restoreHeight?: number) {
- console.log(`[monero] restoring wallet: ${name} (height: ${restoreHeight ?? 0})`)
+ async restoreWallet(
+ name: string,
+ password: string,
+ mnemonic: string,
+ restoreHeight?: number,
+ ) {
+ console.log(
+ `[monero] restoring wallet: ${name} (height: ${restoreHeight ?? 0})`,
+ )
await this.rawRpc('restore_deterministic_wallet', {
filename: name,
password,
@@ -177,55 +229,95 @@ export class MoneroWalletManager {
async getBalance(): Promise<{ balance: bigint; unlocked: bigint }> {
if (!this.wallet) throw new Error('Wallet RPC not started')
- const bal = await this.wallet.getBalance() as bigint
- const unlocked = await this.wallet.getUnlockedBalance() as bigint
+ const bal = (await this.wallet.getBalance()) as bigint
+ const unlocked =
+ (await this.wallet.getUnlockedBalance()) as bigint
console.log(`[monero] balance: ${bal} (unlocked: ${unlocked})`)
return { balance: bal, unlocked }
}
async getAddress(accountIdx = 0, subIdx = 0): Promise {
if (!this.wallet) throw new Error('Wallet RPC not started')
- const address = await this.wallet.getAddress(accountIdx, subIdx) as string
+ const address = (await this.wallet.getAddress(
+ accountIdx,
+ subIdx,
+ )) as string
console.log(`[monero] address: ${address}`)
return address
}
async getAccounts(): Promise {
if (!this.wallet) throw new Error('Wallet RPC not started')
- const accounts = await this.wallet.getAccounts(true) as any[]
- console.log(`[monero] accounts: ${accounts?.length ?? 0} returned`)
+ const accounts = (await this.wallet.getAccounts(true)) as any[]
+ console.log(
+ `[monero] accounts: ${accounts?.length ?? 0} returned`,
+ )
if (accounts?.length) {
for (const acct of accounts) {
const subs = acct.getSubaddresses() ?? []
- console.log(`[monero] account ${acct.getIndex()}: balance=${acct.getBalance()?.toString() ?? '0'} subs=${subs.length} primary=${(acct.getPrimaryAddress() ?? '').substring(0, 16)}...`)
+ console.log(
+ `[monero] account ${acct.getIndex()}: balance=${acct.getBalance()?.toString() ?? '0'} subs=${subs.length} primary=${(acct.getPrimaryAddress() ?? '').substring(0, 16)}...`,
+ )
const has0 = subs.some((s: any) => s.getIndex() === 0)
if (!has0) {
- console.log(`[monero] sub 0 (primary): addr=${(acct.getPrimaryAddress() ?? '').substring(0, 16)}...`)
+ console.log(
+ `[monero] sub 0 (primary): addr=${(acct.getPrimaryAddress() ?? '').substring(0, 16)}...`,
+ )
}
for (const sub of subs) {
- console.log(`[monero] sub ${sub.getIndex()}: addr=${(sub.getAddress() ?? '').substring(0, 16)}... label=${sub.getLabel() ?? ''} balance=${sub.getBalance()?.toString() ?? '0'} used=${sub.getIsUsed()}`)
+ console.log(
+ `[monero] sub ${sub.getIndex()}: addr=${(sub.getAddress() ?? '').substring(0, 16)}... label=${sub.getLabel() ?? ''} balance=${sub.getBalance()?.toString() ?? '0'} used=${sub.getIsUsed()}`,
+ )
}
}
}
- return accounts?.map(a => a.toJson()) ?? []
+ return accounts?.map((a) => a.toJson()) ?? []
}
async getTransactions(): Promise {
if (!this.wallet) throw new Error('Wallet RPC not started')
- const txs = await this.wallet.getTxs() as any[]
+ const txs = (await this.wallet.getTxs()) as any[]
console.log(`[monero] transactions: ${txs?.length ?? 0} returned`)
if (txs?.length) {
for (const tx of txs.slice(0, 5)) {
- console.log(`[monero] tx: hash=${tx.hash?.substring(0, 12) ?? '?'} amount=${tx.amount?.toString() ?? '0'} dir=${tx.direction} height=${tx.height ?? 0}`)
+ const isIn = tx.getIsIncoming()
+ const isOut = tx.getIsOutgoing()
+ const amt = isIn
+ ? (tx.getIncomingAmount() ?? 0n)
+ : (tx.getOutgoingAmount() ?? 0n)
+ console.log(
+ `[monero] tx: hash=${(tx.getHash() ?? '').substring(0, 12)} amount=${amt.toString()} dir=${isIn ? 'in' : 'out'} height=${tx.getHeight() ?? 0}`,
+ )
}
- if (txs.length > 5) console.log(`[monero] ... and ${txs.length - 5} more`)
+ if (txs.length > 5)
+ console.log(`[monero] ... and ${txs.length - 5} more`)
}
- return txs ?? []
+ const result = (txs || []).map((tx: any) => {
+ const isIn = tx.getIsIncoming()
+ const isOut = tx.getIsOutgoing()
+ const amt = isIn
+ ? (tx.getIncomingAmount() ?? 0n)
+ : (tx.getOutgoingAmount() ?? 0n)
+ const block = tx.getBlock()
+ const ts =
+ block?.getTimestamp() ?? tx.getReceivedTimestamp() ?? 0
+ return {
+ hash: tx.getHash(),
+ amount: amt?.toString() ?? '0',
+ timestamp: String(ts),
+ direction: isIn ? ('in' as const) : ('out' as const),
+ height: tx.getHeight() ?? 0,
+ }
+ })
+ result.sort(
+ (a: any, b: any) => Number(b.timestamp) - Number(a.timestamp),
+ )
+ return result
}
async getHeight(): Promise {
if (!this.wallet) throw new Error('Wallet RPC not started')
- const height = await this.wallet.getHeight() as number
+ const height = (await this.wallet.getHeight()) as number
console.log(`[monero] wallet height: ${height}`)
return height
}
@@ -233,8 +325,10 @@ export class MoneroWalletManager {
async getDaemonHeight(): Promise {
if (!this.wallet) throw new Error('Wallet RPC not started')
try {
- const daemon = await connectToDaemonRpc(`http://${this.daemonAddress}`)
- const height = await daemon.getHeight() as number
+ const daemon = await connectToDaemonRpc(
+ `http://${this.daemonAddress}`,
+ )
+ const height = (await daemon.getHeight()) as number
console.log(`[monero] daemon height: ${height}`)
return height
} catch (e) {
@@ -256,7 +350,9 @@ export class MoneroWalletManager {
async isConnected(): Promise {
if (!this.wallet) return false
try {
- const daemon = await connectToDaemonRpc(`http://${this.daemonAddress}`)
+ const daemon = await connectToDaemonRpc(
+ `http://${this.daemonAddress}`,
+ )
await daemon.getHeight()
return true
} catch (e) {
diff --git a/apps/desktop/src/routes/index.svelte b/apps/desktop/src/routes/index.svelte
index fbefa33..bc870c6 100644
--- a/apps/desktop/src/routes/index.svelte
+++ b/apps/desktop/src/routes/index.svelte
@@ -268,12 +268,10 @@
{tx.direction === 'in' ? 'Received' : 'Sent'}
- {Number(tx.amount) > 0
- ? `${tx.direction === 'in' ? '+' : '-'}${(BigInt(tx.amount) / 10_000_000_000_000n).toString()} XMR`
- : '0 XMR'}
+ {atomicToXmr(tx.amount)} XMR
- {#if tx.height > 0}
- Block {tx.height}
+ {#if tx.timestamp && tx.timestamp !== '0'}
+ {new Date(Number(tx.timestamp) * 1000).toLocaleString()}
{/if}
{tx.hash.slice(0, 12)}...