diff --git a/apps/desktop/src/bun/index.ts b/apps/desktop/src/bun/index.ts index 2f0cf97..f96d1ca 100644 --- a/apps/desktop/src/bun/index.ts +++ b/apps/desktop/src/bun/index.ts @@ -727,6 +727,14 @@ const rpc = BrowserView.defineRPC({ console.log('[rpc] moneroTransfer complete:', result.txHash) return result }, + moneroGetTransferDetails: async ({ txid }) => { + console.log('[rpc] moneroGetTransferDetails:', txid) + if (!moneroManager) + throw new Error('Monero wallet RPC not started') + const details = await moneroManager.getTransferDetails(txid) + console.log('[rpc] moneroGetTransferDetails complete:', details?.hash) + return details + }, evmCreateWallet: async ({ name, phrase, passwordHash }) => { console.log('[rpc] evmCreateWallet:', name) const id = crypto.randomUUID() diff --git a/apps/desktop/src/bun/lib/monero/wallet.ts b/apps/desktop/src/bun/lib/monero/wallet.ts index 1bf716e..d12f544 100644 --- a/apps/desktop/src/bun/lib/monero/wallet.ts +++ b/apps/desktop/src/bun/lib/monero/wallet.ts @@ -305,6 +305,38 @@ export class MoneroWalletManager { return all } + async getTransferDetails(txid: string): Promise { + if (!this.wallet) throw new Error('Wallet RPC not started') + const result = await this.rawRpc('get_transfer_by_txid', { txid }) + if (!result?.transfer) return null + const t = result.transfer + const isIn = t.type === 'in' + const isOut = t.type === 'out' + const dests = t.destinations?.map((d: any) => ({ + address: d.address, + amount: d.amount?.toString() ?? '0', + })) ?? [] + const subaddrIndices = isIn + ? [{ major: t.subaddr_index?.major ?? 0, minor: t.subaddr_index?.minor ?? 0 }] + : (t.subaddr_indices ?? []).map((s: any) => ({ major: s.major ?? 0, minor: s.minor ?? 0 })) + return { + hash: t.txid, + direction: isIn ? ('in' as const) : ('out' as const), + amount: t.amount?.toString() ?? '0', + fee: t.fee?.toString() ?? '0', + height: t.height ?? 0, + timestamp: String(t.timestamp ?? 0), + confirmations: t.confirmations ?? 0, + unlockTime: t.unlock_time ?? 0, + locked: t.locked ?? false, + doubleSpend: t.double_spend_seen ?? false, + note: t.note, + paymentId: t.payment_id ?? '', + destinations: dests, + subaddrIndices, + } + } + async getHeight(): Promise { if (!this.wallet) throw new Error('Wallet RPC not started') const height = (await this.wallet.getHeight()) as number diff --git a/apps/desktop/src/lib/components/EvmDashboard.svelte b/apps/desktop/src/lib/components/EvmDashboard.svelte index b04f0c5..49a8e01 100644 --- a/apps/desktop/src/lib/components/EvmDashboard.svelte +++ b/apps/desktop/src/lib/components/EvmDashboard.svelte @@ -261,6 +261,12 @@ ? 'bg-primary text-primary-foreground' : 'bg-muted text-muted-foreground hover:bg-muted/80'}" onclick={() => navigate(navTarget(net.id))}> + + ((e.target as HTMLElement).style.display = 'none')} /> {net.name} {/each} diff --git a/apps/desktop/src/lib/electrobun.ts b/apps/desktop/src/lib/electrobun.ts index def2c6d..9ce1bba 100644 --- a/apps/desktop/src/lib/electrobun.ts +++ b/apps/desktop/src/lib/electrobun.ts @@ -8,6 +8,7 @@ export type { MoneroSubaddressEntry, MoneroAccountEntry, MoneroSendResult, + MoneroTransferDetails, TxEntry, TransactionDetails, TokenBalanceResult, diff --git a/apps/desktop/src/lib/rpc-schema.ts b/apps/desktop/src/lib/rpc-schema.ts index 83f8e33..074a10b 100644 --- a/apps/desktop/src/lib/rpc-schema.ts +++ b/apps/desktop/src/lib/rpc-schema.ts @@ -46,6 +46,23 @@ export type MoneroSendResult = { amount: string } +export type MoneroTransferDetails = { + hash: string + direction: 'in' | 'out' + amount: string + fee: string + height: number + timestamp: string + confirmations: number + unlockTime: number + locked: boolean + doubleSpend: boolean + note?: string + paymentId: string + destinations: { address: string; amount: string }[] + subaddrIndices: { major: number; minor: number }[] +} + export type MoneroAccountEntry = { index: number primaryAddress: string @@ -137,6 +154,7 @@ export type RPC = { moneroGetAccounts: { params: {}; response: MoneroAccountEntry[] } moneroListWallets: { params: {}; response: string[] } moneroTransfer: { params: { address: string; amount: string; priority?: number; accountIndex?: number }; response: MoneroSendResult } + moneroGetTransferDetails: { params: { txid: string }; response: MoneroTransferDetails | null } evmCreateWallet: { params: { name: string; phrase: string; passwordHash?: string }; response: { id: string; name: string; createdAt: string } } evmListWallets: { params: {}; response: EvmWalletInfo[] } evmGetSeed: { params: { vaultKey: string }; response: string } diff --git a/apps/desktop/src/routes/monero/index.svelte b/apps/desktop/src/routes/monero/index.svelte index 46590a7..4a54922 100644 --- a/apps/desktop/src/routes/monero/index.svelte +++ b/apps/desktop/src/routes/monero/index.svelte @@ -266,7 +266,9 @@ {:else}
{#each w.txs as tx} -
+
+ {/each}
{/if} diff --git a/apps/desktop/src/routes/monero/tx/[hash].svelte b/apps/desktop/src/routes/monero/tx/[hash].svelte new file mode 100644 index 0000000..cf433cf --- /dev/null +++ b/apps/desktop/src/routes/monero/tx/[hash].svelte @@ -0,0 +1,157 @@ + + +
+ + + + + Transaction Details + + + {#if loading} +

Loading...

+ {:else if error} +

{error}

+ {:else if details} +
+
+
+ {#if details.direction === 'in'} + + {:else} + + {/if} +
+

+ {details.direction === 'in' ? 'Received' : 'Sent'} +

+
+ +
+

Transaction Hash

+

{details.hash}

+
+ +
+
+

Amount

+

{atomicToXmr(details.amount)} XMR

+
+
+

Fee

+

{atomicToXmr(details.fee)} XMR

+
+
+ +
+
+

Height

+

{details.height.toLocaleString()}

+
+
+

Confirmations

+

{details.confirmations.toLocaleString()}

+
+
+ + {#if details.timestamp && details.timestamp !== '0'} +
+

Date

+

{new Date(Number(details.timestamp) * 1000).toLocaleString()}

+
+ {/if} + + {#if details.direction === 'out' && details.destinations.length > 0} +
+

Destinations

+ {#each details.destinations as dest} +

{dest.address}

+

{atomicToXmr(dest.amount)} XMR

+ {/each} +
+ {/if} + +
+
+

Locked

+

{details.locked ? 'Yes' : 'No'}

+
+
+

Double Spend

+

{details.doubleSpend ? 'Yes' : 'No'}

+
+
+ + {#if details.note} +
+

Note

+

{details.note}

+
+ {/if} + + {#if details.paymentId} +
+

Payment ID

+

{details.paymentId}

+
+ {/if} +
+
+ +
+ {:else} +

No details available

+ {/if} +
+
+