Skip to content

fix(app): unblock wallet connect from social in-app browsers (X) - #5

Merged
OxToF merged 2 commits into
mainfrom
fix/in-app-browser-connect
Jul 22, 2026
Merged

fix(app): unblock wallet connect from social in-app browsers (X)#5
OxToF merged 2 commits into
mainfrom
fix/in-app-browser-connect

Conversation

@OxToF

@OxToF OxToF commented Jul 22, 2026

Copy link
Copy Markdown
Owner

The problem

Visitors arriving from a shared X link cannot connect a wallet at all, and the app never tells them. It looks like a working page with two working buttons.

The on-chain wallet counter has not moved since 14/07 while links circulate. New wallets per day, from quest_completions:

03/07: 223   09/07: 64   14/07: 2   19/07: 1
04/07: 351   10/07:  9   15/07: 1   20/07: 1
06/07: 105   12/07:  9   17/07: 1   21/07: 0
07/07:   7   13/07:  4   18/07: 1   22/07: 0

The counter itself was verified correct (prod API returns the same number as a recomputation from the database, cache MISS). The ingestion is what stopped.

Root cause

isIosAndRedirectable() in @solana/wallet-adapter-base gates the Phantom/Solflare universal links on (iphone|ipad) && ua.includes('safari'). X's iOS WebView reports ... Mobile/21E236 Twitter for iPhone/11.xno safari token. So:

Context Modal offers Tapping it does
X in-app iOS Phantom, Solflare Phantom is NotDetectedwindow.open('https://phantom.app') → App Store page. Dead end.
X in-app Android MWA, Phantom, Solflare MWA offered but its association intent cannot leave the host WebView
Safari iOS Phantom, Solflare universal links → works
Chrome Android MWA opens the installed wallet → works

It works everywhere except exactly where the traffic comes from. WalletModal lists non-installed wallets flat when none is Installed, so there is no warning either.

Also corrects a false comment in providers.tsx: MWA is not hidden in a WebView. getIsSupported() in @solana-mobile/wallet-adapter-mobile@2.2.9 only tests isSecureContext && /android/i.

The fix

Detect the in-app browser and hand the user the universal link the adapters refused to build. Two design points:

  • gated on capability, not the UA: if any wallet is Installed, never switch. A wallet's own in-app browser is a WebView too and the UA alone would misclassify it.
  • URL read at click time, so the ?ref= referral from the shared link survives into the wallet browser (verified end to end).

Verification

  • detection checked against 11 real user agents: X iOS, X Android, Instagram iOS, Android 16 WebView caught; Safari iOS, CriOS, FxiOS, Chrome Android, desktop untouched
  • rendered both states at 375px: normal browser unchanged (Select Wallet, no console errors), in-app state shows the escape screen
  • yarn build passes

Also carries a separate one-file commit: the IDL for checkpoint_lp was missing lp_user_info, so production served 9 accounts where the deployed program expects 10. Harmless today (the component is not mounted) but wrong.

🤖 Generated with Claude Code

OxToF and others added 2 commits July 22, 2026 19:02
The LP-reward accounting fix added lp_user_info to four contexts, but the IDL
committed in 8e4454d carried it for add_liquidity, claim_lp_rewards and
remove_liquidity only. checkpoint_lp, the very subject of that commit, was left
with 9 accounts while the deployed program (slot 477943093) expects 10.

Confirmed against the production bundle, which serves the stale shape. It went
unnoticed because LpEmissions.tsx passes lpUserInfo inside an `as any` cast, so
TypeScript never saw the missing account, and the component is not mounted in
page.tsx, so no tester could reach the path. Harmless today, broken the moment
that component is wired back in.

Resynced verbatim from target/idl/soladrome.json. Note the on-chain published IDL
is stale too (checkpoint_lp with 9 accounts): cosmetic, affects explorers and
third-party clients, fix with `anchor idl upgrade`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… dead end

Users arriving from a shared X link cannot connect a wallet at all, and the app
never tells them. The counter of on-chain wallets has not moved since 14/07 while
links circulate: 0 new wallets on 21/07 and 22/07, against 351 on 04/07.

The cause is one predicate in @solana/wallet-adapter-base. isIosAndRedirectable()
gates the Phantom and Solflare universal links on (iphone|ipad) && ua contains
"safari". X's iOS WebView reports "... Mobile/21E236 Twitter for iPhone/11.x",
with no safari token, so Phantom stays NotDetected and tapping it lands on
window.open('https://phantom.app') via WalletProviderBase, i.e. the App Store
page. The modal lists it as a normal option with no warning, because WalletModal
shows every non-installed wallet flat when none is Installed.

Android is the mirror image: X's WebView carries the `wv` token, so providers.tsx
keeps Phantom/Solflare, and MWA is offered rather than hidden. Its getIsSupported()
only tests isSecureContext && /android/i, with no WebView check, contrary to what
the comment there claimed. That comment is corrected here.

So we detect the in-app browser ourselves and hand the user the universal link the
adapters refused to build. Two guards matter:

- the decision is gated on CAPABILITY, not the UA: if any wallet is Installed we
  never switch. A wallet's own in-app browser is a WebView too, and would
  otherwise be misclassified.
- the URL is read at CLICK time, not at render, so the ?ref= referral captured
  from the shared link survives the hop into the wallet browser.

Detection verified against 11 real user agents: X iOS, X Android, Instagram iOS
and Android WebView are caught; Safari iOS, CriOS, FxiOS, Chrome Android and
desktop are not.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
soladrome-lzn5 Ready Ready Preview, Comment Jul 22, 2026 5:05pm

@OxToF
OxToF merged commit f29f49d into main Jul 22, 2026
5 checks passed
@OxToF
OxToF deleted the fix/in-app-browser-connect branch July 22, 2026 17:29
OxToF added a commit that referenced this pull request Jul 28, 2026
New "Meme Contest" campaign (#5) in the Missions card: post a meme on X
tagging @soladrome (judged for the 5x 50 SOLA prize) and share it in the
Discord #memes-art channel with your wallet. Submitting BOTH links credits
+10 participation points and logs the entry for judging.

- app/lib/quests.ts: MEME group + meme_contest quest, memeVerify flag,
  exported DISCORD_MEME_ART, group `link` field.
- app/lib/xcode.ts: memeIntentUrl() + SOLADROME_HANDLE.
- app/components/Quests.tsx: two-input submission panel (X + Discord).
- app/app/api/meme-verify/route.ts: verifies the X post (oEmbed, tags
  @soladrome) + the Discord message (image via attachment OR embed, wallet
  in the same message via bot REST), dedup on the Discord message id.
- supabase/quests.sql: meme_contest (+10), meme_submissions ledger +
  claim_meme_submission (stores both links).

Prize (50 SOLA x5) is judged manually and paid out-of-band, like the bug bounty.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant