From 5f7e976a06a9f9713addb069f2139045aa7ffca7 Mon Sep 17 00:00:00 2001 From: QSchlegel Date: Tue, 28 Oct 2025 19:24:41 +0100 Subject: [PATCH] Implement proxy data fetching improvements and error handling - Added retry logic for connection errors in RootLayout. - Refactored WalletDataLoader and WalletDataLoaderWrapper to use a new batch function for fetching all proxy data in parallel, replacing individual fetch calls. - Updated ProxyControl to remove redundant balance fetching, as it is now handled globally. - Introduced fetchAllProxyData method in the proxy store for improved data management. --- .../common/overall-layout/layout.tsx | 13 +++ .../wallet-data-loader-wrapper.tsx | 56 ++-------- .../overall-layout/wallet-data-loader.tsx | 58 ++-------- .../multisig/proxy/ProxyControl.tsx | 64 +++-------- src/lib/zustand/proxy.ts | 103 ++++++++++++++++++ 5 files changed, 154 insertions(+), 140 deletions(-) diff --git a/src/components/common/overall-layout/layout.tsx b/src/components/common/overall-layout/layout.tsx index 8b78317..5d549a8 100644 --- a/src/components/common/overall-layout/layout.tsx +++ b/src/components/common/overall-layout/layout.tsx @@ -161,6 +161,19 @@ export default function RootLayout({ return; } + // Handle connection errors with retry logic + if (error instanceof Error && ( + error.message.includes("Could not establish connection") || + error.message.includes("Receiving end does not exist") || + error.message.includes("Cannot read properties of undefined") + )) { + console.log("Browser extension connection error detected, retrying in 2 seconds..."); + setTimeout(() => { + window.location.reload(); + }, 2000); + return; + } + // For other errors, don't throw to prevent app crash // The user can retry by reconnecting their wallet } diff --git a/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx b/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx index 81e1de5..6398365 100644 --- a/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx +++ b/src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx @@ -48,7 +48,7 @@ export default function WalletDataLoaderWrapper({ const setWalletAssetMetadata = useWalletsStore( (state) => state.setWalletAssetMetadata, ); - const { fetchProxyBalance, fetchProxyDrepInfo, fetchProxyDelegatorsInfo, setProxies } = useProxyActions(); + const { fetchAllProxyData, setProxies } = useProxyActions(); const setDrepInfo = useWalletsStore((state) => state.setDrepInfo); @@ -189,49 +189,20 @@ export default function WalletDataLoaderWrapper({ walletId: appWallet.id, }); - // First, add proxies to the store setProxies(appWallet.id, proxies); - // Fetch balance and DRep info for each proxy - for (const proxy of proxies) { - try { - - // Fetch balance - await fetchProxyBalance( - appWallet.id, - proxy.id, - proxy.proxyAddress, - network.toString() - ); - - // Fetch DRep info with force refresh - await fetchProxyDrepInfo( - appWallet.id, - proxy.id, - proxy.proxyAddress, - proxy.authTokenId, - appWallet.scriptCbor, - network.toString(), - proxy.paramUtxo, - true // Force refresh to bypass cache - ); - - // Fetch delegators info with force refresh - await fetchProxyDelegatorsInfo( - appWallet.id, - proxy.id, - proxy.proxyAddress, - proxy.authTokenId, - appWallet.scriptCbor, - network.toString(), - proxy.paramUtxo, - true // Force refresh to bypass cache - ); - - } catch (error) { - console.error(`WalletDataLoaderWrapper: Error fetching data for proxy ${proxy.id}:`, error); - } + // Fetch all proxy data in parallel using the new batch function + if (proxies.length > 0) { + console.log(`WalletDataLoaderWrapper: Fetching data for ${proxies.length} proxies in parallel`); + await fetchAllProxyData( + appWallet.id, + proxies, + appWallet.scriptCbor, + network.toString(), + false // Use cache to avoid duplicate requests + ); + console.log("WalletDataLoaderWrapper: Successfully fetched all proxy data"); } } catch (error) { console.error("WalletDataLoaderWrapper: Error fetching proxy data:", error); @@ -267,9 +238,6 @@ export default function WalletDataLoaderWrapper({ if (appWallet && prevWalletIdRef.current !== appWallet.id) { refreshWallet(); prevWalletIdRef.current = appWallet.id; - } else if (appWallet) { - // If wallet exists but we already have data, still fetch proxy data - fetchProxyData(); } }, [appWallet]); diff --git a/src/components/common/overall-layout/wallet-data-loader.tsx b/src/components/common/overall-layout/wallet-data-loader.tsx index af28bb1..6920b24 100644 --- a/src/components/common/overall-layout/wallet-data-loader.tsx +++ b/src/components/common/overall-layout/wallet-data-loader.tsx @@ -20,7 +20,7 @@ export default function WalletDataLoader() { const ctx = api.useUtils(); const network = useSiteStore((state) => state.network); const setRandomState = useSiteStore((state) => state.setRandomState); - const { fetchProxyBalance, fetchProxyDrepInfo, fetchProxyDelegatorsInfo, setProxies } = useProxyActions(); + const { fetchAllProxyData, setProxies } = useProxyActions(); async function fetchUtxos() { if (appWallet) { @@ -70,47 +70,17 @@ export default function WalletDataLoader() { // First, add proxies to the store setProxies(appWallet.id, proxies); - // Fetch balance and DRep info for each proxy - for (const proxy of proxies) { - try { - console.log(`WalletDataLoader: Fetching data for proxy ${proxy.id}`); - - // Fetch balance - await fetchProxyBalance( - appWallet.id, - proxy.id, - proxy.proxyAddress, - network.toString() - ); - - // Fetch DRep info with force refresh - await fetchProxyDrepInfo( - appWallet.id, - proxy.id, - proxy.proxyAddress, - proxy.authTokenId, - appWallet.scriptCbor, - network.toString(), - proxy.paramUtxo, - true // Force refresh to bypass cache - ); - - // Fetch delegators info with force refresh - await fetchProxyDelegatorsInfo( - appWallet.id, - proxy.id, - proxy.proxyAddress, - proxy.authTokenId, - appWallet.scriptCbor, - network.toString(), - proxy.paramUtxo, - true // Force refresh to bypass cache - ); - - console.log(`WalletDataLoader: Successfully fetched data for proxy ${proxy.id}`); - } catch (error) { - console.error(`WalletDataLoader: Error fetching data for proxy ${proxy.id}:`, error); - } + // Fetch all proxy data in parallel using the new batch function + if (proxies.length > 0) { + console.log(`WalletDataLoader: Fetching data for ${proxies.length} proxies in parallel`); + await fetchAllProxyData( + appWallet.id, + proxies, + appWallet.scriptCbor, + network.toString(), + false // Use cache to avoid duplicate requests + ); + console.log("WalletDataLoader: Successfully fetched all proxy data"); } } catch (error) { console.error("WalletDataLoader: Error fetching proxy data:", error); @@ -144,10 +114,6 @@ export default function WalletDataLoader() { if (appWallet && walletsUtxos[appWallet?.id] === undefined) { console.log("WalletDataLoader: Calling refreshWallet"); refreshWallet(); - } else if (appWallet) { - // If wallet exists but we already have UTxOs, still fetch proxy data - console.log("WalletDataLoader: Calling fetchProxyData directly"); - fetchProxyData(); } }, [appWallet]); diff --git a/src/components/multisig/proxy/ProxyControl.tsx b/src/components/multisig/proxy/ProxyControl.tsx index c45a8b9..bdd7d86 100644 --- a/src/components/multisig/proxy/ProxyControl.tsx +++ b/src/components/multisig/proxy/ProxyControl.tsx @@ -402,31 +402,12 @@ export default function ProxyControl() { } }, [network, wallet, appWallet?.scriptCbor]); - // Fetch all proxy balances for TVL calculation + // Fetch all proxy balances for TVL calculation (now handled globally) const fetchAllProxyBalances = useCallback(async () => { - if (!proxies || proxies.length === 0 || !proxyContract) return; - - try { - setTvlLoading(true); - const balances: Record> = {}; - - for (const proxy of proxies) { - try { - const balance = await getProxyBalance(proxy.proxyAddress); - balances[proxy.id] = balance; - } catch (error) { - console.error(`Failed to fetch balance for proxy ${proxy.id}:`, error); - balances[proxy.id] = []; - } - } - - // Balances handled elsewhere - } catch (error) { - console.error("Failed to fetch proxy balances:", error); - } finally { - setTvlLoading(false); - } - }, [proxies, proxyContract, getProxyBalance]); + // This function is now handled globally by WalletDataLoaderWrapper + // to avoid duplicate API calls. The proxy store already contains the balance data. + console.log("ProxyControl: fetchAllProxyBalances called but data is handled globally"); + }, []); // Calculate Total Value Locked (TVL) across all proxies const calculateTVL = useCallback(() => { @@ -456,37 +437,20 @@ export default function ProxyControl() { const { totalADA, totalAssets } = calculateTVL(); - // Fetch all proxy balances when proxies change - useEffect(() => { - if (proxies && proxies.length > 0 && proxyContract) { - void fetchAllProxyBalances(); - } - }, [proxies, proxyContract, fetchAllProxyBalances]); - - // Refresh balances when component mounts or wallet changes - useEffect(() => { - if (proxies && proxies.length > 0 && proxyContract && connected) { - // Small delay to ensure everything is initialized - const timer = setTimeout(() => { - void fetchAllProxyBalances(); - }, 1000); - return () => clearTimeout(timer); - } - }, [connected, proxyContract, fetchAllProxyBalances, proxies]); + // Proxy balance data is now handled globally by WalletDataLoaderWrapper + // No need to fetch balances here as they're already in the proxy store // Manual TVL refresh function const refreshTVL = useCallback(async () => { - if (proxies && proxies.length > 0 && proxyContract) { - await fetchAllProxyBalances(); - } - }, [proxies, proxyContract, fetchAllProxyBalances]); + // TVL is calculated from proxy store data, no need to fetch + console.log("ProxyControl: refreshTVL called - data comes from proxy store"); + }, []); - // Global refresh function for all proxy balances (unused but kept for potential future use) + // Global refresh function for all proxy balances (now handled globally) const refreshAllBalances = useCallback(async () => { - if (proxies && proxies.length > 0 && proxyContract) { - void fetchAllProxyBalances(); - } - }, [proxies, proxyContract, fetchAllProxyBalances]); + // Balance data is now handled globally by WalletDataLoaderWrapper + console.log("ProxyControl: refreshAllBalances called - data handled globally"); + }, []); // Spend outputs management const handleSpendOutputsChange = useCallback((outputs: ProxyOutput[]) => { diff --git a/src/lib/zustand/proxy.ts b/src/lib/zustand/proxy.ts index 68c360e..e537a81 100644 --- a/src/lib/zustand/proxy.ts +++ b/src/lib/zustand/proxy.ts @@ -74,6 +74,7 @@ interface ProxyState { fetchProxyBalance: (walletId: string, proxyId: string, proxyAddress: string, network: string) => Promise; fetchProxyDrepInfo: (walletId: string, proxyId: string, proxyAddress: string, authTokenId: string, scriptCbor: string, network: string, paramUtxo: string, forceRefresh?: boolean) => Promise; fetchProxyDelegatorsInfo: (walletId: string, proxyId: string, proxyAddress: string, authTokenId: string, scriptCbor: string, network: string, paramUtxo: string, forceRefresh?: boolean) => Promise; + fetchAllProxyData: (walletId: string, proxies: ProxyData[], scriptCbor: string, network: string, forceRefresh?: boolean) => Promise; // Utility actions updateProxyData: (walletId: string, proxyId: string, updates: Partial) => void; @@ -249,6 +250,106 @@ export const useProxyStore = create()( get().setDrepLoading(proxyId, false); } }, + + // Fetch all proxy data in parallel + fetchAllProxyData: async (walletId, proxies, scriptCbor, network, forceRefresh = false) => { + try { + // Prevent multiple simultaneous fetches for the same wallet + const currentState = get(); + if (currentState.loading[walletId]) { + console.log(`Proxy data already loading for wallet ${walletId}, skipping...`); + return; + } + + // Check if data is fresh (less than 30 seconds old) and skip if not forcing refresh + if (!forceRefresh && currentState.proxies[walletId]) { + const oldestUpdate = Math.min( + ...currentState.proxies[walletId].map(p => p.lastUpdated || 0) + ); + const isDataFresh = oldestUpdate > 0 && (Date.now() - oldestUpdate) < 30000; // 30 seconds + if (isDataFresh) { + console.log(`Proxy data is fresh (less than 30s old) for wallet ${walletId}, skipping...`); + return; + } + } + + get().setLoading(walletId, true); + get().setError(walletId, null); + + // Create a single txBuilder instance to reuse across all proxies + const txBuilder = getTxBuilder(parseInt(network)); + + // Create all fetch promises in parallel + const fetchPromises = proxies.map(async (proxy) => { + try { + // Set loading state for this proxy + get().setDrepLoading(proxy.id, true); + get().setDrepError(proxy.id, null); + + // Reuse the same txBuilder instance for all proxies + const proxyContract = new MeshProxyContract( + { + mesh: txBuilder, + wallet: undefined, + networkId: parseInt(network), + }, + { + paramUtxo: JSON.parse(proxy.paramUtxo || '{}'), + }, + scriptCbor, + ); + proxyContract.proxyAddress = proxy.proxyAddress; + + // Fetch all data for this proxy in parallel + const [balance, drepStatus, delegators] = await Promise.allSettled([ + proxyContract.getProxyBalance(), + proxyContract.getDrepStatus(forceRefresh), + proxyContract.getDrepDelegators(forceRefresh), + ]); + + // Get DRep ID + const drepId = proxyContract.getDrepId(); + + // Process results + const drepInfo: ProxyDrepInfo | undefined = drepStatus.status === 'fulfilled' ? drepStatus.value : undefined; + const delegatorsInfo: ProxyDelegatorsInfo | undefined = delegators.status === 'fulfilled' ? (delegators.value as ProxyDelegatorsInfo) : undefined; + + // Update the specific proxy's data + const currentState = get(); + const updatedProxies = currentState.proxies[walletId]?.map(p => + p.id === proxy.id + ? { + ...p, + balance: balance.status === 'fulfilled' ? balance.value : p.balance, + drepId, + drepInfo, + delegatorsInfo, + lastUpdated: Date.now() + } + : p + ) || []; + + set((state) => ({ + proxies: { ...state.proxies, [walletId]: updatedProxies }, + drepLoading: { ...state.drepLoading, [proxy.id]: false }, + drepErrors: { ...state.drepErrors, [proxy.id]: null }, + })); + + } catch (error) { + get().setDrepError(proxy.id, `Failed to fetch data for proxy ${proxy.id}`); + get().setDrepLoading(proxy.id, false); + } + }); + + // Wait for all proxy data to be fetched + await Promise.allSettled(fetchPromises); + + get().setLoading(walletId, false); + } catch (error) { + get().setError(walletId, `Failed to fetch proxy data: ${error instanceof Error ? error.message : 'Unknown error'}`); + get().setLoading(walletId, false); + } + }, // Update specific proxy data updateProxyData: (walletId, proxyId, updates) => @@ -324,6 +425,7 @@ export const useProxyActions = () => { const fetchProxyBalance = useProxyStore((state) => state.fetchProxyBalance); const fetchProxyDrepInfo = useProxyStore((state) => state.fetchProxyDrepInfo); const fetchProxyDelegatorsInfo = useProxyStore((state) => state.fetchProxyDelegatorsInfo); + const fetchAllProxyData = useProxyStore((state) => state.fetchAllProxyData); const updateProxyData = useProxyStore((state) => state.updateProxyData); const clearProxyData = useProxyStore((state) => state.clearProxyData); @@ -336,6 +438,7 @@ export const useProxyActions = () => { fetchProxyBalance, fetchProxyDrepInfo, fetchProxyDelegatorsInfo, + fetchAllProxyData, updateProxyData, clearProxyData, };