fix(sdk): mobile wallet UX fixes and connection stability - #53
Conversation
- RozoPayModal: force EVM connect on deeplink open when Solana-only auto-connected; matches injected connector by Solana wallet name (works for Phantom, Backpack, any dual-chain wallet) - RozoPayModal: fix dualChainConnect flag not cleared after Phantom connect, causing subsequent MetaMask connect to loop back to SELECT_METHOD instead of SELECT_TOKEN - RozoPayProvider: combine route+routeMeta into single setState to prevent two-render race on 'Pay with another wallet' click - RozoPayProvider: reuse consumer QueryClient if present - SelectMethod: extract disconnectAll() with Promise.allSettled and isSolanaConnected/isStellarConnected guards to prevent TypeError when disconnect is undefined on uninitialized adapter - OptionsList: replace 'Loading...' subtitle with spinner on loading option item (Pay to Address tile) - ConnectorSolana: add error state + Retry/Cancel buttons on WalletConnect failure (was a silent hang) - ConnectorStellar: same error handling as ConnectorSolana - rozoPay: add sourceAmountUnits/sourceTokenSymbol optional fields to zRozoPayOrderMetadata schema
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0adee842fd
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
…h-another-wallet
Adds a session-lifetime userDisconnected flag to PayContext.
Problem: On Phantom mobile deeplink, after user clicks 'Pay with another
wallet' and disconnectAll runs, Phantom's in-app browser silently
re-authorizes the EVM injected provider (persistent origin trust).
This flipped isEthConnected back to true, triggering auto-navigate
effects that routed the user to SELECT_TOKEN or back to SELECT_METHOD
with only EVM connected — never reaching MOBILECONNECTORS.
Fix: userDisconnected flag set to true right before disconnectAll,
guarding three auto-navigate effects in RozoPayModal:
- Force-connect (Phantom deeplink EVM auto-connect)
- SELECT_METHOD → SELECT_TOKEN auto-navigate
- CONNECT/CONNECTORS/MOBILECONNECTORS → SELECT_TOKEN auto-route
Flag resets on:
- Modal open/close (fresh session)
- Explicit wallet pick in MobileConnectors handleInjectedWallet /
handleDeeplinkWallet
- ConnectorList onClick (desktop CONNECTORS page)
Safe under repeat cycles: each 'Pay with another wallet' click sets the
flag, disconnect + route works cleanly, wallet pick resets flag, connect
+ auto-route resumes. No state leaks between cycles.
- walletConfigs: enable showInMobileConnectors for MetaMask (was false). Missing entry caused MetaMask to never appear as a deeplink option, even when not injected in the current browser (Safari, Phantom in-app, etc.). - MobileConnectors: dedup logic now cross-matches both name and shortName between injected and deeplink configs so any injected wallet correctly hides its deeplink counterpart. - bundle-analysis, package.json version bumps, pnpm-lock refresh.
Review — mobile wallet UX fixesSolid direction overall: the 🟠 P1 — Force-EVM-connect races the auto-navigation on Phantom/Backpack deeplink
On a mobile deeplink open, Solana connects first via wallet-standard ( In that same render, the SELECT_METHOD auto-navigation effect sees Fix: set 🟡 P2 — "Try Again" on Solana connect error may hang on the spinner
Try Again does 🟡 P2 —
|
shawnmuggle
left a comment
There was a problem hiding this comment.
Reviewed the full diff — no P0. The mobile UX fixes are sound: userDisconnected flag to stop auto-reconnect from re-routing after an explicit disconnect, disconnectAll() via Promise.allSettled with connected-guards (fixes r.disconnect is not a function), atomic route+routeMeta state (fixes the two-render race), and the Solana/Stellar connector Try Again / Cancel recovery.
RozoPayModal, SelectMethod, RozoPayProvider, PayContext, ConnectorSolana, ConnectorStellar, ConnectorList, OptionsList, rozoPay.ts), and both target master. This PR is effectively an iteration of the same mobile-UX work in #47:
- Force-EVM-connect: #47 hardcodes
isPhantom()+connectors.find(c => c.type === 'injected')(first injected); this PR generalizes to match bysolanaWallet.adapter.name. This one is the better approach. userDisconnectedguard exists here, not in #47.
They cannot be merged independently — whichever lands second will conflict heavily and may re-introduce the older approach. Recommend deciding an order (land this UX approach, rebase/drop the duplicate in #47) or folding them together.
P2 (non-blocking): the force-EVM match c.name.toLowerCase().includes(solanaName) is a substring match — a short wallet name could in theory match an unrelated injected connector. Low risk, but an exact-name match would be safer.
Approving on the UX logic; please resolve the #47 overlap before/at merge.
Summary
Fixes several mobile wallet UX issues, primarily around Phantom/Backpack deeplink flows and connection stability.
Changes
Connection fixes
connect()for the matching injected EVM connector — wallet approves inside its own browser, no user prompt needed. Matched by Solana wallet name, not hardcoded to Phantom.dualChainConnectflag not cleared: After Phantom connects both chains, the flag was never cleared. Subsequent wallet connections (e.g. MetaMask) triggered the dual-chain handler again and looped back toSELECT_METHODinstead ofSELECT_TOKEN. Fixed by moving the clear inside the route-guarded effect.State race fix
RozoPayProviderwas settingrouteandrouteMetaas two separatesetStatecalls. After anawait(disconnect sequence), this created a two-render window where guards read stale route, causing 'Pay with another wallet' click to do nothing. Combined into a singlesetState.Disconnect safety
disconnectAll(): Extracted shared helper inSelectMethodusingPromise.allSettledwithisSolanaConnected/isStellarConnectedguards. FixesTypeError: r.disconnect is not a functionwhen Solana wallet adapter is uninitialized (Phantom deeplink, Solana-only connected).UX improvements
'Loading...'subtitle text with a spinner in the right-icon slot while deposit address options fetch.ConnectorSolanaandConnectorStellarnow showConnection Failedwith Try Again and Cancel buttons instead of a silent hang (e.g. WalletConnect proposal expired).Schema
sourceAmountUnitsandsourceTokenSymboloptional fields tozRozoPayOrderMetadatainpay-common.Testing
pay-common35/35 tests pass