Skip to content

wallet-sdk: drop pre-emptive popup for unauthenticated wallet_connect#37

Merged
coffeexcoin merged 1 commit into
mainfrom
claude/privy-wallet-iframe-issue-OVAeW
May 9, 2026
Merged

wallet-sdk: drop pre-emptive popup for unauthenticated wallet_connect#37
coffeexcoin merged 1 commit into
mainfrom
claude/privy-wallet-iframe-issue-OVAeW

Conversation

@coffeexcoin
Copy link
Copy Markdown
Contributor

Summary

pickInitialMode currently escalates to popup mode whenever the user invokes one of wallet_connect / eth_requestAccounts / wallet_requestPermissions against a wallet host that reports authenticated: false. The original rationale (preserved in the file's header docstring) was that external OAuth providers reject embedded user agents.

That gate is too coarse. Email OTP, SMS, and navigator.credentials.get()-based passkey sign-in all work fine inside the iframe; only OAuth providers genuinely require a top-level surface, and the Privy SDK already pops its own provider window from inside the iframe when the user clicks one of those buttons. Forcing a popup for every unauth auth method just to handle the OAuth subset is a UX tax we don't need to pay.

This PR makes the iframe the default surface for every request unless:

  • the caller explicitly forced dialog: 'popup', or
  • the wallet host explicitly requests escalation at runtime via __internal { type: 'switch', mode: 'popup' } (the existing channel — used today for IO-v2-unsupported parents not on the trusted-host list, and reserved for cases like Safari-only WebAuthn create() that are genuinely top-level-only).

The secure() iframe-eligibility check (HTTPS, IO-v2 supported OR parent on trusted-hosts) still runs before the first request and still transparently downgrades to popup when those gates fail. So the existing safety properties for clickjacking-vulnerable browsers stay intact.

Changes

  • packages/wallet-sdk/src/core/Wallet.ts
    • Remove TOP_LEVEL_AUTH_METHODS set + requiresTopLevelAuth helper
    • pickInitialMode simplified to: if the caller forced popup, return popup; otherwise iframe
    • Header docstring updated to describe the new default + the host-driven escalation contract
  • packages/wallet-sdk/test/src/Wallet.test.ts
    • "opens wallet_connect in a top-level popup in auto mode" → "opens wallet_connect in the iframe in auto mode (host handles inline auth)" — asserts window.open is not called
    • "opens eth_requestAccounts in a top-level popup even when iframe mode was requested" replaced with a positive assertion that dialog: 'popup' override still pops a window for eth_requestAccounts

Test plan

  • pnpm test (17/17 pass)
  • pnpm typecheck
  • pnpm build
  • Manual: wallet_connect from the SDK against a host serving an inline <LoginView> — auth happens inline; popup only opens when the user picks an OAuth provider
  • Manual: dialog: 'popup' override still opens a top-level popup for both wallet_connect and eth_requestAccounts
  • Manual: parent on a non-IO-v2 browser without trusted-host entry still gets transparently downgraded to popup via secure()

Notes for reviewers

  • No change to the __internal { type: 'switch' } escalation contract, the popup factory, the messenger, or the security model.
  • secure() continues to be the authoritative iframe-eligibility check for clickjacking defence; this PR doesn't touch it.
  • Hosts that depended on the old behaviour (expecting a popup whenever the user hit wallet_connect unauthenticated) will need to render their own login surface in the iframe — or pass dialog: 'popup' through createWallet.

https://claude.ai/code/session_018kuvZqC6ReXAqjGbJt1yNz


Generated by Claude Code

The host now renders <LoginView> inline in the iframe for unauthenticated
sessions (email OTP, SMS, passkey) — Privy's auth `allowed_domains`
already covers the wallet host origin, so these flows don't need a
popup. External OAuth providers (Google, Apple, …) still demand a
top-level surface; the Privy SDK handles those by opening its own
provider popup directly from the iframe.

This removes the `TOP_LEVEL_AUTH_METHODS` set + `requiresTopLevelAuth`
gate from `pickInitialMode`. The iframe is now the default surface for
every request unless the caller forces popup mode or the iframe
explicitly escalates via `__internal { type: 'switch', mode: 'popup' }`
(IO-v2-unsupported parents not on the trusted-host list, Safari-only
WebAuthn enrollment).

Updates Wallet.test to reflect the new behaviour: `wallet_connect`
under auto mode no longer pre-opens a popup; explicit `dialog: 'popup'`
override still does.

https://claude.ai/code/session_018kuvZqC6ReXAqjGbJt1yNz
@vercel
Copy link
Copy Markdown

vercel Bot commented May 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agw-wallet-sdk-demo Ready Ready Preview, Comment May 9, 2026 9:28pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
agw-nextjs Skipped Skipped May 9, 2026 9:28pm
mpp-demo Skipped Skipped May 9, 2026 9:28pm

Request Review

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coffeexcoin coffeexcoin merged commit 8993847 into main May 9, 2026
13 checks passed
@coffeexcoin coffeexcoin deleted the claude/privy-wallet-iframe-issue-OVAeW branch May 9, 2026 21:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants