Skip to content

Commit 352dcf2

Browse files
authored
Merge pull request #160 from MeshJS/feature/aiken-multisig
Implement proxy data fetching improvements and error handling
2 parents fc957dc + 5f7e976 commit 352dcf2

File tree

5 files changed

+154
-140
lines changed

5 files changed

+154
-140
lines changed

src/components/common/overall-layout/layout.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,19 @@ export default function RootLayout({
161161
return;
162162
}
163163

164+
// Handle connection errors with retry logic
165+
if (error instanceof Error && (
166+
error.message.includes("Could not establish connection") ||
167+
error.message.includes("Receiving end does not exist") ||
168+
error.message.includes("Cannot read properties of undefined")
169+
)) {
170+
console.log("Browser extension connection error detected, retrying in 2 seconds...");
171+
setTimeout(() => {
172+
window.location.reload();
173+
}, 2000);
174+
return;
175+
}
176+
164177
// For other errors, don't throw to prevent app crash
165178
// The user can retry by reconnecting their wallet
166179
}

src/components/common/overall-layout/mobile-wrappers/wallet-data-loader-wrapper.tsx

Lines changed: 12 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default function WalletDataLoaderWrapper({
4848
const setWalletAssetMetadata = useWalletsStore(
4949
(state) => state.setWalletAssetMetadata,
5050
);
51-
const { fetchProxyBalance, fetchProxyDrepInfo, fetchProxyDelegatorsInfo, setProxies } = useProxyActions();
51+
const { fetchAllProxyData, setProxies } = useProxyActions();
5252

5353
const setDrepInfo = useWalletsStore((state) => state.setDrepInfo);
5454

@@ -189,49 +189,20 @@ export default function WalletDataLoaderWrapper({
189189
walletId: appWallet.id,
190190
});
191191

192-
193192
// First, add proxies to the store
194193
setProxies(appWallet.id, proxies);
195194

196-
// Fetch balance and DRep info for each proxy
197-
for (const proxy of proxies) {
198-
try {
199-
200-
// Fetch balance
201-
await fetchProxyBalance(
202-
appWallet.id,
203-
proxy.id,
204-
proxy.proxyAddress,
205-
network.toString()
206-
);
207-
208-
// Fetch DRep info with force refresh
209-
await fetchProxyDrepInfo(
210-
appWallet.id,
211-
proxy.id,
212-
proxy.proxyAddress,
213-
proxy.authTokenId,
214-
appWallet.scriptCbor,
215-
network.toString(),
216-
proxy.paramUtxo,
217-
true // Force refresh to bypass cache
218-
);
219-
220-
// Fetch delegators info with force refresh
221-
await fetchProxyDelegatorsInfo(
222-
appWallet.id,
223-
proxy.id,
224-
proxy.proxyAddress,
225-
proxy.authTokenId,
226-
appWallet.scriptCbor,
227-
network.toString(),
228-
proxy.paramUtxo,
229-
true // Force refresh to bypass cache
230-
);
231-
232-
} catch (error) {
233-
console.error(`WalletDataLoaderWrapper: Error fetching data for proxy ${proxy.id}:`, error);
234-
}
195+
// Fetch all proxy data in parallel using the new batch function
196+
if (proxies.length > 0) {
197+
console.log(`WalletDataLoaderWrapper: Fetching data for ${proxies.length} proxies in parallel`);
198+
await fetchAllProxyData(
199+
appWallet.id,
200+
proxies,
201+
appWallet.scriptCbor,
202+
network.toString(),
203+
false // Use cache to avoid duplicate requests
204+
);
205+
console.log("WalletDataLoaderWrapper: Successfully fetched all proxy data");
235206
}
236207
} catch (error) {
237208
console.error("WalletDataLoaderWrapper: Error fetching proxy data:", error);
@@ -267,9 +238,6 @@ export default function WalletDataLoaderWrapper({
267238
if (appWallet && prevWalletIdRef.current !== appWallet.id) {
268239
refreshWallet();
269240
prevWalletIdRef.current = appWallet.id;
270-
} else if (appWallet) {
271-
// If wallet exists but we already have data, still fetch proxy data
272-
fetchProxyData();
273241
}
274242
}, [appWallet]);
275243

src/components/common/overall-layout/wallet-data-loader.tsx

Lines changed: 12 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export default function WalletDataLoader() {
2020
const ctx = api.useUtils();
2121
const network = useSiteStore((state) => state.network);
2222
const setRandomState = useSiteStore((state) => state.setRandomState);
23-
const { fetchProxyBalance, fetchProxyDrepInfo, fetchProxyDelegatorsInfo, setProxies } = useProxyActions();
23+
const { fetchAllProxyData, setProxies } = useProxyActions();
2424

2525
async function fetchUtxos() {
2626
if (appWallet) {
@@ -70,47 +70,17 @@ export default function WalletDataLoader() {
7070
// First, add proxies to the store
7171
setProxies(appWallet.id, proxies);
7272

73-
// Fetch balance and DRep info for each proxy
74-
for (const proxy of proxies) {
75-
try {
76-
console.log(`WalletDataLoader: Fetching data for proxy ${proxy.id}`);
77-
78-
// Fetch balance
79-
await fetchProxyBalance(
80-
appWallet.id,
81-
proxy.id,
82-
proxy.proxyAddress,
83-
network.toString()
84-
);
85-
86-
// Fetch DRep info with force refresh
87-
await fetchProxyDrepInfo(
88-
appWallet.id,
89-
proxy.id,
90-
proxy.proxyAddress,
91-
proxy.authTokenId,
92-
appWallet.scriptCbor,
93-
network.toString(),
94-
proxy.paramUtxo,
95-
true // Force refresh to bypass cache
96-
);
97-
98-
// Fetch delegators info with force refresh
99-
await fetchProxyDelegatorsInfo(
100-
appWallet.id,
101-
proxy.id,
102-
proxy.proxyAddress,
103-
proxy.authTokenId,
104-
appWallet.scriptCbor,
105-
network.toString(),
106-
proxy.paramUtxo,
107-
true // Force refresh to bypass cache
108-
);
109-
110-
console.log(`WalletDataLoader: Successfully fetched data for proxy ${proxy.id}`);
111-
} catch (error) {
112-
console.error(`WalletDataLoader: Error fetching data for proxy ${proxy.id}:`, error);
113-
}
73+
// Fetch all proxy data in parallel using the new batch function
74+
if (proxies.length > 0) {
75+
console.log(`WalletDataLoader: Fetching data for ${proxies.length} proxies in parallel`);
76+
await fetchAllProxyData(
77+
appWallet.id,
78+
proxies,
79+
appWallet.scriptCbor,
80+
network.toString(),
81+
false // Use cache to avoid duplicate requests
82+
);
83+
console.log("WalletDataLoader: Successfully fetched all proxy data");
11484
}
11585
} catch (error) {
11686
console.error("WalletDataLoader: Error fetching proxy data:", error);
@@ -144,10 +114,6 @@ export default function WalletDataLoader() {
144114
if (appWallet && walletsUtxos[appWallet?.id] === undefined) {
145115
console.log("WalletDataLoader: Calling refreshWallet");
146116
refreshWallet();
147-
} else if (appWallet) {
148-
// If wallet exists but we already have UTxOs, still fetch proxy data
149-
console.log("WalletDataLoader: Calling fetchProxyData directly");
150-
fetchProxyData();
151117
}
152118
}, [appWallet]);
153119

src/components/multisig/proxy/ProxyControl.tsx

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -402,31 +402,12 @@ export default function ProxyControl() {
402402
}
403403
}, [network, wallet, appWallet?.scriptCbor]);
404404

405-
// Fetch all proxy balances for TVL calculation
405+
// Fetch all proxy balances for TVL calculation (now handled globally)
406406
const fetchAllProxyBalances = useCallback(async () => {
407-
if (!proxies || proxies.length === 0 || !proxyContract) return;
408-
409-
try {
410-
setTvlLoading(true);
411-
const balances: Record<string, Array<{ unit: string; quantity: string }>> = {};
412-
413-
for (const proxy of proxies) {
414-
try {
415-
const balance = await getProxyBalance(proxy.proxyAddress);
416-
balances[proxy.id] = balance;
417-
} catch (error) {
418-
console.error(`Failed to fetch balance for proxy ${proxy.id}:`, error);
419-
balances[proxy.id] = [];
420-
}
421-
}
422-
423-
// Balances handled elsewhere
424-
} catch (error) {
425-
console.error("Failed to fetch proxy balances:", error);
426-
} finally {
427-
setTvlLoading(false);
428-
}
429-
}, [proxies, proxyContract, getProxyBalance]);
407+
// This function is now handled globally by WalletDataLoaderWrapper
408+
// to avoid duplicate API calls. The proxy store already contains the balance data.
409+
console.log("ProxyControl: fetchAllProxyBalances called but data is handled globally");
410+
}, []);
430411

431412
// Calculate Total Value Locked (TVL) across all proxies
432413
const calculateTVL = useCallback(() => {
@@ -456,37 +437,20 @@ export default function ProxyControl() {
456437

457438
const { totalADA, totalAssets } = calculateTVL();
458439

459-
// Fetch all proxy balances when proxies change
460-
useEffect(() => {
461-
if (proxies && proxies.length > 0 && proxyContract) {
462-
void fetchAllProxyBalances();
463-
}
464-
}, [proxies, proxyContract, fetchAllProxyBalances]);
465-
466-
// Refresh balances when component mounts or wallet changes
467-
useEffect(() => {
468-
if (proxies && proxies.length > 0 && proxyContract && connected) {
469-
// Small delay to ensure everything is initialized
470-
const timer = setTimeout(() => {
471-
void fetchAllProxyBalances();
472-
}, 1000);
473-
return () => clearTimeout(timer);
474-
}
475-
}, [connected, proxyContract, fetchAllProxyBalances, proxies]);
440+
// Proxy balance data is now handled globally by WalletDataLoaderWrapper
441+
// No need to fetch balances here as they're already in the proxy store
476442

477443
// Manual TVL refresh function
478444
const refreshTVL = useCallback(async () => {
479-
if (proxies && proxies.length > 0 && proxyContract) {
480-
await fetchAllProxyBalances();
481-
}
482-
}, [proxies, proxyContract, fetchAllProxyBalances]);
445+
// TVL is calculated from proxy store data, no need to fetch
446+
console.log("ProxyControl: refreshTVL called - data comes from proxy store");
447+
}, []);
483448

484-
// Global refresh function for all proxy balances (unused but kept for potential future use)
449+
// Global refresh function for all proxy balances (now handled globally)
485450
const refreshAllBalances = useCallback(async () => {
486-
if (proxies && proxies.length > 0 && proxyContract) {
487-
void fetchAllProxyBalances();
488-
}
489-
}, [proxies, proxyContract, fetchAllProxyBalances]);
451+
// Balance data is now handled globally by WalletDataLoaderWrapper
452+
console.log("ProxyControl: refreshAllBalances called - data handled globally");
453+
}, []);
490454

491455
// Spend outputs management
492456
const handleSpendOutputsChange = useCallback((outputs: ProxyOutput[]) => {

src/lib/zustand/proxy.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ interface ProxyState {
7474
fetchProxyBalance: (walletId: string, proxyId: string, proxyAddress: string, network: string) => Promise<void>;
7575
fetchProxyDrepInfo: (walletId: string, proxyId: string, proxyAddress: string, authTokenId: string, scriptCbor: string, network: string, paramUtxo: string, forceRefresh?: boolean) => Promise<void>;
7676
fetchProxyDelegatorsInfo: (walletId: string, proxyId: string, proxyAddress: string, authTokenId: string, scriptCbor: string, network: string, paramUtxo: string, forceRefresh?: boolean) => Promise<void>;
77+
fetchAllProxyData: (walletId: string, proxies: ProxyData[], scriptCbor: string, network: string, forceRefresh?: boolean) => Promise<void>;
7778

7879
// Utility actions
7980
updateProxyData: (walletId: string, proxyId: string, updates: Partial<ProxyData>) => void;
@@ -249,6 +250,106 @@ export const useProxyStore = create<ProxyState>()(
249250
get().setDrepLoading(proxyId, false);
250251
}
251252
},
253+
254+
// Fetch all proxy data in parallel
255+
fetchAllProxyData: async (walletId, proxies, scriptCbor, network, forceRefresh = false) => {
256+
try {
257+
// Prevent multiple simultaneous fetches for the same wallet
258+
const currentState = get();
259+
if (currentState.loading[walletId]) {
260+
console.log(`Proxy data already loading for wallet ${walletId}, skipping...`);
261+
return;
262+
}
263+
264+
// Check if data is fresh (less than 30 seconds old) and skip if not forcing refresh
265+
if (!forceRefresh && currentState.proxies[walletId]) {
266+
const oldestUpdate = Math.min(
267+
...currentState.proxies[walletId].map(p => p.lastUpdated || 0)
268+
);
269+
const isDataFresh = oldestUpdate > 0 && (Date.now() - oldestUpdate) < 30000; // 30 seconds
270+
if (isDataFresh) {
271+
console.log(`Proxy data is fresh (less than 30s old) for wallet ${walletId}, skipping...`);
272+
return;
273+
}
274+
}
275+
276+
get().setLoading(walletId, true);
277+
get().setError(walletId, null);
278+
279+
// Create a single txBuilder instance to reuse across all proxies
280+
const txBuilder = getTxBuilder(parseInt(network));
281+
282+
// Create all fetch promises in parallel
283+
const fetchPromises = proxies.map(async (proxy) => {
284+
try {
285+
// Set loading state for this proxy
286+
get().setDrepLoading(proxy.id, true);
287+
get().setDrepError(proxy.id, null);
288+
289+
// Reuse the same txBuilder instance for all proxies
290+
const proxyContract = new MeshProxyContract(
291+
{
292+
mesh: txBuilder,
293+
wallet: undefined,
294+
networkId: parseInt(network),
295+
},
296+
{
297+
paramUtxo: JSON.parse(proxy.paramUtxo || '{}'),
298+
},
299+
scriptCbor,
300+
);
301+
proxyContract.proxyAddress = proxy.proxyAddress;
302+
303+
// Fetch all data for this proxy in parallel
304+
const [balance, drepStatus, delegators] = await Promise.allSettled([
305+
proxyContract.getProxyBalance(),
306+
proxyContract.getDrepStatus(forceRefresh),
307+
proxyContract.getDrepDelegators(forceRefresh),
308+
]);
309+
310+
// Get DRep ID
311+
const drepId = proxyContract.getDrepId();
312+
313+
// Process results
314+
const drepInfo: ProxyDrepInfo | undefined = drepStatus.status === 'fulfilled' ? drepStatus.value : undefined;
315+
const delegatorsInfo: ProxyDelegatorsInfo | undefined = delegators.status === 'fulfilled' ? (delegators.value as ProxyDelegatorsInfo) : undefined;
316+
317+
// Update the specific proxy's data
318+
const currentState = get();
319+
const updatedProxies = currentState.proxies[walletId]?.map(p =>
320+
p.id === proxy.id
321+
? {
322+
...p,
323+
balance: balance.status === 'fulfilled' ? balance.value : p.balance,
324+
drepId,
325+
drepInfo,
326+
delegatorsInfo,
327+
lastUpdated: Date.now()
328+
}
329+
: p
330+
) || [];
331+
332+
set((state) => ({
333+
proxies: { ...state.proxies, [walletId]: updatedProxies },
334+
drepLoading: { ...state.drepLoading, [proxy.id]: false },
335+
drepErrors: { ...state.drepErrors, [proxy.id]: null },
336+
}));
337+
338+
} catch (error) {
339+
get().setDrepError(proxy.id, `Failed to fetch data for proxy ${proxy.id}`);
340+
get().setDrepLoading(proxy.id, false);
341+
}
342+
});
343+
344+
// Wait for all proxy data to be fetched
345+
await Promise.allSettled(fetchPromises);
346+
347+
get().setLoading(walletId, false);
348+
} catch (error) {
349+
get().setError(walletId, `Failed to fetch proxy data: ${error instanceof Error ? error.message : 'Unknown error'}`);
350+
get().setLoading(walletId, false);
351+
}
352+
},
252353

253354
// Update specific proxy data
254355
updateProxyData: (walletId, proxyId, updates) =>
@@ -324,6 +425,7 @@ export const useProxyActions = () => {
324425
const fetchProxyBalance = useProxyStore((state) => state.fetchProxyBalance);
325426
const fetchProxyDrepInfo = useProxyStore((state) => state.fetchProxyDrepInfo);
326427
const fetchProxyDelegatorsInfo = useProxyStore((state) => state.fetchProxyDelegatorsInfo);
428+
const fetchAllProxyData = useProxyStore((state) => state.fetchAllProxyData);
327429
const updateProxyData = useProxyStore((state) => state.updateProxyData);
328430
const clearProxyData = useProxyStore((state) => state.clearProxyData);
329431

@@ -336,6 +438,7 @@ export const useProxyActions = () => {
336438
fetchProxyBalance,
337439
fetchProxyDrepInfo,
338440
fetchProxyDelegatorsInfo,
441+
fetchAllProxyData,
339442
updateProxyData,
340443
clearProxyData,
341444
};

0 commit comments

Comments
 (0)