From d037094bc70de96353acb5ad79527547939dd340 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 12 Jan 2026 20:07:15 +0000 Subject: [PATCH] feat: add transaction history to wallet viewer Add expandable transaction list when wallet supports list_transactions: - Import Transaction type from applesauce-wallet-connect - Add collapsible transaction history section - Display transaction items with type icons (incoming/outgoing) - Show amount, fees, description, and relative timestamp - Auto-load transactions when section expanded - Color-code incoming (green) vs outgoing (orange) transactions - Use date-fns for relative time formatting - Limit to 50 most recent transactions - Scrollable transaction list with max height Enhances wallet viewer with full transaction visibility for supported NIP-47 wallet services. --- src/components/WalletViewer.tsx | 138 +++++++++++++++++++++++++++++++- 1 file changed, 136 insertions(+), 2 deletions(-) diff --git a/src/components/WalletViewer.tsx b/src/components/WalletViewer.tsx index 148a37e..8088778 100644 --- a/src/components/WalletViewer.tsx +++ b/src/components/WalletViewer.tsx @@ -1,5 +1,16 @@ -import { useState } from "react"; -import { Wallet, Zap, RefreshCw, Copy, Check } from "lucide-react"; +import { useState, useEffect, useCallback } from "react"; +import { + Wallet, + Zap, + RefreshCw, + Copy, + Check, + ChevronDown, + ChevronRight, + ArrowUpRight, + ArrowDownLeft, + Clock, +} from "lucide-react"; import { use$ } from "applesauce-react/hooks"; import walletManager from "@/services/wallet"; import { Button } from "@/components/ui/button"; @@ -7,12 +18,17 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { toast } from "sonner"; import WalletConnectDialog from "./WalletConnectDialog"; import { npubEncode } from "applesauce-core/helpers"; +import { formatDistanceToNow } from "date-fns"; +import type { Transaction } from "applesauce-wallet-connect/helpers/methods"; export default function WalletViewer() { const walletState = use$(walletManager.state$); const [showConnect, setShowConnect] = useState(false); const [isRefreshing, setIsRefreshing] = useState(false); const [copiedPubkey, setCopiedPubkey] = useState(false); + const [transactions, setTransactions] = useState([]); + const [isLoadingTx, setIsLoadingTx] = useState(false); + const [showTransactions, setShowTransactions] = useState(false); async function handleRefresh() { setIsRefreshing(true); @@ -44,6 +60,32 @@ export default function WalletViewer() { return sats.toLocaleString(); } + const supportsTransactions = + walletState.info?.methods.includes("list_transactions"); + + const loadTransactions = useCallback(async () => { + const wallet = walletManager.getWallet(); + if (!wallet || !supportsTransactions) return; + + setIsLoadingTx(true); + try { + const result = await wallet.listTransactions({ + limit: 50, + }); + setTransactions(result.transactions || []); + } catch (_error) { + toast.error("Failed to load transactions"); + } finally { + setIsLoadingTx(false); + } + }, [supportsTransactions]); + + useEffect(() => { + if (showTransactions && transactions.length === 0) { + loadTransactions(); + } + }, [showTransactions, transactions.length, loadTransactions]); + if (!walletState.connected || !walletState.info) { return (
@@ -152,6 +194,47 @@ export default function WalletViewer() { + {/* Transactions */} + {supportsTransactions && ( + + setShowTransactions(!showTransactions)} + > +
+ + Transactions + +
+ {isLoadingTx && ( + + )} + {showTransactions ? ( + + ) : ( + + )} +
+
+
+ {showTransactions && ( + + {transactions.length === 0 ? ( +
+ {isLoadingTx ? "Loading..." : "No transactions"} +
+ ) : ( +
+ {transactions.map((tx) => ( + + ))} +
+ )} +
+ )} +
+ )} + {/* Actions */}