fix(wallet): show Cashu/Fedimint receives in the Transactions modal

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.
This commit is contained in:
ssmithx 2026-07-03 02:46:48 +00:00
parent 8f47d6608a
commit d99f4438d8

View File

@ -519,6 +519,35 @@ const walletTransactions = ref<WalletTransaction[]>([])
function openInMempool(txHash: string) { router.push({ name: 'app-session', params: { appId: 'mempool' }, query: { path: `/tx/${txHash}` } }) } 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() { async function loadWeb5Status() {
// A transient RPC timeout must NOT flash the balance to 0 ("wallet says 0 when // 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 // 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; 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.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<{ 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 // System stats