bug(frontend): Android Chrome cannot complete Connect Wallet (Lunc Dash / Galaxy Station hang + Keplr extension-only) #554

Closed
opened 2026-08-18 00:20:43 +00:00 by PlasticDigits · 26 comments
PlasticDigits commented 2026-08-18 00:20:43 +00:00 (Migrated from gitlab.com)

Working control (do not miss)

usera (Android 16 Chrome) successfully disconnected and reconnected a Terra wallet on https://ust1cmm.com (ustr-cmm frontend). After reconnect they had the connected-wallet chrome (orange indicator, top right) and reached Accept T&C.

That means ustr-cmm wallet connect works on the same device / browser / OS where DEX and bridge fail. Treat ustr-cmm frontend/ as the known-good connect implementation (PlasticDigits2/ustr-cmm). Diff DEX / bridge against it (wallet modal, WC pairing, deep links, overlays). Do not treat this incident as “mobile wallets are broken everywhere.”

T&C after that successful ust1cmm connect still failed with Keplr extension not found — that is the Legal portal signer (window.keplr), not a ustr-cmm connect bug.


Summary

Community report (2026-08-17/18): on Android 16 Chrome, https://dex.cl8y.com cannot complete Connect Wallet for Lunc Dash or Galaxy Station (UI sits on a spinner / “circles circling” and never shows an address). Keplr in that same Chrome tab is offered only as a desktop browser extension (Install / Extension pill); tapping it does not connect mobile Keplr. Workaround that does work: open the dApp inside the Keplr in-app browser. In-app Keplr (and, for one reporter, iOS Brave) can also complete Legal T&C. This issue bundles the DEX-side mobile Chrome connect gaps so the general public is not forced into an in-app browser.

Follow-on to closed #519 / !1053. That work added the same-device pairing sheet (Open {wallet} + Copy pairing link). Ops #521 marked Coolify ship + device QA done. This report shows Android Chrome still fails after that ship.

Reporter notes (anonymized):

  • usera (Android 16, Chrome): Lunc Dash and Galaxy Station do nothing except spin; Keplr in Chrome only offers the desktop extension; connecting via Keplr in-app browser succeeded (including T&C).
  • userb (iPhone): Keplr in-app browser and Brave both completed connect + T&C.

Cross-product (file separately; do not implement here):

  • bridge.cl8y.com — cannot tap Connect Terra Wallet (bridge repo).
  • ust1cmm.com / Legal portal — Accept T&C → Keplr extension not found (Legal portal uses window.keplr only).

Current codebase

  • Connect list: frontend-dapp/src/components/wallet/WalletModal.tsx hard-codes WALLET_OPTIONS: Station / Keplr / Cosmostation as WalletType.EXTENSION; LuncDash / Galaxy Station as WalletType.WALLETCONNECT. There is no Keplr (or Station/Cosmostation) WalletConnect row. Missing extensions get a dimmed row + Install link (WALLET_EXTENSION_INSTALL_URL → https://www.keplr.app/download). The Keplr row remains clickable even when the extension is absent (disabled is only isConnecting).
  • Controllers: frontend-dapp/src/services/terraclassic/wallet.ts constructs new KeplrController(WC_PROJECT_ID) (cosmes does support Keplr WalletConnect) and new GalaxyStationController(WC_PROJECT_ID), but new LUNCDashController() with no project id (Lunc Dash WC v1 / custom bridge). connectTerraWallet awaits controller.connect() with no timeout and no abort.
  • Connecting spinner: useWallet.ts sets isConnecting: true before the await and only clears it on success or thrown error. WalletButton.tsx disables the header CTA and shows an animate-spin SVG; on viewports < sm the “Connecting...” label is hidden, so the user only sees circles. WalletModal.tsx also shows “Connecting...” and disables every wallet row for the whole session wait.
  • #519 pairing sheet: walletConnectPairing.ts + walletConnectPairingHook.ts (installed in main.tsx before createRoot) + WalletConnectPairingModal.tsx in Layout.tsx. Cosmes QRCodeModal delegates to globalThis.__CL8Y_WC_PAIRING_MODAL__ on mobile. Desktop QR is unchanged (WC-M2). Deep links are user-gesture <a href> only (WC-M3).
  • Modal stacking: both Connect Wallet and the pairing sheet use Modal.tsx (z-[9999], portaled to document.body). Connect Wallet stays open while connect() is in flight. Dismissing the pairing sheet only clears useWalletConnectPairingStore; it does not abort controller.connect(), so isConnecting can stay true forever (no Cancel, unlike cl8y-bridge-monorepo TerraWalletModal.cancelConnection).
  • Android deep links: Lunc Dash uses luncdash://wallet_connect?payload=… (buildLuncDashDeepLink). Galaxy Station on Android uses buildAndroidWalletIntent against the cosmes android template (tests use https://station.hexxagon.io/wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;). That yields an https URL with a #Intent fragment, not a leading intent: URI. There is a Galaxy iOS unit test; no Android Galaxy deep-link test. Allowlist (WC-M5) includes wc:, luncdash:, keplrwallet:, galaxystation:, intent:, Hexxagon / Terra Station hosts — keplrwallet: is allowlisted even though the modal never starts Keplr WC.
  • In-app browser: documented as WC-M7 (valid alternate path, not the only fix). DEX has no in-app-browser detector or Connect-modal copy pointing users at Keplr / Lunc Dash / Galaxy Station browsers. Bridge frontend already has detectInAppBrowser.
  • Legal after connect: DEX ConnectedTermsGate redirects to terms.cl8y.com (C1: DEX must not implement ADR-036). Portal web/src/pages/terra.ts throws Keplr extension not found when !window.keplr. Chrome Android will fail T&C even after a successful WC connect unless the user is in a Keplr-injected WebView. Track that in the Legal repo; DEX should not strand users with a dead Accept button.

Existing tests: walletConnectPairing.test.ts, walletConnectPairingHook.test.ts, WalletConnectPairingModal.test.tsx, cosmesPatch127.test.ts, WalletModal.test.tsx. make verify-issue-519 is unit-only; it never exercised Android Chrome + real wallet apps.


Why this is needed

  • Android Chrome is a primary retail browser. After #519, Lunc Dash / Galaxy Station still do not complete for usera; the only working path is Keplr’s in-app browser. That is WC-M7 as a workaround, not a product-quality Connect flow.
  • Keplr is listed as Extension-only, so Chrome Android users are sent to a desktop download page. Cosmes KeplrController already takes VITE_WC_PROJECT_ID; the UI never uses it. #521’s “Keplr Mobile same-device Open / Copy” checkbox is not reflected in WALLET_OPTIONS.
  • An unbounded isConnecting spinner with no cancel matches the reported “circles circling” and leaves the header CTA disabled.
  • Same-device pairing that the user cannot see, cannot cancel, or cannot open into the installed app is equivalent to the original #519 QR-only failure.

Constraints / guardrails

  1. Keep WC-M1–WC-M7. Do not replace desktop QR with AppKit/Web3Modal unless product asks (WC-M2). Do not auto-location.href from the async display_uri callback (WC-M3). Copy the raw wc: URI via CopyButton (WC-M4). Pairing hrefs must pass isAllowedWalletConnectDeepLink (WC-M5). Keep the cosmes hook + vanilla Open/Copy fallback and patch-package (WC-M6). Keep in-app browser as an alternate path, documented in the modal — not “the” fix (WC-M7).
  2. Do not treat WalletConnect rows as missing extensions (#139 / #160).
  3. Do not add @walletconnect/modal / Reown AppKit just for this (playbook).
  4. Lunc Dash scheme stays luncdash://wallet_connect?payload=… unless vendor docs change; still ship Copy + generic wc: Open.
  5. User-gesture deep links only (<a href> / button click).
  6. Do not implement Legal ADR-036 in the DEX (C1). If T&C still requires window.keplr, surface a short “open in Keplr browser to accept terms” path; portal signing belongs in cl8y-ecosystem-legal.
  7. Production CSP / VITE_WC_PROJECT_ID: do not drop the Vite WC project-id guard (#378). Do not widen CSP connect-src to https:.
  8. Retail copy: short labels (Open Lunc Dash, Open Galaxy Station, Open Keplr, Copy pairing link, Open your wallet, then return here.). Optional one-line in-app-browser hint. Follow AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.md.
  9. Desktop Keplr / Station / Cosmostation extension path unchanged when window.keplr / Station / Cosmostation is injected.
  10. After editing the cosmes patch: npx patch-package @goblinhunt/cosmes and update frontend-dapp/patches/.cosmes-patch-sha256.

Relevant files

Area Paths
Connect list frontend-dapp/src/components/wallet/WalletModal.tsx, WalletButton.tsx, walletExtensionInstall.ts
Connect state frontend-dapp/src/hooks/useWallet.ts, services/terraclassic/wallet.ts
Pairing UX utils/walletConnectPairing.ts, services/terraclassic/walletConnectPairingHook.ts, hooks/useWalletConnectPairingStore.ts, components/wallet/WalletConnectPairingModal.tsx, components/common/Layout.tsx, main.tsx
Modal chrome frontend-dapp/src/components/ui/Modal.tsx
Cosmes patch frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch
Legal (DEX gate only) components/legal/ConnectedTermsGate.tsx, utils/legalClickwrap.ts
Tests utils/__tests__/walletConnectPairing.test.ts, services/terraclassic/__tests__/walletConnectPairingHook.test.ts, components/wallet/__tests__/WalletConnectPairingModal.test.tsx, components/wallet/__tests__/WalletModal.test.tsx, hooks/__tests__/useWallet.test.ts, services/terraclassic/__tests__/cosmesPatch127.test.ts
Docs / skills docs/frontend.md § WalletConnect same-device, skills/AGENTS_FRONTEND_WALLETCONNECT_MOBILE.md, skills/AGENTS_FRONTEND_WALLET_CONNECT_MODAL.md, QA_TEMPLATE.md § 1.6
Verify make verify-issue-519 (extend or add verify-issue-NNN)

  1. Stop the infinite spinner. Closing Connect Wallet or the pairing sheet must abort/cancel controller.connect(), clear isConnecting, and restore the header CTA. Add a bounded WC wait (then a retail error + Retry), matching bridge cancelConnection.
  2. Pairing sheet must be the foreground UI on mobile WC. Hide or replace the Connect list while pairing (do not leave two z-[9999] dialogs racing). Show Open {wallet}, Open wallet, Copy pairing link as soon as display_uri fires; never leave the user on a spinner-only header.
  3. Fix Android deep links. Verify Lunc Dash luncdash:// and Galaxy Station Android intent/universal-link templates on Chrome Android 16. Prefer intent: / documented vendor schemes that actually open the app; keep Copy pairing link. Add unit tests for Android UA + Galaxy Station (today only iOS is covered).
  4. Keplr on mobile Chrome: when isWalletConnectMobileClient() and window.keplr is absent, offer Keplr via WalletConnect (WalletName.KEPLR + WalletType.WALLETCONNECT — controller already has WC_PROJECT_ID) or a primary CTA to open the dApp in the Keplr in-app browser (keplrwallet: allowlisted). Do not send Android users to the Chrome Web Store. When window.keplr is present (in-app browser), keep the existing Extension row (WC-M7).
  5. Short in-modal hint (not a wall of text): same-device wallets live in the phone; use Open / Copy; in-app browser still works.
  6. Cross-link Legal: after WC connect, if window.keplr is missing, do not dump users on a portal that only throws Keplr extension not found without a next step.

Acceptance criteria

  • Android Chrome (including Android 16): Connect → Lunc Dash shows pairing sheet with Open Lunc Dash + Copy pairing link; after approve in the app and return, header shows terra1….
  • Same for Galaxy Station.
  • Android Chrome: Keplr is connectable without a desktop extension (WalletConnect and/or explicit in-app-browser CTA). Install must not be the only path.
  • Desktop Chrome: Keplr / Station / Cosmostation extension path unchanged; Lunc Dash / Galaxy Station still QR-only (WC-M2).
  • In-app Keplr / Lunc Dash / Galaxy Station browsers still connect (WC-M7).
  • No unbounded spinner: Cancel / close / timeout clears isConnecting and re-enables Connect Wallet.
  • Pairing hrefs stay on the WC-M5 allowlist; no javascript: / arbitrary https from the payload.
  • Deep links still require a user tap (WC-M3).
  • Docs / QA_TEMPLATE.md / AGENTS_FRONTEND_WALLETCONNECT_MOBILE.md updated; make verify-issue-NNN covers new unit tests.

Test plan (functional paths)

  1. Unit — pairing: existing #519 cases plus Android UA Galaxy Station deep-link shape; Lunc Dash luncdash://; keplrwallet: / intent: allowlist; reject javascript: and off-allowlist https.
  2. Unit — connect state: isConnecting true during WC; Cancel/close/timeout sets false and does not persist a half-session; pairing close does not leave header disabled.
  3. Unit — modal: mobile client + no window.keplr shows Keplr WC or in-app CTA, not Install-only; injected window.keplr still shows Extension + Ready.
  4. Unit — hook: Android Chrome UA is isWalletConnectMobileClient() === true; desktop UA still false.
  5. Manual Android 16 Chrome (required): Lunc Dash Open + Copy; Galaxy Station Open + Copy; Keplr WC or in-app CTA; reject pairing in wallet → retail error, spinner stops; dismiss pairing → can retry; airplane mode / WC relay down → timeout, not infinite spin.
  6. Manual desktop: Lunc Dash QR + second-device scan still works; Keplr extension unchanged.
  7. Manual in-app: Keplr / Lunc Dash / Galaxy Station browsers still connect and can swap (or at least show address + pass TermsGate when Legal allows).
  8. Regression: make verify-issue-519 still green; WalletModal #139/#160/#490 tests still green.

Test plan (attack / abuse / hack vectors)

Vector Expectation
Pairing payload uri is javascript:… / data: / https://evil.example/wc Hook returns false or href stripped; no navigation (WC-M5)
Mutated android / ios templates pointing off-allowlist isAllowedWalletConnectDeepLink rejects; <a href> unset
User closes pairing then a late WC session attaches Must not silently connect a session the user cancelled; clear pending connector
Stale cl8y_wallet_connection localStorage for WC Reconnect must not loop spinner forever; permanent errors still clear storage (isPermanentError)
Clickjacking / stacked modals Pairing actions must be visible and tappable; backdrop must not intercept Open / Copy
Clipboard abuse Copy writes only the wc: pairing URI, not extra scripts/HTML
WC project id missing in prod Existing Vite guard fails the build; do not weaken it
Deep-link intent: extra extras / S.browser_fallback_url to attacker site Stay on allowlisted hosts/schemes only
Rapid tap Connect on every row while connecting Rows stay disabled; one session; Cancel recovers

Verification criteria

  • New/extended Vitest suite green via make verify-issue-NNN (or documented extension of make verify-issue-519).

  • Manual matrix recorded on this issue (device / OS / browser / wallet / path / result):

    Device Browser Wallet Result
    Android 16 phone Chrome Lunc Dash Open address in header
    Android 16 phone Chrome Galaxy Station Open or Copy address in header
    Android 16 phone Chrome Keplr (WC or in-app CTA) address in header
    Android 16 phone Chrome dismiss / timeout spinner clears
    Desktop Chrome Keplr extension unchanged
    Desktop Chrome Lunc Dash QR second-device scan works
    Phone Keplr in-app browser Keplr still works (WC-M7)
  • Production https://dex.cl8y.com after Coolify rebuild (confirm npm ci applied @goblinhunt/cosmes@0.0.71-ghunt.22 + patch).

  • Legal T&C on mobile Chrome tracked on the Legal issue; DEX must not claim “connect works” if Accept T&C is still Keplr extension not found outside in-app Keplr.

## Working control (do not miss) **usera (Android 16 Chrome) successfully disconnected and reconnected a Terra wallet on `https://ust1cmm.com` (ustr-cmm frontend).** After reconnect they had the connected-wallet chrome (orange indicator, top right) and reached Accept T&C. That means **ustr-cmm wallet connect works on the same device / browser / OS** where DEX and bridge fail. Treat **ustr-cmm `frontend/` as the known-good connect implementation** (`PlasticDigits2/ustr-cmm`). Diff DEX / bridge against it (wallet modal, WC pairing, deep links, overlays). Do not treat this incident as “mobile wallets are broken everywhere.” T&C **after** that successful ust1cmm connect still failed with `Keplr extension not found` — that is the **Legal portal** signer (`window.keplr`), not a ustr-cmm connect bug. --- ## Summary Community report (2026-08-17/18): on **Android 16 Chrome**, `https://dex.cl8y.com` cannot complete **Connect Wallet** for **Lunc Dash** or **Galaxy Station** (UI sits on a spinner / “circles circling” and never shows an address). **Keplr** in that same Chrome tab is offered only as a **desktop browser extension** (Install / Extension pill); tapping it does not connect mobile Keplr. Workaround that **does** work: open the dApp inside the **Keplr in-app browser**. In-app Keplr (and, for one reporter, iOS Brave) can also complete Legal T&C. This issue bundles the DEX-side mobile Chrome connect gaps so the general public is not forced into an in-app browser. Follow-on to closed [#519](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/519) / [!1053](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/335). That work added the same-device pairing sheet (Open {wallet} + Copy pairing link). Ops [#521](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/521) marked Coolify ship + device QA done. This report shows **Android Chrome still fails** after that ship. Reporter notes (anonymized): - **usera** (Android 16, Chrome): Lunc Dash and Galaxy Station do nothing except spin; Keplr in Chrome only offers the desktop extension; connecting via Keplr **in-app browser** succeeded (including T&C). - **userb** (iPhone): Keplr in-app browser **and** Brave both completed connect + T&C. Cross-product (file separately; do not implement here): - `bridge.cl8y.com` — cannot tap Connect Terra Wallet (bridge repo). - `ust1cmm.com` / Legal portal — Accept T&C → `Keplr extension not found` (Legal portal uses `window.keplr` only). --- ## Current codebase - **Connect list:** [`frontend-dapp/src/components/wallet/WalletModal.tsx`](frontend-dapp/src/components/wallet/WalletModal.tsx) hard-codes `WALLET_OPTIONS`: Station / Keplr / Cosmostation as `WalletType.EXTENSION`; LuncDash / Galaxy Station as `WalletType.WALLETCONNECT`. There is **no** Keplr (or Station/Cosmostation) WalletConnect row. Missing extensions get a dimmed row + **Install** link (`WALLET_EXTENSION_INSTALL_URL` → `https://www.keplr.app/download`). The Keplr row remains clickable even when the extension is absent (`disabled` is only `isConnecting`). - **Controllers:** [`frontend-dapp/src/services/terraclassic/wallet.ts`](frontend-dapp/src/services/terraclassic/wallet.ts) constructs `new KeplrController(WC_PROJECT_ID)` (cosmes **does** support Keplr WalletConnect) and `new GalaxyStationController(WC_PROJECT_ID)`, but `new LUNCDashController()` with **no** project id (Lunc Dash WC v1 / custom bridge). `connectTerraWallet` awaits `controller.connect()` with **no timeout and no abort**. - **Connecting spinner:** [`useWallet.ts`](frontend-dapp/src/hooks/useWallet.ts) sets `isConnecting: true` before the await and only clears it on success or thrown error. [`WalletButton.tsx`](frontend-dapp/src/components/wallet/WalletButton.tsx) disables the header CTA and shows an **animate-spin** SVG; on viewports `< sm` the “Connecting...” label is `hidden`, so the user only sees circles. [`WalletModal.tsx`](frontend-dapp/src/components/wallet/WalletModal.tsx) also shows “Connecting...” and disables every wallet row for the whole session wait. - **#519 pairing sheet:** [`walletConnectPairing.ts`](frontend-dapp/src/utils/walletConnectPairing.ts) + [`walletConnectPairingHook.ts`](frontend-dapp/src/services/terraclassic/walletConnectPairingHook.ts) (installed in [`main.tsx`](frontend-dapp/src/main.tsx) before `createRoot`) + [`WalletConnectPairingModal.tsx`](frontend-dapp/src/components/wallet/WalletConnectPairingModal.tsx) in [`Layout.tsx`](frontend-dapp/src/components/common/Layout.tsx). Cosmes [`QRCodeModal`](frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch) delegates to `globalThis.__CL8Y_WC_PAIRING_MODAL__` on mobile. Desktop QR is unchanged (**WC-M2**). Deep links are user-gesture `<a href>` only (**WC-M3**). - **Modal stacking:** both Connect Wallet and the pairing sheet use [`Modal.tsx`](frontend-dapp/src/components/ui/Modal.tsx) (`z-[9999]`, portaled to `document.body`). Connect Wallet **stays open** while `connect()` is in flight. Dismissing the pairing sheet only clears [`useWalletConnectPairingStore`](frontend-dapp/src/hooks/useWalletConnectPairingStore.ts); it does **not** abort `controller.connect()`, so `isConnecting` can stay true forever (no Cancel, unlike `cl8y-bridge-monorepo` `TerraWalletModal.cancelConnection`). - **Android deep links:** Lunc Dash uses `luncdash://wallet_connect?payload=…` (`buildLuncDashDeepLink`). Galaxy Station on Android uses `buildAndroidWalletIntent` against the cosmes `android` template (tests use `https://station.hexxagon.io/wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;`). That yields an **https URL with a `#Intent` fragment**, not a leading `intent:` URI. There is a Galaxy **iOS** unit test; **no Android Galaxy deep-link test**. Allowlist (**WC-M5**) includes `wc:`, `luncdash:`, `keplrwallet:`, `galaxystation:`, `intent:`, Hexxagon / Terra Station hosts — `keplrwallet:` is allowlisted even though the modal never starts Keplr WC. - **In-app browser:** documented as **WC-M7** (valid alternate path, not the only fix). DEX has **no** in-app-browser detector or Connect-modal copy pointing users at Keplr / Lunc Dash / Galaxy Station browsers. Bridge frontend already has `detectInAppBrowser`. - **Legal after connect:** DEX [`ConnectedTermsGate`](frontend-dapp/src/components/legal/ConnectedTermsGate.tsx) redirects to `terms.cl8y.com` (**C1**: DEX must not implement ADR-036). Portal `web/src/pages/terra.ts` throws **`Keplr extension not found`** when `!window.keplr`. Chrome Android will fail T&C even after a successful WC connect unless the user is in a Keplr-injected WebView. Track that in the Legal repo; DEX should not strand users with a dead Accept button. Existing tests: `walletConnectPairing.test.ts`, `walletConnectPairingHook.test.ts`, `WalletConnectPairingModal.test.tsx`, `cosmesPatch127.test.ts`, `WalletModal.test.tsx`. `make verify-issue-519` is unit-only; it never exercised Android Chrome + real wallet apps. --- ## Why this is needed - Android Chrome is a primary retail browser. After #519, Lunc Dash / Galaxy Station still do not complete for usera; the only working path is Keplr’s in-app browser. That is **WC-M7 as a workaround**, not a product-quality Connect flow. - Keplr is listed as Extension-only, so Chrome Android users are sent to a desktop download page. Cosmes `KeplrController` already takes `VITE_WC_PROJECT_ID`; the UI never uses it. #521’s “Keplr Mobile same-device Open / Copy” checkbox is not reflected in `WALLET_OPTIONS`. - An unbounded `isConnecting` spinner with no cancel matches the reported “circles circling” and leaves the header CTA disabled. - Same-device pairing that the user cannot see, cannot cancel, or cannot open into the installed app is equivalent to the original #519 QR-only failure. --- ## Constraints / guardrails 1. **Keep WC-M1–WC-M7.** Do not replace desktop QR with AppKit/Web3Modal unless product asks (**WC-M2**). Do not auto-`location.href` from the async `display_uri` callback (**WC-M3**). Copy the raw `wc:` URI via `CopyButton` (**WC-M4**). Pairing hrefs must pass `isAllowedWalletConnectDeepLink` (**WC-M5**). Keep the cosmes hook + vanilla Open/Copy fallback and `patch-package` (**WC-M6**). Keep in-app browser as an **alternate** path, documented in the modal — not “the” fix (**WC-M7**). 2. **Do not treat WalletConnect rows as missing extensions** ([#139](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/139) / [#160](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/160)). 3. **Do not add `@walletconnect/modal` / Reown AppKit** just for this (playbook). 4. **Lunc Dash scheme** stays `luncdash://wallet_connect?payload=…` unless vendor docs change; still ship Copy + generic `wc:` Open. 5. **User-gesture deep links only** (`<a href>` / button click). 6. **Do not implement Legal ADR-036 in the DEX** (**C1**). If T&C still requires `window.keplr`, surface a short “open in Keplr browser to accept terms” path; portal signing belongs in `cl8y-ecosystem-legal`. 7. **Production CSP / `VITE_WC_PROJECT_ID`:** do not drop the Vite WC project-id guard ([#378](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/378)). Do not widen CSP `connect-src` to `https:`. 8. **Retail copy:** short labels (**Open Lunc Dash**, **Open Galaxy Station**, **Open Keplr**, **Copy pairing link**, **Open your wallet, then return here.**). Optional one-line in-app-browser hint. Follow [`AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.md`](skills/AGENTS_FRONTEND_COPY_COGNITIVE_LOAD.md). 9. **Desktop Keplr / Station / Cosmostation extension path unchanged** when `window.keplr` / Station / Cosmostation is injected. 10. After editing the cosmes patch: `npx patch-package @goblinhunt/cosmes` and update `frontend-dapp/patches/.cosmes-patch-sha256`. --- ## Relevant files | Area | Paths | |------|--------| | Connect list | `frontend-dapp/src/components/wallet/WalletModal.tsx`, `WalletButton.tsx`, `walletExtensionInstall.ts` | | Connect state | `frontend-dapp/src/hooks/useWallet.ts`, `services/terraclassic/wallet.ts` | | Pairing UX | `utils/walletConnectPairing.ts`, `services/terraclassic/walletConnectPairingHook.ts`, `hooks/useWalletConnectPairingStore.ts`, `components/wallet/WalletConnectPairingModal.tsx`, `components/common/Layout.tsx`, `main.tsx` | | Modal chrome | `frontend-dapp/src/components/ui/Modal.tsx` | | Cosmes patch | `frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch` | | Legal (DEX gate only) | `components/legal/ConnectedTermsGate.tsx`, `utils/legalClickwrap.ts` | | Tests | `utils/__tests__/walletConnectPairing.test.ts`, `services/terraclassic/__tests__/walletConnectPairingHook.test.ts`, `components/wallet/__tests__/WalletConnectPairingModal.test.tsx`, `components/wallet/__tests__/WalletModal.test.tsx`, `hooks/__tests__/useWallet.test.ts`, `services/terraclassic/__tests__/cosmesPatch127.test.ts` | | Docs / skills | `docs/frontend.md` § WalletConnect same-device, `skills/AGENTS_FRONTEND_WALLETCONNECT_MOBILE.md`, `skills/AGENTS_FRONTEND_WALLET_CONNECT_MODAL.md`, `QA_TEMPLATE.md` § 1.6 | | Verify | `make verify-issue-519` (extend or add `verify-issue-NNN`) | --- ## Recommended direction 1. **Stop the infinite spinner.** Closing Connect Wallet or the pairing sheet must abort/cancel `controller.connect()`, clear `isConnecting`, and restore the header CTA. Add a bounded WC wait (then a retail error + Retry), matching bridge `cancelConnection`. 2. **Pairing sheet must be the foreground UI on mobile WC.** Hide or replace the Connect list while pairing (do not leave two `z-[9999]` dialogs racing). Show **Open {wallet}**, **Open wallet**, **Copy pairing link** as soon as `display_uri` fires; never leave the user on a spinner-only header. 3. **Fix Android deep links.** Verify Lunc Dash `luncdash://` and Galaxy Station Android intent/universal-link templates on Chrome Android 16. Prefer `intent:` / documented vendor schemes that actually open the app; keep Copy pairing link. Add unit tests for **Android UA + Galaxy Station** (today only iOS is covered). 4. **Keplr on mobile Chrome:** when `isWalletConnectMobileClient()` and `window.keplr` is absent, offer **Keplr via WalletConnect** (`WalletName.KEPLR` + `WalletType.WALLETCONNECT` — controller already has `WC_PROJECT_ID`) **or** a primary CTA to open the dApp in the Keplr in-app browser (`keplrwallet:` allowlisted). Do not send Android users to the Chrome Web Store. When `window.keplr` **is** present (in-app browser), keep the existing Extension row (**WC-M7**). 5. **Short in-modal hint** (not a wall of text): same-device wallets live in the phone; use Open / Copy; in-app browser still works. 6. **Cross-link Legal:** after WC connect, if `window.keplr` is missing, do not dump users on a portal that only throws `Keplr extension not found` without a next step. --- ## Acceptance criteria - [ ] Android Chrome (including Android 16): Connect → **Lunc Dash** shows pairing sheet with **Open Lunc Dash** + **Copy pairing link**; after approve in the app and return, header shows `terra1…`. - [ ] Same for **Galaxy Station**. - [ ] Android Chrome: **Keplr** is connectable without a desktop extension (WalletConnect and/or explicit in-app-browser CTA). Install must not be the only path. - [ ] Desktop Chrome: Keplr / Station / Cosmostation extension path unchanged; Lunc Dash / Galaxy Station still QR-only (**WC-M2**). - [ ] In-app Keplr / Lunc Dash / Galaxy Station browsers still connect (**WC-M7**). - [ ] No unbounded spinner: Cancel / close / timeout clears `isConnecting` and re-enables Connect Wallet. - [ ] Pairing hrefs stay on the WC-M5 allowlist; no `javascript:` / arbitrary https from the payload. - [ ] Deep links still require a user tap (**WC-M3**). - [ ] Docs / `QA_TEMPLATE.md` / `AGENTS_FRONTEND_WALLETCONNECT_MOBILE.md` updated; `make verify-issue-NNN` covers new unit tests. --- ## Test plan (functional paths) 1. **Unit — pairing:** existing #519 cases plus Android UA Galaxy Station deep-link shape; Lunc Dash `luncdash://`; `keplrwallet:` / `intent:` allowlist; reject `javascript:` and off-allowlist https. 2. **Unit — connect state:** `isConnecting` true during WC; Cancel/close/timeout sets false and does not persist a half-session; pairing close does not leave header disabled. 3. **Unit — modal:** mobile client + no `window.keplr` shows Keplr WC or in-app CTA, not Install-only; injected `window.keplr` still shows Extension + Ready. 4. **Unit — hook:** Android Chrome UA is `isWalletConnectMobileClient() === true`; desktop UA still false. 5. **Manual Android 16 Chrome (required):** Lunc Dash Open + Copy; Galaxy Station Open + Copy; Keplr WC or in-app CTA; reject pairing in wallet → retail error, spinner stops; dismiss pairing → can retry; airplane mode / WC relay down → timeout, not infinite spin. 6. **Manual desktop:** Lunc Dash QR + second-device scan still works; Keplr extension unchanged. 7. **Manual in-app:** Keplr / Lunc Dash / Galaxy Station browsers still connect and can swap (or at least show address + pass TermsGate when Legal allows). 8. **Regression:** `make verify-issue-519` still green; `WalletModal` #139/#160/#490 tests still green. --- ## Test plan (attack / abuse / hack vectors) | Vector | Expectation | |--------|-------------| | Pairing payload `uri` is `javascript:…` / `data:` / `https://evil.example/wc` | Hook returns false or href stripped; no navigation (**WC-M5**) | | Mutated `android` / `ios` templates pointing off-allowlist | `isAllowedWalletConnectDeepLink` rejects; `<a href>` unset | | User closes pairing then a late WC session attaches | Must not silently connect a session the user cancelled; clear pending connector | | Stale `cl8y_wallet_connection` localStorage for WC | Reconnect must not loop spinner forever; permanent errors still clear storage (`isPermanentError`) | | Clickjacking / stacked modals | Pairing actions must be visible and tappable; backdrop must not intercept Open / Copy | | Clipboard abuse | Copy writes **only** the `wc:` pairing URI, not extra scripts/HTML | | WC project id missing in prod | Existing Vite guard fails the build; do not weaken it | | Deep-link `intent:` extra extras / `S.browser_fallback_url` to attacker site | Stay on allowlisted hosts/schemes only | | Rapid tap Connect on every row while connecting | Rows stay disabled; one session; Cancel recovers | --- ## Verification criteria - New/extended Vitest suite green via `make verify-issue-NNN` (or documented extension of `make verify-issue-519`). - Manual matrix recorded on this issue (device / OS / browser / wallet / path / result): | Device | Browser | Wallet | Result | |--------|---------|--------|--------| | Android 16 phone | Chrome | Lunc Dash Open | address in header | | Android 16 phone | Chrome | Galaxy Station Open or Copy | address in header | | Android 16 phone | Chrome | Keplr (WC or in-app CTA) | address in header | | Android 16 phone | Chrome | dismiss / timeout | spinner clears | | Desktop | Chrome | Keplr extension | unchanged | | Desktop | Chrome | Lunc Dash QR | second-device scan works | | Phone | Keplr in-app browser | Keplr | still works (**WC-M7**) | - Production `https://dex.cl8y.com` after Coolify rebuild (confirm `npm ci` applied `@goblinhunt/cosmes@0.0.71-ghunt.22` + patch). - Legal T&C on mobile Chrome tracked on the Legal issue; DEX must not claim “connect works” if Accept T&C is still `Keplr extension not found` outside in-app Keplr.
PlasticDigits commented 2026-08-18 00:20:44 +00:00 (Migrated from gitlab.com)

marked as related to #519

marked as related to #519
PlasticDigits commented 2026-08-18 00:20:45 +00:00 (Migrated from gitlab.com)

marked as related to #517

marked as related to #517
PlasticDigits commented 2026-08-18 00:21:01 +00:00 (Migrated from gitlab.com)

Same incident, other products (anonymized community report 2026-08-17/18):

Same incident, other products (anonymized community report 2026-08-17/18): - Legal portal T&C (`Keplr extension not found` on mobile Chrome): https://gitlab.com/PlasticDigits/cl8y-ecosystem-legal/-/work_items/9 - Bridge `CONNECT TC` not tappable on Android Chrome: https://gitlab.com/PlasticDigits/cl8y-bridge-monorepo/-/work_items/137
PlasticDigits commented 2026-08-18 00:21:45 +00:00 (Migrated from gitlab.com)

Follow-up: higher-confidence root causes (code)

1. Pairing sheet is behind Connect Wallet (likely “circles circling”)

Both dialogs use Modal at z-[9999] and portal to document.body.

Render order: Layout mounts WalletConnectPairingModal first; WalletButton (under Outlet) mounts WalletModal later. At equal z-index the later portal wins, so Connect Wallet stays on top.

Sequence: tap Lunc Dash / Galaxy Station → isConnecting: true → Connect list shows Connecting… and disables rows; header shows the spinner (on small screens, spinner only). Cosmes then fires display_uri → pairing store opens under that dialog. #519 Open / Copy controls are not tappable. controller.connect() waits forever for WC connect / approval() (v1 Lunc Dash WalletConnectV1 + custom bridge; v2 Galaxy approval()). No timeout.

Vanilla cosmes fallback uses z-index: 10000 and would appear above Connect Wallet; the hook path suppresses that overlay, so production is worse than the fallback for stacking.

Fix direction: on hook open(), close/hide WalletModal (keep isConnecting), or give the pairing portal z-[10001], or mount pairing after WalletModal. Closing pairing / Connect must abort the WC session.

2. Keplr Chrome gap is UI-only — cosmes already has WC

KeplrController is constructed with VITE_WC_PROJECT_ID and already embeds:

  • Android: intent://wcV2#Intent;package=com.chainapsis.keplr;scheme=keplrwallet;end;
  • iOS: keplrwallet://wcV2

connectTerraWallet(WalletName.KEPLR, WalletType.WALLETCONNECT) is unused because WALLET_OPTIONS only lists Keplr as EXTENSION. #519 pairing already allowlists keplrwallet: / that intent. Adding a mobile Keplr WC row reuses the pairing sheet; it still needs the stacking fix or Open Keplr will be hidden the same way.

3. Galaxy Android href shape (secondary, after sheet is visible)

Galaxy android template is https://station.hexxagon.io/wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end; → buildAndroidWalletIntent yields https + #Intent fragment, not intent://. Chrome Android 16 may open the website instead of the app. Unit tests cover Galaxy iOS only.

4. Test gaps to add on the MR

  • Pairing portal is above Connect Wallet when both are open (DOM order / z-index).
  • Android UA + Galaxy Station intent href.
  • Cancel / timeout clears isConnecting.
  • Mobile + no window.keplr offers Keplr WC (or in-app CTA), not Install-only.
## Follow-up: higher-confidence root causes (code) ### 1. Pairing sheet is behind Connect Wallet (likely “circles circling”) Both dialogs use `Modal` at `z-[9999]` and portal to `document.body`. Render order: `Layout` mounts `WalletConnectPairingModal` **first**; `WalletButton` (under `Outlet`) mounts `WalletModal` **later**. At equal z-index the later portal wins, so **Connect Wallet stays on top**. Sequence: tap Lunc Dash / Galaxy Station → `isConnecting: true` → Connect list shows **Connecting…** and disables rows; header shows the spinner (on small screens, spinner only). Cosmes then fires `display_uri` → pairing store opens **under** that dialog. `#519` Open / Copy controls are not tappable. `controller.connect()` waits forever for WC `connect` / `approval()` (v1 Lunc Dash `WalletConnectV1` + custom bridge; v2 Galaxy `approval()`). No timeout. Vanilla cosmes fallback uses `z-index: 10000` and would appear above Connect Wallet; the hook path **suppresses** that overlay, so production is worse than the fallback for stacking. **Fix direction:** on hook `open()`, close/hide `WalletModal` (keep `isConnecting`), **or** give the pairing portal `z-[10001]`, **or** mount pairing after WalletModal. Closing pairing / Connect must abort the WC session. ### 2. Keplr Chrome gap is UI-only — cosmes already has WC `KeplrController` is constructed with `VITE_WC_PROJECT_ID` and already embeds: - Android: `intent://wcV2#Intent;package=com.chainapsis.keplr;scheme=keplrwallet;end;` - iOS: `keplrwallet://wcV2` `connectTerraWallet(WalletName.KEPLR, WalletType.WALLETCONNECT)` is unused because `WALLET_OPTIONS` only lists Keplr as `EXTENSION`. `#519` pairing already allowlists `keplrwallet:` / that intent. Adding a mobile Keplr WC row reuses the pairing sheet; it still needs the stacking fix or Open Keplr will be hidden the same way. ### 3. Galaxy Android href shape (secondary, after sheet is visible) Galaxy `android` template is `https://station.hexxagon.io/wcV2#Intent;package=io.hexxagon.station;scheme=galaxystation;end;` → `buildAndroidWalletIntent` yields **https + `#Intent` fragment**, not `intent://`. Chrome Android 16 may open the website instead of the app. Unit tests cover Galaxy **iOS** only. ### 4. Test gaps to add on the MR - Pairing portal is above Connect Wallet when both are open (DOM order / z-index). - Android UA + Galaxy Station intent href. - Cancel / timeout clears `isConnecting`. - Mobile + no `window.keplr` offers Keplr WC (or in-app CTA), not Install-only.
PlasticDigits commented 2026-08-18 00:25:29 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-08-18 00:25:33 +00:00 (Migrated from gitlab.com)

Working control — ustr-cmm connect succeeded on the same phone

usera (Android 16 Chrome) disconnected and reconnected on https://ust1cmm.com (ustr-cmm). Wallet connect worked (orange indicator, then Accept T&C).

Same session:

  • ustr-cmm (ust1cmm.com) connect: works
  • DEX (dex.cl8y.com) connect: does not (Lunc Dash / Galaxy Station spinner; Keplr extension-only)
  • Bridge (bridge.cl8y.com) connect: does not (cannot tap Connect Terra Wallet)

Use PlasticDigits2/ustr-cmm frontend/ as the reference. T&C failure after that connect is Legal portal window.keplr, not proof that ustr-cmm connect is broken.

**Working control — ustr-cmm connect succeeded on the same phone** usera (Android 16 Chrome) **disconnected and reconnected** on `https://ust1cmm.com` (ustr-cmm). Wallet **connect worked** (orange indicator, then Accept T&C). Same session: - **ustr-cmm (`ust1cmm.com`) connect: works** - **DEX (`dex.cl8y.com`) connect: does not** (Lunc Dash / Galaxy Station spinner; Keplr extension-only) - **Bridge (`bridge.cl8y.com`) connect: does not** (cannot tap Connect Terra Wallet) Use `PlasticDigits2/ustr-cmm` `frontend/` as the reference. T&C failure after that connect is Legal portal `window.keplr`, not proof that ustr-cmm connect is broken.
PlasticDigits commented 2026-08-18 02:02:53 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1079

mentioned in merge request !1079
PlasticDigits commented 2026-08-18 02:05:35 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1080

mentioned in merge request !1080
PlasticDigits commented 2026-08-18 03:00:20 +00:00 (Migrated from gitlab.com)

mentioned in commit 879202a0b1

mentioned in commit 879202a0b1b2ca9fc18085e4ee7d50fdee839749
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-18 03:00:21 +00:00
PlasticDigits commented 2026-08-18 03:11:21 +00:00 (Migrated from gitlab.com)

Merged in !1080 (no automerge, CI not waited — pipeline failed with ci_quota_exceeded, not a test fail).

Shipped vs #554 AC: pairing sheet is no longer covered by Connect Wallet; pending WC can be cancelled/timeout; mobile Chrome without window.keplr offers Keplr via WalletConnect; Galaxy Station Android uses intent://; Legal shows Keplr-browser hint after WC without injected Keplr.

Still open (manual / ops, not blocking the merge):

  • Android 16 Chrome device QA: Lunc Dash, Galaxy Station, Keplr WC, dismiss/timeout, desktop Keplr unchanged, Keplr in-app (WC-M7)
  • Production Coolify rebuild so npm ci applies the cosmes patch

Tracked in the post-merge follow-up after !1080–!1084.

Merged in !1080 (no automerge, CI not waited — pipeline failed with `ci_quota_exceeded`, not a test fail). **Shipped vs #554 AC:** pairing sheet is no longer covered by Connect Wallet; pending WC can be cancelled/timeout; mobile Chrome without `window.keplr` offers Keplr via WalletConnect; Galaxy Station Android uses `intent://`; Legal shows Keplr-browser hint after WC without injected Keplr. **Still open (manual / ops, not blocking the merge):** - Android 16 Chrome device QA: Lunc Dash, Galaxy Station, Keplr WC, dismiss/timeout, desktop Keplr unchanged, Keplr in-app (WC-M7) - Production Coolify rebuild so `npm ci` applies the cosmes patch Tracked in the post-merge follow-up after !1080–!1084.
PlasticDigits commented 2026-08-18 03:12:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #560

mentioned in issue #560
PlasticDigits commented 2026-08-18 03:12:09 +00:00 (Migrated from gitlab.com)

marked as related to #560

marked as related to #560
PlasticDigits commented 2026-08-18 03:12:51 +00:00 (Migrated from gitlab.com)

Post-merge follow-up: #560

Post-merge follow-up: #560
PlasticDigits commented 2026-08-18 15:21:51 +00:00 (Migrated from gitlab.com)

mentioned in issue PlasticDigits2/ustr-cmm#17

mentioned in issue PlasticDigits2/ustr-cmm#17
PlasticDigits commented 2026-08-18 15:22:40 +00:00 (Migrated from gitlab.com)

mentioned in issue #566

mentioned in issue #566
PlasticDigits commented 2026-08-18 15:22:41 +00:00 (Migrated from gitlab.com)

marked as related to #566

marked as related to #566
PlasticDigits commented 2026-08-19 01:06:07 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1090

mentioned in merge request !1090
PlasticDigits commented 2026-08-21 11:32:37 +00:00 (Migrated from gitlab.com)

mentioned in issue #588

mentioned in issue #588
PlasticDigits commented 2026-08-21 11:32:52 +00:00 (Migrated from gitlab.com)

mentioned in issue #511

mentioned in issue #511
PlasticDigits commented 2026-08-25 01:57:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #632

mentioned in issue #632
PlasticDigits commented 2026-08-25 01:57:10 +00:00 (Migrated from gitlab.com)

marked as related to #632

marked as related to #632
PlasticDigits commented 2026-08-26 04:08:00 +00:00 (Migrated from gitlab.com)

mentioned in issue #658

mentioned in issue #658
PlasticDigits commented 2026-08-26 04:08:02 +00:00 (Migrated from gitlab.com)

marked as related to #658

marked as related to #658
PlasticDigits commented 2026-08-26 04:23:24 +00:00 (Migrated from gitlab.com)

mentioned in issue #672

mentioned in issue #672
PlasticDigits commented 2026-08-26 04:23:26 +00:00 (Migrated from gitlab.com)

marked as related to #672

marked as related to #672
PlasticDigits commented 2026-08-26 07:00:44 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1160

mentioned in merge request !1160
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#554
No description provided.