Skip to content

feat(mobile): WalletConnect pairing and connect dApp modal - #543

Merged
Miracle656 merged 2 commits into
Miracle656:mainfrom
noevidence1017:feat/mobile-walletconnect-connect-modal-495
Jul 29, 2026
Merged

feat(mobile): WalletConnect pairing and connect dApp modal#543
Miracle656 merged 2 commits into
Miracle656:mainfrom
noevidence1017:feat/mobile-walletconnect-connect-modal-495

Conversation

@noevidence1017

Copy link
Copy Markdown
Contributor

Summary

Ports the browser wallet's WalletConnect v2 integration to React Native so the mobile wallet can pair with external dApps and sign for web apps from the phone. Scanning or pasting a wc: URI establishes a session after an explicit approval.

closes #495

What changed

lib/polyfills.ts — React Native shims

WalletConnect and the Stellar SDK both assume browser or Node globals that Hermes does not provide. Import order matters here and is the reason this is its own module: @walletconnect/react-native-compat has to be evaluated before the WalletConnect core, which reads TextEncoder, URL and async storage at import time. Also installs react-native-get-random-values, react-native-url-polyfill/auto and Buffer. lib/walletConnect.ts imports it first.

lib/walletConnectHelpers.ts — pure logic

URI normalisation, session parsing, request parsing, JSON-RPC envelopes and user-rejection detection, kept free of native imports so the rules are unit-testable without booting Expo, WalletConnect or the Stellar SDK.

URI handling is a little more forgiving than the web version, because phones are where the awkward inputs come from: surrounding whitespace from clipboards and scanners is trimmed, and a deep link that wraps the pairing URI in a percent-encoded query parameter is unwrapped.

lib/walletConnect.ts — client, sessions and signing

The signing pipeline is a faithful port — same recording-mode simulation, same signatureExpirationLedger handling, same enforce-mode re-simulation so the footprint covers the wallet contract storage __check_auth reads. What differs is the runtime plumbing:

  • Session snapshots persist to AsyncStorage instead of sessionStorage, and the fee-payer secret comes from the OS keychain (lib/walletStore.ts, expo-secure-store) rather than web storage.
  • Hashing goes through expo-crypto, since Hermes has no crypto.subtle.
  • The passkey assertion comes from an injected signer (registerAuthEntrySigner) rather than a direct navigator.credentials call, so this module stays independent of any one passkey implementation. Without a registered signer, signing fails with a clear message rather than silently doing nothing.
  • Incoming session_request events are held in a subscribable queue instead of being dispatched as one-shot DOM events. On mobile an approval surface can easily mount after the request arrives; a fire-and-forget event would drop it.
  • Concurrent callers share one in-flight client init, and a failed init is not cached so a later call can retry.

hooks/useWalletConnect.ts — React binding over the store, exposing sessions, the pending proposal, the pending requests, and an error for surfacing a missing project id.

components/ConnectDAppModal.tsx — bottom sheet with scan and paste tabs. The browser camera plus jsQR pipeline is replaced by expo-camera's native QR scanner, with camera-permission handling and a graceful fallback to the paste tab when permission is denied. Paste has a clipboard shortcut. Once a proposal arrives the sheet shows who is asking, what they get and which account is involved, and the session is established only on an explicit approve.

app/index.tsx — a Connect dApp entry point and the list of connected dApps, so the flow is reachable and testable.

Dependencies

@walletconnect/core, @walletconnect/utils and @walletconnect/react-native-compat are pinned to 2.17.1, matching what @walletconnect/web3wallet@1.16.1 itself pins — a floating ^2.x pulls in a second copy of @walletconnect/types and the Core instance then fails to typecheck against ICore.

Also adds expo-camera, expo-clipboard, expo-crypto, expo-secure-store, @react-native-async-storage/async-storage, react-native-get-random-values, react-native-url-polyfill and buffer, plus jest-expo as a dev dependency — frontend/mobile had no test harness.

app.json gains the expo-camera plugin with a camera-usage string and the expo-secure-store plugin.

Configuration

# frontend/mobile/.env.local
EXPO_PUBLIC_WC_PROJECT_ID=your_project_id
EXPO_PUBLIC_NETWORK=testnet
EXPO_PUBLIC_SOROBAN_RPC_URL=https://soroban-testnet.stellar.org

Sessions are namespaced to stellar:testnet or stellar:pubnet from EXPO_PUBLIC_NETWORK and advertise stellar_signXDR and stellar_signAndSubmitXDR.

Testing

npm test — 16 unit tests over the parsing helpers: URI normalisation (plain, whitespace-padded, uppercase scheme, deep-link-wrapped, invalid), chain id mapping, session parsing with and without namespaced accounts, all seven XDR param shapes dApps send plus the null cases, request flattening, JSON-RPC envelopes and rejection detection.

npm run typecheck passes.

Manually verified end to end against a WalletConnect test dApp: scanning the QR code and pasting the URI both produce a session proposal, approving establishes the session, and it appears in the connected list.

Notes for review

  • This PR stops at establishing sessions and the request queue. The UI that approves individual sign requests is 68. dApp approval modal #496, which builds on this branch.
  • tsconfig.json gains "types": ["jest"]; without it TypeScript did not pick up the jest globals under the Expo base config.
  • The package-lock.json diff is large because the WalletConnect tree and the jest harness are both new to this package.

Port the browser wallet's WalletConnect v2 integration to React Native so the
mobile wallet can pair with external dApps and sign for web apps from the phone.

- lib/polyfills.ts: the shims WalletConnect and the Stellar SDK need on Hermes.
  react-native-compat has to be evaluated first, so the import order is fixed
  here and this module is imported before anything else in lib/walletConnect.ts.
- lib/walletConnectHelpers.ts: pure URI, session and request parsing kept free
  of native imports so the rules are unit-testable. Pasted or scanned deep links
  that wrap the pairing URI in a query parameter are unwrapped.
- lib/walletConnect.ts: client lifecycle, pairing, session approve/reject/
  disconnect and the passkey signing pipeline. Sessions persist to AsyncStorage,
  hashing goes through expo-crypto, and the passkey assertion comes from an
  injected signer so this module stays independent of any one passkey
  implementation. Incoming requests are held in a subscribable queue rather than
  dispatched as one-shot events, so an approval UI that mounts later still sees
  them.
- lib/walletStore.ts: wallet address and fee-payer secret in the OS keychain via
  expo-secure-store rather than web storage.
- lib/network.ts: network config reading EXPO_PUBLIC_* variables.
- hooks/useWalletConnect.ts, components/ConnectDAppModal.tsx: scan with
  expo-camera or paste a URI, then approve or reject the session proposal.
- app/index.tsx: Connect dApp entry point and the list of connected dApps.

Adds a jest-expo test harness with unit coverage for the parsing helpers.
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

@noevidence1017 is attempting to deploy a commit to the miracle656's projects Team on Vercel.

A member of the Team first needs to authorize it.

…nect-connect-modal-495

# Conflicts:
#	frontend/mobile/README.md
#	frontend/mobile/app/index.tsx
#	frontend/mobile/package-lock.json
#	frontend/mobile/package.json
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@noevidence1017 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@Miracle656

Copy link
Copy Markdown
Owner

Thanks @noevidence1017 — WalletConnect v2 pairing with the connect-dApp sheet, the polyfill layer Hermes needs, and sessions namespaced per EXPO_PUBLIC_NETWORK. Holding session_request events in a subscribable queue rather than firing them once is the right call — an approval UI that mounts late still sees the request. Closes #495.

Merged main in and folded app/index.tsx together: the home route now keeps the themed createStyles(colors) from #490 and your WalletConnect entry point / session list, with the session-row hex values mapped onto ThemeColors roles so the screen follows the light/dark toggle. Typecheck clean, 100 tests pass.

@Miracle656
Miracle656 merged commit 1d5d5a3 into Miracle656:main Jul 29, 2026
9 of 12 checks passed
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.

67. WalletConnect / connect modal

3 participants