diff --git a/packages/chain-adapters/src/utils/ledgerAppGate.ts b/packages/chain-adapters/src/utils/ledgerAppGate.ts index 66b93c5ab01..7793a925864 100644 --- a/packages/chain-adapters/src/utils/ledgerAppGate.ts +++ b/packages/chain-adapters/src/utils/ledgerAppGate.ts @@ -102,17 +102,23 @@ export const verifyLedgerAppOpen = async (chainId: ChainId | KnownChainIds, wall const args: LedgerOpenAppEventArgs = { chainId, reject } emitter.emit('LedgerOpenApp', args) - // prompt user to open app on device - wallet.openApp(appName) - - intervalId = setInterval(async () => { - if (!(await isAppOpen())) return - - // emit event to trigger modal close - emitter.emit('LedgerAppOpened') - clearInterval(intervalId) - resolve() - }, 1000) + // start polling for app open status after openApp completes to avoid concurrent USB requests + const startPolling = () => { + intervalId = setInterval(async () => { + if (!(await isAppOpen())) return + + // emit event to trigger modal close + emitter.emit('LedgerAppOpened') + clearInterval(intervalId) + resolve() + }, 1000) + } + + // prompt user to open app on device, then start polling + // Promise.resolve normalizes both promise and non-promise return values + Promise.resolve(wallet.openApp(appName)) + .then(() => startPolling()) + .catch(() => startPolling()) }) } catch { clearInterval(intervalId) diff --git a/src/context/AppProvider/AppContext.tsx b/src/context/AppProvider/AppContext.tsx index eb8949ec6da..38d94da03d2 100644 --- a/src/context/AppProvider/AppContext.tsx +++ b/src/context/AppProvider/AppContext.tsx @@ -99,7 +99,12 @@ export const AppProvider = ({ children }: { children: React.ReactNode }) => { emitter.off('LedgerOpenApp', handleLedgerOpenApp) emitter.off('LedgerAppOpened', handleLedgerAppOpened) } - }) + // Empty deps array intentional - event listeners are global via EventEmitter. + // They should register once on AppProvider mount and remain stable throughout app lifecycle. + // The handlers capture closeModal/openModal but these are stable functions from ModalProvider. + // Re-registering listeners on every render was causing the Ledger app gate flakiness (issue #11492). + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) // track anonymous portfolio useMixpanelPortfolioTracking()