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.
This commit is contained in:
Claude
2026-01-19 16:59:16 +00:00
parent 8f008ddd39
commit ea5530d57d
6 changed files with 60 additions and 51 deletions

View File

@@ -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");

View File

@@ -76,17 +76,6 @@ interface Transaction {
metadata?: Record<string, any>;
}
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<WalletInfo | null>(null);
const [transactions, setTransactions] = useState<Transaction[]>([]);
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(() => {

View File

@@ -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<any>(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={

View File

@@ -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 };

View File

@@ -28,6 +28,23 @@ let notificationSubscription: Subscription | null = null;
*/
export const balance$ = new BehaviorSubject<number | undefined>(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<WalletInfo | undefined>(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);
}
/**

View File

@@ -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;