Skip to content

Account spinner shows ~6.6s on load — ~3.9s is Privy smart wallet provisioning #554

Description

@yu23ki14

One line description

The account area in the app header shows a loading spinner for ~6.6 seconds after page load, and ~3.9 s of that comes from Privy's smart wallet setup — not from our own code.

Summary (for everyone)

When a logged-in user opens the app, the wallet slot in the top bar (desktop sidebar / mobile header) shows a spinner while things load. On a fresh reload this spinner stays up for about 6.6 seconds before the account name and avatar appear.

We measured where that time goes. The result:

What's happening Time Is it our problem?
Privy finishes starting up (hydration) ~1.4 s Privy SDK
Privy prepares the "smart wallet" so it can sign transactions ~3.9 s Privy SDK — the biggest chunk
We look up the user's display name (Namestone) ~0.24 s Ours — already fast, not a problem

Roughly 80% of the wait comes from Privy, and the single biggest piece (~3.9 s) is Privy provisioning the smart wallet client. Our own name lookup is only a quarter of a second, so speeding that up would not help.

Why this matters

This is the first thing every returning user sees. A ~6.6 s spinner on the account area makes the app feel slow to start, even though the rest of the page is already interactive.

The good news — we can likely fix most of it ourselves

To show the account (name + avatar), we only need the wallet's address. Privy makes that address available almost immediately after login. Today, though, our spinner waits for the full smart wallet "client" (the thing needed to sign transactions), which is the slow ~3.9 s part.

If we display the account as soon as we have the address, and only wait for the full client when the user actually signs something, the spinner should drop from ~6.6 s to roughly ~1.4 s. We have confirmed the address is available through Privy's official API, so this fix does not depend on Privy changing anything.

There are also a few open questions we want to raise with the Privy team directly (see the technical section).

Requirements

What should be done

  • Split "ready to display" (needs only the smart wallet address) from "ready to sign" (needs the full client), so the account UI no longer blocks on smart wallet provisioning.
  • Drive the account display + Namestone identity lookup off the smart wallet address (user.linkedAccounts, walletClientType === 'privy_smart_account') instead of useActiveWallet().wallet.
  • Keep write/sign flows gated on the real smart wallet client.
  • Remove the temporary timing instrumentation once the fix lands (see "Instrumentation" below).
  • Raise the open questions with Privy and record their answers here.

Supplements

How we measured it

Temporary timing logs were added (prefixed [wallet-timing], still on a local branch, not merged). They record each milestone once, timestamped from app start. Enable in any build with localStorage.setItem("wallet-timing", "1") then reload; always on in dev.

Files touched (to be reverted after the fix):

  • pkgs/frontend/utils/walletTiming.ts (new helper)
  • pkgs/frontend/app/components/layout/AccountMenu.tsx
  • pkgs/frontend/hooks/useWallet.ts
  • pkgs/frontend/hooks/useENS.ts
  • pkgs/frontend/app/routes/api.namestone.$action.tsx

Representative measurement (fresh reload, returning user)

[wallet-timing] +886ms  (Δ886ms)   account-menu-mount
[wallet-timing] +2306ms (Δ1420ms)  privy-ready            ← Privy hydration ~1.4s
[wallet-timing] +2307ms (Δ1ms)     logged-in
[wallet-timing] +2307ms (Δ1ms)     smart-wallet-ready  {isSmartWallet: false}   ← false positive, see note
[wallet-timing] +6248ms (Δ3941ms)  wallet-usable       {isSmartWallet: true}    ← smart wallet client ~3.9s
[wallet-timing] namestone resolve-names round-trip: 235ms {count: 1}            ← Namestone is fast
[wallet-timing] +6576ms (Δ328ms)   identity-resolved   {hasIdentity: true}
[wallet-timing] +6577ms (Δ1ms)     spinner-stop                                 ← total ~6.6s

Note: smart-wallet-ready fired early with isSmartWallet: false because at the logged-in instant the embedded wallet was not yet in wallets[], so isPreparingSmartWallet briefly read false. The trustworthy "wallet is actually usable" milestone is wallet-usable at +6248 ms. This false positive is itself evidence that useSmartWallets() gives us no reliable readiness signal (see Q2).

Root cause (technical)

  • The spinner condition is !ready || (isLoggedIn && (isPreparingSmartWallet || isIdentityLoading)) in AccountMenu.tsx.
  • useActiveWallet() returns wallet === undefined while an embedded wallet is connected but useSmartWallets().client is not yet provisioned. That gap is ~3.9 s.
  • useActiveWalletIdentity() reads its address from useActiveWallet().wallet, so the Namestone query is enabled: false and does not even start until the smart wallet client is ready — the two waits are serial, not parallel. (Namestone itself is only 235 ms, so this serialization is not the current bottleneck, but it's worth noting.)

Proposed fix (technical)

  • Get the smart wallet address from Privy's user object:
    const { user } = usePrivy();
    const smartWalletAddress = user?.linkedAccounts.find(
      (a): a is WalletWithMetadata =>
        a.type === "wallet" && a.walletClientType === "privy_smart_account",
    )?.address;
  • Feed that address into useActiveWalletIdentity / the account display so both start as soon as the address exists (right after hydration), independent of client provisioning.
  • Keep useActiveWallet().wallet (the signing client) as the gate for send/write flows only.
  • Expected result: spinner ~6.6 s → ~1.4 s (hydration) + ~0.24 s (identity).

Open questions for Privy (documented behavior is missing)

Confirmed against the official docs — these are genuine gaps, not things we can self-serve:

  1. What is the useSmartWallets().client ~3.9 s doing? (embedded signer reconstruction / bundler+paymaster calls / key decryption). Provisioning timing is not documented.
  2. Is there an official readiness signal for the client? Docs expose only { client, getClientForChain } — no ready / isLoading. We currently infer with !!client, which is fragile (see the false positive above).
  3. Returning users pay the ~3.9 s on every load. Is there a supported way to cache/shorten client construction?
  4. Confirm the pattern: display + identity off user.linkedAccounts address, and only wait for client at sign time — is this the recommended approach?
  5. Privy ready (hydration) is itself ~1.4 s. Any recommended setup for SSR (React Router v7) to reduce this?

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    HighPriority HighbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions