From d99f4438d8e73749be152f6987c0d0b0db3cbdee Mon Sep 17 00:00:00 2001 From: ssmithx Date: Fri, 3 Jul 2026 02:46:48 +0000 Subject: [PATCH] fix(wallet): show Cashu/Fedimint receives in the Transactions modal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found live: after receiving a TollGate Cashu payment into the wallet, the balance correctly showed the new sats (wallet.ecash-balance), but the Transactions modal said "no transactions yet." Home.vue's loadWeb5Status() only ever fetched lnd.gettransactions — ecash and Fedimint activity (wallet.ecash-history, which already unifies both) was never wired into walletTransactions at all, so no Cashu or Fedimint receive could ever show up there regardless of how long you waited. Maps EcashTransaction into the existing (LND-shaped) WalletTransaction interface rather than widening that type, and merges+sorts both lists by timestamp. --- neode-ui/src/views/Home.vue | 39 ++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/neode-ui/src/views/Home.vue b/neode-ui/src/views/Home.vue index d733404e..245988bb 100644 --- a/neode-ui/src/views/Home.vue +++ b/neode-ui/src/views/Home.vue @@ -519,6 +519,35 @@ const walletTransactions = ref([]) function openInMempool(txHash: string) { router.push({ name: 'app-session', params: { appId: 'mempool' }, query: { path: `/tx/${txHash}` } }) } +// wallet.ecash-history's shape (see handle_wallet_ecash_history in +// api/rpc/wallet.rs) — distinct from the LND-shaped WalletTransaction used +// elsewhere in this file, so it's mapped into that shape below rather than +// widening WalletTransaction itself with a pile of ecash-only fields. +interface EcashTransaction { + id: string + tx_type: 'send' | 'receive' + amount_sats: number + timestamp: string + description: string + mint_url: string + peer: string + kind: 'cashu' | 'fedimint' +} + +function ecashToWalletTransaction(tx: EcashTransaction): WalletTransaction { + return { + tx_hash: tx.id, + amount_sats: tx.amount_sats, + direction: tx.tx_type === 'receive' ? 'incoming' : 'outgoing', + num_confirmations: 1, + time_stamp: Math.floor(new Date(tx.timestamp).getTime() / 1000), + total_fees: 0, + dest_addresses: [], + label: tx.description, + block_height: 0, + } +} + async function loadWeb5Status() { // A transient RPC timeout must NOT flash the balance to 0 ("wallet says 0 when // there is a balance"). On failure keep the last-known value — the refs start @@ -526,7 +555,15 @@ async function loadWeb5Status() { try { const res = await rpcClient.call<{ balance_sats: number; channel_balance_sats: number }>({ method: 'lnd.getinfo', timeout: 5000 }); walletOnchain.value = res.balance_sats || 0; walletLightning.value = res.channel_balance_sats || 0; walletConnected.value = true } catch { walletConnected.value = false } try { const res = await rpcClient.call<{ balance_sats: number }>({ method: 'wallet.ecash-balance', timeout: 5000 }); walletEcash.value = res.balance_sats ?? 0 } catch { /* keep last-known balance */ } try { const res = await rpcClient.call<{ balance_sats: number }>({ method: 'wallet.fedimint-balance', timeout: 5000 }); walletFedimint.value = res.balance_sats ?? 0 } catch { /* keep last-known balance */ } - try { const res = await rpcClient.call<{ transactions: WalletTransaction[]; incoming_pending_count: number }>({ method: 'lnd.gettransactions', timeout: 5000 }); walletTransactions.value = res.transactions || [] } catch { /* keep last-known transactions */ } + // Merge LND transactions with ecash/Fedimint history (wallet.ecash-history + // already unifies both) — previously only LND transactions were fetched + // here, so any Cashu or Fedimint receive (e.g. a TollGate payment) never + // appeared in the Transactions modal even though the balance included it. + let lndTxs: WalletTransaction[] = [] + try { const res = await rpcClient.call<{ transactions: WalletTransaction[]; incoming_pending_count: number }>({ method: 'lnd.gettransactions', timeout: 5000 }); lndTxs = res.transactions || [] } catch { /* keep last-known transactions */ } + let ecashTxs: WalletTransaction[] = [] + try { const res = await rpcClient.call<{ transactions: EcashTransaction[] }>({ method: 'wallet.ecash-history', timeout: 5000 }); ecashTxs = (res.transactions || []).map(ecashToWalletTransaction) } catch { /* keep last-known transactions */ } + walletTransactions.value = [...lndTxs, ...ecashTxs].sort((a, b) => b.time_stamp - a.time_stamp) } // System stats