From ea5530d57d0fd556a62b9bf3a9ab090f1df5788e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Jan 2026 16:59:16 +0000 Subject: [PATCH] feat: cache NWC get_info response to avoid redundant calls - Add info$ BehaviorSubject observable to nwc.ts service for cached wallet info - Update WalletInfo type to include methods (required), network (optional) - Update useWallet hook to return cached info via use$(info$) - Update WalletViewer to use cached info instead of calling getInfo() on mount - Update ZapWindow to use cached info instead of calling getInfo() on mount - Update ConnectWalletDialog to populate info$ when connecting - Update NWCConnection type to include network field This eliminates unnecessary NWC get_info calls that were made every time WalletViewer or ZapWindow mounted. The info is now cached from initial connection and served from the observable. --- src/components/ConnectWalletDialog.tsx | 12 ++++++--- src/components/WalletViewer.tsx | 35 +++----------------------- src/components/ZapWindow.tsx | 19 +++++--------- src/hooks/useWallet.ts | 11 +++++++- src/services/nwc.ts | 31 +++++++++++++++++++++++ src/types/app.ts | 3 ++- 6 files changed, 60 insertions(+), 51 deletions(-) diff --git a/src/components/ConnectWalletDialog.tsx b/src/components/ConnectWalletDialog.tsx index 44333d5..533685c 100644 --- a/src/components/ConnectWalletDialog.tsx +++ b/src/components/ConnectWalletDialog.tsx @@ -11,7 +11,7 @@ import { import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useGrimoire } from "@/core/state"; -import { createWalletFromURI } from "@/services/nwc"; +import { createWalletFromURI, setWalletInfo } from "@/services/nwc"; interface ConnectWalletDialogProps { open: boolean; @@ -85,6 +85,7 @@ export default function ConnectWalletDialog({ alias: info.alias, methods: info.methods, notifications: info.notifications, + network: info.network, }, }); @@ -93,12 +94,15 @@ export default function ConnectWalletDialog({ updateNWCBalance(balance); } - // Update info - updateNWCInfo({ + // Update info in state and observable + const cachedInfo = { alias: info.alias, methods: info.methods, notifications: info.notifications, - }); + network: info.network, + }; + updateNWCInfo(cachedInfo); + setWalletInfo(cachedInfo); // Show success toast toast.success("Wallet Connected"); diff --git a/src/components/WalletViewer.tsx b/src/components/WalletViewer.tsx index e610397..7daabb7 100644 --- a/src/components/WalletViewer.tsx +++ b/src/components/WalletViewer.tsx @@ -76,17 +76,6 @@ interface Transaction { metadata?: Record; } -interface WalletInfo { - alias?: string; - color?: string; - pubkey?: string; - network?: string; - block_height?: number; - block_hash?: string; - methods: string[]; - notifications?: string[]; -} - interface InvoiceDetails { amount?: number; description?: string; @@ -408,8 +397,8 @@ export default function WalletViewer() { const { wallet, balance, + info: walletInfo, isConnected, - getInfo, refreshBalance, listTransactions, makeInvoice, @@ -417,8 +406,6 @@ export default function WalletViewer() { lookupInvoice, disconnect, } = useWallet(); - - const [walletInfo, setWalletInfo] = useState(null); const [transactions, setTransactions] = useState([]); const [loading, setLoading] = useState(false); const [loadingMore, setLoadingMore] = useState(false); @@ -429,7 +416,6 @@ export default function WalletViewer() { const [txLoadFailed, setTxLoadFailed] = useState(false); // Use refs to track loading attempts without causing re-renders - const walletInfoLoadedRef = useRef(false); const lastConnectionStateRef = useRef(isConnected); const lastBalanceRefreshRef = useRef(0); const lastTxLoadRef = useRef(0); @@ -462,43 +448,28 @@ export default function WalletViewer() { const [showRawTransaction, setShowRawTransaction] = useState(false); const [copiedRawTx, setCopiedRawTx] = useState(false); - // Load wallet info when connected + // Reset state when connection changes useEffect(() => { // Detect connection state changes if (isConnected !== lastConnectionStateRef.current) { lastConnectionStateRef.current = isConnected; - walletInfoLoadedRef.current = false; if (isConnected) { // Reset transaction loading flags when wallet connects setTxLoadAttempted(false); setTxLoadFailed(false); setTransactions([]); - setWalletInfo(null); } else { // Clear all state when wallet disconnects setTxLoadAttempted(false); setTxLoadFailed(false); setTransactions([]); - setWalletInfo(null); setLoading(false); setLoadingMore(false); setHasMore(true); } } - - // Load wallet info if connected and not yet loaded - if (isConnected && !walletInfoLoadedRef.current) { - walletInfoLoadedRef.current = true; - getInfo() - .then((info) => setWalletInfo(info)) - .catch((error) => { - console.error("Failed to load wallet info:", error); - toast.error("Failed to load wallet info"); - walletInfoLoadedRef.current = false; // Allow retry - }); - } - }, [isConnected, getInfo]); + }, [isConnected]); // Load transactions when wallet info is available (only once) useEffect(() => { diff --git a/src/components/ZapWindow.tsx b/src/components/ZapWindow.tsx index b4eb3c4..f6154aa 100644 --- a/src/components/ZapWindow.tsx +++ b/src/components/ZapWindow.tsx @@ -11,7 +11,7 @@ * - Shows feed render of zapped event */ -import { useState, useMemo, useEffect, useRef } from "react"; +import { useState, useMemo, useRef } from "react"; import { toast } from "sonner"; import { Zap, @@ -117,17 +117,7 @@ export function ZapWindow({ const activeAccount = accountManager.active; const canSign = !!activeAccount?.signer; - const { wallet, payInvoice, refreshBalance, getInfo } = useWallet(); - - // Fetch wallet info - const [walletInfo, setWalletInfo] = useState(null); - useEffect(() => { - if (wallet) { - getInfo() - .then((info) => setWalletInfo(info)) - .catch((error) => console.error("Failed to get wallet info:", error)); - } - }, [wallet, getInfo]); + const { wallet, info: walletInfo, payInvoice, refreshBalance } = useWallet(); // Cache LNURL data for recipient's Lightning address const { data: lnurlData } = useLnurlCache(recipientProfile?.lud16); @@ -717,7 +707,10 @@ export function ZapWindow({ isPaid ? onClose?.() : handleZap( - wallet && walletInfo?.methods.includes("pay_invoice"), + !!( + wallet && + walletInfo?.methods.includes("pay_invoice") + ), ) } disabled={ diff --git a/src/hooks/useWallet.ts b/src/hooks/useWallet.ts index 07c5bdd..1acbe39 100644 --- a/src/hooks/useWallet.ts +++ b/src/hooks/useWallet.ts @@ -29,6 +29,8 @@ import { clearWallet as clearWalletService, refreshBalance as refreshBalanceService, balance$, + info$, + type WalletInfo, } from "@/services/nwc"; import type { WalletConnect } from "applesauce-wallet-connect"; @@ -40,6 +42,9 @@ export function useWallet() { // Subscribe to balance updates from observable (fully reactive!) const balance = use$(balance$); + // Subscribe to cached wallet info (from initial connection) + const info = use$(info$); + // Initialize wallet on mount if connection exists but no wallet instance useEffect(() => { if (nwcConnection && !wallet) { @@ -175,13 +180,15 @@ export function useWallet() { wallet, /** Current balance in millisats (auto-updates via observable!) */ balance, + /** Cached wallet info (alias, methods, notifications) from initial connection */ + info, /** Whether a wallet is connected */ isConnected: !!wallet, /** Pay a BOLT11 invoice */ payInvoice, /** Generate a new invoice */ makeInvoice, - /** Get wallet information */ + /** Get wallet information (prefer using cached `info` instead) */ getInfo, /** Get current balance */ getBalance, @@ -197,3 +204,5 @@ export function useWallet() { disconnect, }; } + +export type { WalletInfo }; diff --git a/src/services/nwc.ts b/src/services/nwc.ts index 8c40ba0..29704ff 100644 --- a/src/services/nwc.ts +++ b/src/services/nwc.ts @@ -28,6 +28,23 @@ let notificationSubscription: Subscription | null = null; */ export const balance$ = new BehaviorSubject(undefined); +/** + * Cached wallet info type + * Contains the essential fields from NIP-47 get_info response + */ +export type WalletInfo = { + alias?: string; + methods: string[]; + notifications?: string[]; + network?: string; +}; + +/** + * Observable for wallet info + * Cached from initial connection, components can subscribe via use$() + */ +export const info$ = new BehaviorSubject(undefined); + /** * Helper to convert hex string to Uint8Array */ @@ -107,6 +124,11 @@ export function restoreWallet(connection: NWCConnection): WalletConnect { balance$.next(connection.balance); } + // Set cached info from connection + if (connection.info) { + info$.next(connection.info); + } + subscribeToNotifications(walletInstance); return walletInstance; } @@ -128,6 +150,15 @@ export function clearWallet(): void { } walletInstance = null; balance$.next(undefined); + info$.next(undefined); +} + +/** + * Set the cached wallet info + * Called after initial connection to cache the get_info response + */ +export function setWalletInfo(info: WalletInfo): void { + info$.next(info); } /** diff --git a/src/types/app.ts b/src/types/app.ts index b7d1e88..ec1a951 100644 --- a/src/types/app.ts +++ b/src/types/app.ts @@ -98,8 +98,9 @@ export interface NWCConnection { /** Optional wallet info */ info?: { alias?: string; - methods?: string[]; + methods: string[]; notifications?: string[]; + network?: string; }; /** Last connection time */ lastConnected?: number;