Skip to content

Wagmi connector: ghost-connected state after disconnect (isConnected=true, address=undefined) #1060

@TimDaub

Description

@TimDaub

Description

After disconnecting a Porto passkey wallet via wagmi's disconnect(), subsequent page reloads result in a "ghost-connected" state where:

  • useAccount() reports isConnected: true and status: "connected"
  • account.address is undefined
  • connect({ connector: portoConnector }) is a no-op — no passkey dialog appears, no error is thrown

Users get stuck in a state where the app thinks they're connected but they can't perform any wallet actions.

Steps to Reproduce

  1. Connect with Porto passkey connector via wagmi
  2. Call wagmi's disconnect() (e.g., user clicks logout)
  3. Reload the page
  4. useAccount() cycles through these states:
isConnected: true,  address: "0x68FF...", status: "reconnecting"
isConnected: false, address: undefined,   status: "reconnecting"
isConnected: true,  address: undefined,   status: "connected"     ← settles here
  1. Calling connect({ connector: portoConnector }) again does nothing — no dialog, no error

Expected Behavior

After disconnect() + page reload, either:

  • Successfully restore the session with a valid address, OR
  • Report isConnected: false so the app can show a clean login flow

What I See in Storage

After the ghost state occurs:

  • wagmi.recentConnectorId still set to "xyz.ithaca.porto" in localStorage
  • wagmi.store in localStorage has connections Map with empty value: []
  • Porto's IndexedDB (porto database, store object store) still exists with a porto.store entry containing {state: Object, version: 5}

Manually deleting the Porto IndexedDB + wagmi localStorage keys and reloading fixes it.

Possible Root Cause

Looking at Porto's source, I suspect a persistence timing issue:

  1. wallet_disconnect in provider.ts calls store.setState((x) => ({ ...x, accounts: [] })) — this updates memory, but the zustand persist write to IndexedDB is async. If the page unloads before the write completes, stale account data survives in IndexedDB.

  2. waitForHydration in store.ts has a setTimeout(() => resolve(true), 100) fallback. If IndexedDB hydration is slower than 100ms (common on mobile/cold start), the provider may initially read empty state, then hydration completes later with stale accounts, firing a late connect event that creates the ghost state after wagmi already decided "not authorized."

  3. The wagmi connector's connect() with isReconnecting: true returns { accounts: [], chainId } instead of failing when no accounts are found — wagmi stores this as a "connected" connection with zero accounts.

Workaround

We currently work around this by:

  1. Detecting ghost state (account.isConnected && !account.address) before calling connect(), and calling disconnectAsync() first
  2. Clearing Porto's IndexedDB (indexedDB.deleteDatabase('porto')) during logout
  3. Also clearing wagmi.store and wagmi.recentConnectorId from localStorage during logout

Environment

  • Porto connector via wagmi v2
  • Safari on macOS (Darwin 24.6.0)
  • Porto dialog mode with id.porto.sh

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions