Skip to content

Widget, dapp+Safe: app says 'connect wallet' when it is connected #7777

Open

Description

@elena-zh

Update 2026-10-20: not what Lido hit. Their disconnect is the ConnectorAlreadyConnectedError race fixed by #8002 (their pin, v3.24.0, predates it; see FE-451). This retry gap is still real but only reachable when the host provider fails or returns no accounts on the first request, so it's moved to Backlog as hardening.


Follow-up to FE-451 (Lido, high: "sometimes, when the ETH Widget reloads, the wallet disconnects inside the embedded CowSwap widget").

Status of the original race

  • The ConnectorAlreadyConnectedError race from Lido's gist was fixed in fix(wallet): swallow ConnectorAlreadyConnectedError in widget dapp-mode connect race (first shipped in v3.26.0).
  • But stake.lido.fi loads the widget from https://cow-ipfs.lido.fi (cowswapWidgetBaseUrl in their /runtime/window-env.js), which serves v3.24.0. They don't have the fix.
  • On v3.24.0 the error does show up, but the widget still ends up connected. Corrected 2026-10-20: with Lido's mount pattern (no provider → provider a few ms later), their v3.24.0 pin ended disconnected in 12 of 33 automated attempts; prod v3.31.1 in 0 of 11. Details in FE-451.

Remaining gap

If the host provider's first eth_accounts / eth_requestAccounts fails or comes back empty, and no accountsChanged follows, the widget stays disconnected for good.

  • WidgetStandaloneModeUpdater (libs/wallet/src/updaters/WidgetStandaloneMode.updater.tsx) connects exactly once per isDappMode change and only logs on failure. wagmi's reconnect gives up too.
  • Reproduced on v3.24.0 and on prod: the host wallet was ready at ~4.6s, and the widget never asked again (2 requests in total). A later accountsChanged does recover it.

Lido's provider makes this reachable:

  • its synthetic connect event fires via setTimeout(0) when our bridge subscribes. That happens in createCowSwapWidget, before the iframe has loaded, so the event is lost;
  • accountsChanged only fires on a wagmi change;
  • their request wrapper uses retryCount: 0.

Fix idea

  • In dapp mode, retry the widget connector with a bounded backoff when connect fails or returns no accounts.
  • Have IframeRpcProviderBridge re-send the current accounts/chainId once the iframe posts ACTIVATE / READY, so events emitted before load aren't lost.
  • Treat a connect with zero accounts as not connected (see the "Signer is missing account" crash issue).

For Lido

Update the cow-ipfs.lido.fi pin to current prod. Mount the widget only once the provider exists and keep it stable. Emit accountsChanged when the wallet becomes ready.

Related: GitHub #7777 (open), where dapp+Safe shows "connect wallet" while connected. A comment there notes that useIsRestoringConnection returns false in dapp mode.

Activity

  1. added
    BugSomething isn't working
    LowSeverity indicator for defects. It won't cause any major break-down of the system
    on Jun 30, 2026
  2. changed the title [-]Widget, dapp mode: app says 'connect wallet', however, it is connected[/-] [+]Widget, dapp+Safe: app says 'connect wallet', however, it is connected[/+] on Jun 30, 2026
  3. changed the title [-]Widget, dapp+Safe: app says 'connect wallet', however, it is connected[/-] [+]Widget, dapp+Safe: app says 'connect wallet' when it is connected[/+] on Jun 30, 2026
  4. added theissue type on Jun 30, 2026
  5. tenderdeve commented on Jul 1, 2026

    @tenderdeve
    Contributor

    @elena-zh I'd like to work on this.

  6. tenderdeve commented on Jul 1, 2026

    @tenderdeve
    Contributor

    @elena-zh looked into this. The disabled 'Connect Wallet' comes from validateTradeForm pushing WalletNotConnected whenever account is empty; in dapp mode useIsRestoringConnection returns false, so there's no 'restoring' grace state and an unpopulated widget-connector account shows immediately as a hard Connect Wallet.

    Two candidate causes, and I'd like your read before patching wallet-connector internals:

    1. config.ts changed enableReconnect: true → enableReconnect: hasRecentConnector (in fix(wallet): guard mobile injected provider #7757, touched again in fix(wallet): keep injected provider isolated #7766). A fresh widget iframe inside Safe has no recentConnectorId, so AppKit no longer reconnects on mount.
    2. Nested-iframe Safe detection: getIsSafeAppIframe() / getAncestorOrigin() only inspect ancestorOrigins[0] (the dapp), never the top-level app.safe.global, so the widget never knows it's ultimately inside Safe and the instant Safe auto-connect never runs.

    This one really needs the exact nested topology (Safe → dapp → widget) to repro and confirm which is the culprit — do you have a repro dapp/config, or can you confirm whether this regressed with #7757? I'd rather not blind-patch enableReconnect given the blast radius.

  7. elena-zh commented on Jul 1, 2026

    @elena-zh
    ContributorAuthor

    @tenderdeve , the issue was originally introduced in #7757

    In order to reproduce it, you'll need to create your own Safe account, and then open this link https://app.safe.global/apps/open?safe=**your_account**&appUrl=https%3A%2F%2Fdev.widget.cow.fi

  8. linear-code commented on Oct 16, 2026

    @linear-code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugSomething isn't workingLidoLowSeverity indicator for defects. It won't cause any major break-down of the systemWidget

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions