From 5e0c273149e5d511b74071c2eb368d8b9d66fea6 Mon Sep 17 00:00:00 2001 From: Alejo Amiras Date: Mon, 18 May 2026 14:55:42 -0300 Subject: [PATCH] fix: wallet init race lets external connect win before node is set Co-Authored-By: Claude Opus 4.7 (1M context) --- src/contexts/wallet/WalletContext.tsx | 32 +++++++++++++++++++++++---- src/contexts/wallet/reducer.ts | 7 ++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/contexts/wallet/WalletContext.tsx b/src/contexts/wallet/WalletContext.tsx index 752a773..00891b4 100644 --- a/src/contexts/wallet/WalletContext.tsx +++ b/src/contexts/wallet/WalletContext.tsx @@ -58,6 +58,9 @@ export function WalletProvider({ children }: WalletProviderProps) { const embeddedAddressRef = useRef(null); const previousNodeUrlRef = useRef(null); const hasConnectedExternalWalletRef = useRef(false); + // Generation counter. A stale createEmbeddedWallet resolving after a network switch + // checks this to skip overwriting current state. + const initIdRef = useRef(0); // Provider tracking for disconnect handling const currentProviderRef = useRef(null); @@ -80,25 +83,43 @@ export function WalletProvider({ children }: WalletProviderProps) { previousNodeUrlRef.current = nodeUrl; hasConnectedExternalWalletRef.current = false; + // Drop the previous network's embedded wallet so it can't be restored on the new network. + embeddedWalletRef.current = null; + embeddedAddressRef.current = null; + const initId = ++initIdRef.current; async function initializeWallet() { try { actions.initStart(); + // Node is a property of the network, not the wallet. Publish it immediately so + // external-wallet flows don't have to wait on the slow embedded init. const node = walletService.createNodeClient(nodeUrl); + actions.setNode(node); + const { wallet: embeddedWallet, address: defaultAccountAddress } = await walletService.createEmbeddedWallet(node); - // Store embedded wallet for later restoration + // Bail if a newer init has superseded this one (e.g. user switched network mid-init). + if (initId !== initIdRef.current) return; + embeddedWalletRef.current = embeddedWallet; embeddedAddressRef.current = defaultAccountAddress; - // Only set embedded wallet as active if user hasn't connected an external wallet if (!hasConnectedExternalWalletRef.current) { actions.initEmbedded(embeddedWallet, node, defaultAccountAddress); } } catch (err) { + if (initId !== initIdRef.current) return; + const errorMessage = err instanceof Error ? err.message : 'Unknown error occurred'; + // External wallet already connected. Embedded init failures here are background + // noise; don't clobber the working session with a fatal error. + if (hasConnectedExternalWalletRef.current) { + console.warn('Embedded wallet init failed (external wallet active):', errorMessage); + return; + } + const fullError = errorMessage.includes('timeout') || errorMessage.includes('unreachable') ? `${errorMessage}\n\nIf using local network, make sure Aztec sandbox is running:\n aztec start --sandbox\n\nThen deploy contracts:\n yarn deploy:local` @@ -238,10 +259,13 @@ export function WalletProvider({ children }: WalletProviderProps) { } currentProviderRef.current = null; - // Restore embedded wallet + // Restore embedded wallet if available; otherwise clear state so the UI doesn't + // keep showing the disconnected external wallet as active. + hasConnectedExternalWalletRef.current = false; if (embeddedWalletRef.current) { - hasConnectedExternalWalletRef.current = false; actions.restoreEmbedded(embeddedWalletRef.current, embeddedAddressRef.current); + } else { + actions.disconnect(); } }, [actions]); diff --git a/src/contexts/wallet/reducer.ts b/src/contexts/wallet/reducer.ts index 91c8070..03bba8e 100644 --- a/src/contexts/wallet/reducer.ts +++ b/src/contexts/wallet/reducer.ts @@ -36,6 +36,7 @@ export const initialWalletState: WalletState = { export const walletActions = { initStart: () => ({ type: 'wallet/INIT_START' as const }), + setNode: (node: AztecNode) => ({ type: 'wallet/SET_NODE' as const, node }), initEmbedded: (wallet: Wallet, node: AztecNode, address: AztecAddress) => ({ type: 'wallet/INIT_EMBEDDED' as const, wallet, @@ -64,6 +65,9 @@ export function walletReducer(state: WalletState, action: WalletAction): WalletS case 'wallet/INIT_START': return { ...state, isLoading: true, error: null }; + case 'wallet/SET_NODE': + return { ...state, node: action.node }; + case 'wallet/INIT_EMBEDDED': return { ...state, @@ -81,6 +85,8 @@ export function walletReducer(state: WalletState, action: WalletAction): WalletS wallet: action.wallet, currentAddress: null, isUsingEmbeddedWallet: false, + isLoading: false, + error: null, }; case 'wallet/SET_ADDRESS': @@ -100,6 +106,7 @@ export function walletReducer(state: WalletState, action: WalletAction): WalletS wallet: action.wallet, currentAddress: action.address, isUsingEmbeddedWallet: true, + isLoading: false, }; case 'wallet/SET_ERROR':