From 7edd51579237c3aa372c935b1ffcd0af1c76b420 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Fri, 30 Jan 2026 09:08:44 +0000 Subject: [PATCH 1/6] . --- src/Web3AccountContext.tsx | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/Web3AccountContext.tsx b/src/Web3AccountContext.tsx index 8d1ce60..e01ca36 100644 --- a/src/Web3AccountContext.tsx +++ b/src/Web3AccountContext.tsx @@ -31,6 +31,7 @@ type Web3AccountControl = { web3ChainId: number | undefined | null; web3Account: Web3Account | undefined | null; web3LoginSignature: Web3LoginSignature | undefined | null; + isRestoringSession: boolean; providers: Eip6963ProviderDetail[]; chooseEip1193Provider: (eip1193ProviderRdns: string) => void; onLinkWeb3AccountsClicked: () => Promise; @@ -88,11 +89,22 @@ interface IAppKitSyncProps { setEip1193Provider: (provider: Eip1193Provider | null) => void; setWeb3Account: (account: Web3Account | null) => void; setWeb3ChainId: (chainId: number) => void; + setIsReownSessionRestored: (isRestored: boolean) => void; } function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { const appKitAccount = useAppKitAccount(); const appKitProviderResult = useAppKitProvider('eip155'); + + // Mark Reown session as restored once AppKit has determined connection status + // appKitAccount.isConnected will be false if no session, or true if session restored + React.useEffect((): void => { + // appKitAccount is undefined while loading, then becomes an object with isConnected + if (appKitAccount !== undefined && appKitAccount !== null) { + props.setIsReownSessionRestored(true); + } + }, [appKitAccount, props]); + React.useEffect((): void => { if (!appKitAccount?.isConnected || !appKitAccount?.address || !appKitProviderResult?.walletProvider) { return; @@ -120,6 +132,14 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro const [loginCount, setLoginCount] = React.useState(0); const [providers, setProviders] = React.useState(undefined); const [isWaitingToLinkAccount, setIsWaitingToLinkAccount] = React.useState(false); + const [isReownSessionRestored, setIsReownSessionRestored] = React.useState(false); + // Check if there's a saved provider that might be restoring (used to prevent flash of logged-out state) + const savedProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); + // For Reown, we need to wait until AppKitSync has determined if there's a session + const isReownRestoring = savedProviderRdns === 'reown' && !isReownSessionRestored && web3Account === undefined; + // For other providers, check if we have a saved provider but no account yet + const isOtherProviderRestoring = savedProviderRdns != null && savedProviderRdns !== 'reown' && web3Account === undefined; + const isRestoringSession = isReownRestoring || isOtherProviderRestoring; const providersRef = React.useRef(undefined); providersRef.current = providers; const onError = props.onError; @@ -496,8 +516,8 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro }, [web3Account, web3ChainId, props.localStorageClient, loginCount]); const providerValue = React.useMemo((): Web3AccountControl => { - return { web3Account, web3LoginSignature, providers: providers ?? [], onLinkWeb3AccountsClicked, onWeb3LoginClicked, onWeb3BaseLoginClicked, chooseEip1193Provider, onSwitchToChainIdClicked, web3, web3ChainId }; - }, [web3Account, web3LoginSignature, providers, chooseEip1193Provider, onLinkWeb3AccountsClicked, onWeb3LoginClicked, onWeb3BaseLoginClicked, onSwitchToChainIdClicked, web3, web3ChainId]); + return { web3Account, web3LoginSignature, isRestoringSession, providers: providers ?? [], onLinkWeb3AccountsClicked, onWeb3LoginClicked, onWeb3BaseLoginClicked, chooseEip1193Provider, onSwitchToChainIdClicked, web3, web3ChainId }; + }, [web3Account, web3LoginSignature, isRestoringSession, providers, chooseEip1193Provider, onLinkWeb3AccountsClicked, onWeb3LoginClicked, onWeb3BaseLoginClicked, onSwitchToChainIdClicked, web3, web3ChainId]); return ( @@ -507,6 +527,7 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro setEip1193Provider={setEip1193Provider} setWeb3Account={setWeb3Account} setWeb3ChainId={setWeb3ChainId} + setIsReownSessionRestored={setIsReownSessionRestored} /> )} {props.children} @@ -566,6 +587,14 @@ export const useIsReownInitialized = (): boolean => { return isReownInitializedGlobally; }; +export const useIsRestoringSession = (): boolean => { + const web3AccountsControl = React.useContext(Web3AccountContext); + if (!web3AccountsControl) { + throw Error('web3AccountsControl has not been initialized correctly.'); + } + return web3AccountsControl.isRestoringSession; +}; + export const useWeb3LoginSignature = (): Web3LoginSignature | undefined | null => { const web3AccountsControl = React.useContext(Web3AccountContext); if (!web3AccountsControl) { From 9bf925ff1b18125902f8a5ec737639e972ea3858 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Fri, 30 Jan 2026 09:15:49 +0000 Subject: [PATCH 2/6] . --- src/Web3AccountContext.tsx | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Web3AccountContext.tsx b/src/Web3AccountContext.tsx index e01ca36..dc9cefa 100644 --- a/src/Web3AccountContext.tsx +++ b/src/Web3AccountContext.tsx @@ -89,19 +89,20 @@ interface IAppKitSyncProps { setEip1193Provider: (provider: Eip1193Provider | null) => void; setWeb3Account: (account: Web3Account | null) => void; setWeb3ChainId: (chainId: number) => void; - setIsReownSessionRestored: (isRestored: boolean) => void; } function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { const appKitAccount = useAppKitAccount(); const appKitProviderResult = useAppKitProvider('eip155'); - // Mark Reown session as restored once AppKit has determined connection status - // appKitAccount.isConnected will be false if no session, or true if session restored + // When Reown finishes loading and there's no session, clear the saved provider and set account to null React.useEffect((): void => { - // appKitAccount is undefined while loading, then becomes an object with isConnected - if (appKitAccount !== undefined && appKitAccount !== null) { - props.setIsReownSessionRestored(true); + if (appKitAccount !== undefined && appKitAccount !== null && !appKitAccount.isConnected) { + const savedRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); + if (savedRdns === 'reown') { + props.localStorageClient.removeValue('web3Account-chosenEip1193ProviderRdns'); + props.setWeb3Account(null); + } } }, [appKitAccount, props]); @@ -132,14 +133,9 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro const [loginCount, setLoginCount] = React.useState(0); const [providers, setProviders] = React.useState(undefined); const [isWaitingToLinkAccount, setIsWaitingToLinkAccount] = React.useState(false); - const [isReownSessionRestored, setIsReownSessionRestored] = React.useState(false); // Check if there's a saved provider that might be restoring (used to prevent flash of logged-out state) const savedProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); - // For Reown, we need to wait until AppKitSync has determined if there's a session - const isReownRestoring = savedProviderRdns === 'reown' && !isReownSessionRestored && web3Account === undefined; - // For other providers, check if we have a saved provider but no account yet - const isOtherProviderRestoring = savedProviderRdns != null && savedProviderRdns !== 'reown' && web3Account === undefined; - const isRestoringSession = isReownRestoring || isOtherProviderRestoring; + const isRestoringSession = savedProviderRdns != null && web3Account === undefined; const providersRef = React.useRef(undefined); providersRef.current = providers; const onError = props.onError; @@ -258,6 +254,10 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro return; } const savedProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); + // Reown/WalletConnect is handled by AppKitSync, not here + if (savedProviderRdns === 'reown') { + return; + } if (savedProviderRdns === 'base') { const baseReconnected = await autoReconnectBaseProvider(); if (!baseReconnected) { @@ -527,7 +527,6 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro setEip1193Provider={setEip1193Provider} setWeb3Account={setWeb3Account} setWeb3ChainId={setWeb3ChainId} - setIsReownSessionRestored={setIsReownSessionRestored} /> )} {props.children} From a8a754a3bde96e34a470e84e483672046578f066 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Fri, 30 Jan 2026 09:57:46 +0000 Subject: [PATCH 3/6] . --- src/Web3AccountContext.tsx | 79 ++++++++++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/src/Web3AccountContext.tsx b/src/Web3AccountContext.tsx index dc9cefa..43e2f4e 100644 --- a/src/Web3AccountContext.tsx +++ b/src/Web3AccountContext.tsx @@ -94,35 +94,93 @@ interface IAppKitSyncProps { function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { const appKitAccount = useAppKitAccount(); const appKitProviderResult = useAppKitProvider('eip155'); + const hasSetAccountRef = React.useRef(false); + const noSessionTimeoutRef = React.useRef(null); - // When Reown finishes loading and there's no session, clear the saved provider and set account to null - React.useEffect((): void => { - if (appKitAccount !== undefined && appKitAccount !== null && !appKitAccount.isConnected) { - const savedRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); - if (savedRdns === 'reown') { + console.log('[AppKitSync] render', { + appKitAccount: appKitAccount ? { isConnected: appKitAccount.isConnected, address: appKitAccount.address } : appKitAccount, + hasWalletProvider: !!appKitProviderResult?.walletProvider, + hasSetAccount: hasSetAccountRef.current, + }); + + // Handle the case when Reown has no session - use a timeout to avoid race conditions + // Reown initially reports isConnected=false before it finishes restoring the session + React.useEffect((): (() => void) => { + const savedRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); + + // Only set up the timeout if we're expecting a reown session and haven't already set up the account + if (savedRdns !== 'reown' || hasSetAccountRef.current) { + return () => {}; + } + + // If already connected, no need for timeout + if (appKitAccount?.isConnected) { + console.log('[AppKitSync] already connected, clearing any pending timeout'); + if (noSessionTimeoutRef.current) { + clearTimeout(noSessionTimeoutRef.current); + noSessionTimeoutRef.current = null; + } + return () => {}; + } + + // Set a timeout - if we don't get connected within this time, assume no session + console.log('[AppKitSync] setting no-session timeout'); + noSessionTimeoutRef.current = setTimeout(() => { + console.log('[AppKitSync] timeout fired, checking connection status'); + // Double-check we still haven't connected + if (!hasSetAccountRef.current) { + console.log('[AppKitSync] no session after timeout, clearing reown from localStorage'); props.localStorageClient.removeValue('web3Account-chosenEip1193ProviderRdns'); props.setWeb3Account(null); } - } - }, [appKitAccount, props]); + }, 3000); // 3 second timeout to allow Reown to restore session + + return () => { + if (noSessionTimeoutRef.current) { + clearTimeout(noSessionTimeoutRef.current); + noSessionTimeoutRef.current = null; + } + }; + }, [appKitAccount?.isConnected, props]); + // When Reown connects, sync the account React.useEffect((): void => { if (!appKitAccount?.isConnected || !appKitAccount?.address || !appKitProviderResult?.walletProvider) { return; } + + // Don't sync again if we've already set the account + if (hasSetAccountRef.current) { + return; + } + + console.log('[AppKitSync] connected with wallet provider, syncing account'); + + // Clear any pending no-session timeout + if (noSessionTimeoutRef.current) { + clearTimeout(noSessionTimeoutRef.current); + noSessionTimeoutRef.current = null; + } + + // Mark as set immediately to prevent duplicate calls + hasSetAccountRef.current = true; + const walletProvider = appKitProviderResult.walletProvider as Eip1193Provider; props.setEip1193Provider(walletProvider); props.localStorageClient.setValue('web3Account-chosenEip1193ProviderRdns', 'reown'); + const syncAccount = async (): Promise => { const browserProvider = new EthersBrowserProvider(walletProvider); const signer = await browserProvider.getSigner(); const address = await signer.getAddress(); + console.log('[AppKitSync] setting web3Account:', address); props.setWeb3Account({ address, signer }); const chainIdHex = await browserProvider.send('eth_chainId', []); props.setWeb3ChainId(Number(chainIdHex)); }; syncAccount(); }, [appKitAccount?.isConnected, appKitAccount?.address, appKitProviderResult?.walletProvider, props]); + return null; } @@ -136,6 +194,13 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro // Check if there's a saved provider that might be restoring (used to prevent flash of logged-out state) const savedProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); const isRestoringSession = savedProviderRdns != null && web3Account === undefined; + console.log('[Web3AccountControlProvider] render', { + savedProviderRdns, + web3Account: web3Account === undefined ? 'undefined' : web3Account === null ? 'null' : web3Account.address, + isRestoringSession, + eip1193Provider: eip1193Provider === undefined ? 'undefined' : eip1193Provider === null ? 'null' : 'set', + providers: providers?.length, + }); const providersRef = React.useRef(undefined); providersRef.current = providers; const onError = props.onError; From 43402e6497e5bc64cc39f07e3a669390f131a235 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Fri, 30 Jan 2026 10:13:03 +0000 Subject: [PATCH 4/6] . --- src/Web3AccountContext.tsx | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/src/Web3AccountContext.tsx b/src/Web3AccountContext.tsx index 43e2f4e..b30e962 100644 --- a/src/Web3AccountContext.tsx +++ b/src/Web3AccountContext.tsx @@ -95,13 +95,7 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { const appKitAccount = useAppKitAccount(); const appKitProviderResult = useAppKitProvider('eip155'); const hasSetAccountRef = React.useRef(false); - const noSessionTimeoutRef = React.useRef(null); - - console.log('[AppKitSync] render', { - appKitAccount: appKitAccount ? { isConnected: appKitAccount.isConnected, address: appKitAccount.address } : appKitAccount, - hasWalletProvider: !!appKitProviderResult?.walletProvider, - hasSetAccount: hasSetAccountRef.current, - }); + const noSessionTimeoutRef = React.useRef | null>(null); // Handle the case when Reown has no session - use a timeout to avoid race conditions // Reown initially reports isConnected=false before it finishes restoring the session @@ -115,7 +109,6 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { // If already connected, no need for timeout if (appKitAccount?.isConnected) { - console.log('[AppKitSync] already connected, clearing any pending timeout'); if (noSessionTimeoutRef.current) { clearTimeout(noSessionTimeoutRef.current); noSessionTimeoutRef.current = null; @@ -124,12 +117,9 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { } // Set a timeout - if we don't get connected within this time, assume no session - console.log('[AppKitSync] setting no-session timeout'); noSessionTimeoutRef.current = setTimeout(() => { - console.log('[AppKitSync] timeout fired, checking connection status'); // Double-check we still haven't connected if (!hasSetAccountRef.current) { - console.log('[AppKitSync] no session after timeout, clearing reown from localStorage'); props.localStorageClient.removeValue('web3Account-chosenEip1193ProviderRdns'); props.setWeb3Account(null); } @@ -154,8 +144,6 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { return; } - console.log('[AppKitSync] connected with wallet provider, syncing account'); - // Clear any pending no-session timeout if (noSessionTimeoutRef.current) { clearTimeout(noSessionTimeoutRef.current); @@ -173,7 +161,6 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { const browserProvider = new EthersBrowserProvider(walletProvider); const signer = await browserProvider.getSigner(); const address = await signer.getAddress(); - console.log('[AppKitSync] setting web3Account:', address); props.setWeb3Account({ address, signer }); const chainIdHex = await browserProvider.send('eth_chainId', []); props.setWeb3ChainId(Number(chainIdHex)); @@ -194,13 +181,6 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro // Check if there's a saved provider that might be restoring (used to prevent flash of logged-out state) const savedProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); const isRestoringSession = savedProviderRdns != null && web3Account === undefined; - console.log('[Web3AccountControlProvider] render', { - savedProviderRdns, - web3Account: web3Account === undefined ? 'undefined' : web3Account === null ? 'null' : web3Account.address, - isRestoringSession, - eip1193Provider: eip1193Provider === undefined ? 'undefined' : eip1193Provider === null ? 'null' : 'set', - providers: providers?.length, - }); const providersRef = React.useRef(undefined); providersRef.current = providers; const onError = props.onError; @@ -318,12 +298,12 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro if (eip1193Provider != null || providers === undefined) { return; } - const savedProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); + const savedRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); // Reown/WalletConnect is handled by AppKitSync, not here - if (savedProviderRdns === 'reown') { + if (savedRdns === 'reown') { return; } - if (savedProviderRdns === 'base') { + if (savedRdns === 'base') { const baseReconnected = await autoReconnectBaseProvider(); if (!baseReconnected) { setWeb3Account(null); @@ -331,8 +311,8 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro } return; } - if (savedProviderRdns && providers.length > 0) { - const savedProvider = providers.find((provider: Eip6963ProviderDetail): boolean => provider.info.rdns === savedProviderRdns); + if (savedRdns && providers.length > 0) { + const savedProvider = providers.find((provider: Eip6963ProviderDetail): boolean => provider.info.rdns === savedRdns); if (savedProvider) { setEip1193Provider(savedProvider.provider); } else { @@ -362,8 +342,8 @@ export function Web3AccountControlProvider(props: IWeb3AccountControlProviderPro if (linkedWeb3Accounts.length === 0) { setWeb3Account(null); // NOTE(krishan711): Clean up Base connection if it was a Base provider - const savedProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); - if (savedProviderRdns === 'base') { + const currentProviderRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); + if (currentProviderRdns === 'base') { props.localStorageClient.removeValue('web3Account-baseConnection'); props.localStorageClient.removeValue('web3Account-chosenEip1193ProviderRdns'); } From 9cb202e6aeb9a2dc29cf257c691c741532e65405 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Fri, 30 Jan 2026 10:13:50 +0000 Subject: [PATCH 5/6] . --- src/Web3AccountContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web3AccountContext.tsx b/src/Web3AccountContext.tsx index b30e962..57177ec 100644 --- a/src/Web3AccountContext.tsx +++ b/src/Web3AccountContext.tsx @@ -631,7 +631,7 @@ export const useIsReownInitialized = (): boolean => { return isReownInitializedGlobally; }; -export const useIsRestoringSession = (): boolean => { +export const useIsRestoringWeb3Session = (): boolean => { const web3AccountsControl = React.useContext(Web3AccountContext); if (!web3AccountsControl) { throw Error('web3AccountsControl has not been initialized correctly.'); From 5649d04390a172d695e0b287219d69b78d087290 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Fri, 30 Jan 2026 10:15:24 +0000 Subject: [PATCH 6/6] . --- src/Web3AccountContext.tsx | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/src/Web3AccountContext.tsx b/src/Web3AccountContext.tsx index 57177ec..fbcb0c6 100644 --- a/src/Web3AccountContext.tsx +++ b/src/Web3AccountContext.tsx @@ -101,21 +101,18 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { // Reown initially reports isConnected=false before it finishes restoring the session React.useEffect((): (() => void) => { const savedRdns = props.localStorageClient.getValue('web3Account-chosenEip1193ProviderRdns'); - // Only set up the timeout if we're expecting a reown session and haven't already set up the account if (savedRdns !== 'reown' || hasSetAccountRef.current) { - return () => {}; + return (): void => {}; } - // If already connected, no need for timeout if (appKitAccount?.isConnected) { if (noSessionTimeoutRef.current) { clearTimeout(noSessionTimeoutRef.current); noSessionTimeoutRef.current = null; } - return () => {}; + return (): void => {}; } - // Set a timeout - if we don't get connected within this time, assume no session noSessionTimeoutRef.current = setTimeout(() => { // Double-check we still haven't connected @@ -124,8 +121,7 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { props.setWeb3Account(null); } }, 3000); // 3 second timeout to allow Reown to restore session - - return () => { + return (): void => { if (noSessionTimeoutRef.current) { clearTimeout(noSessionTimeoutRef.current); noSessionTimeoutRef.current = null; @@ -133,30 +129,22 @@ function AppKitSync(props: IAppKitSyncProps): React.ReactElement | null { }; }, [appKitAccount?.isConnected, props]); - // When Reown connects, sync the account React.useEffect((): void => { if (!appKitAccount?.isConnected || !appKitAccount?.address || !appKitProviderResult?.walletProvider) { return; } - - // Don't sync again if we've already set the account if (hasSetAccountRef.current) { return; } - - // Clear any pending no-session timeout if (noSessionTimeoutRef.current) { clearTimeout(noSessionTimeoutRef.current); noSessionTimeoutRef.current = null; } - // Mark as set immediately to prevent duplicate calls hasSetAccountRef.current = true; - const walletProvider = appKitProviderResult.walletProvider as Eip1193Provider; props.setEip1193Provider(walletProvider); props.localStorageClient.setValue('web3Account-chosenEip1193ProviderRdns', 'reown'); - const syncAccount = async (): Promise => { const browserProvider = new EthersBrowserProvider(walletProvider); const signer = await browserProvider.getSigner();