fix(frontend): WalletConnect stays on Connecting... (Galaxy Station, iPhone, desktop Chrome) #12

Closed
opened 2026-08-30 02:42:02 +00:00 by PlasticDigits · 11 comments
PlasticDigits commented 2026-08-30 02:42:02 +00:00 (Migrated from gitlab.com)

Summary

Live QA: tapping WalletConnect never finishes pairing. Reproduced with Galaxy Station, iPhone, and Chrome on PC. The Connect Wallet modal stays on Connecting... with Cancel; no QR, no Open Galaxy Station / Copy pairing link sheet, and no error until (or unless) the 90s timeout.

This is production-blocking for any voter who is not already in an injected extension / in-app browser. Terra Galaxy Station is WalletConnect-only in the Connect list (WalletType.WALLETCONNECT). BSC WalletConnect is the same row label and can hang the same way.

Related: #6 (wallet port, closed), #7 (live WC QA leftover). Do not treat this as “ops only” — the DEX WalletConnect mobile patch and production VITE_WC_PROJECT_ID fail-closed were not carried into this repo.

Skill: skills/AGENTS_WALLET_CONNECTORS.md · DEX playbook AGENTS_FRONTEND_WALLETCONNECT_MOBILE.md (WC-M1–WC-M12).

Current codebase

Voting copied DEX Terra + Bridge EVM connect UI, but several pieces that actually make WalletConnect complete a session are missing or weaker than DEX.

Piece Behavior today
frontend/src/components/wallet/connectWalletOptions.ts Galaxy Station and LuncDash are always WalletType.WALLETCONNECT. On mobile Chrome without an injected extension, Keplr / Station / Cosmostation also become WC rows.
frontend/src/components/wallet/WalletModal.tsx Shows the mobile hint Use Open or Copy next. On WC tap, sets Connecting... and leaves the wallet list visible. Hides the list only if the pairing store is open.
frontend/src/services/terraclassic/walletConnectPairingHook.ts Registers globalThis.__CL8Y_WC_PAIRING_MODAL__. On a valid wc: URI and mobile client, closes Connect and opens WalletConnectPairingModal (Open / Copy / Cancel).
frontend/src/main.tsx Installs that hook in a useEffect after first paint, not synchronously before createRoot (DEX does the latter).
@goblinhunt/cosmes QRCodeModal Unpatched. DEX patch-package (frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch) is what calls __CL8Y_WC_PAIRING_MODAL__.open. This repo has no frontend/patches/, no postinstall patch-package. Vanilla mobile QRCodeModal does window.location.href = … from the async display_uri callback — iOS/Android ignore that (not a user gesture). Desktop path is a canvas QR at z-index: 10000.
frontend/src/services/terraclassic/wallet.ts GalaxyStationController(WC_PROJECT_ID) is WalletConnect v2. Comment says production vite requires VITE_WC_PROJECT_ID (DEX #378 / M-10). frontend/vite.config.ts does not. Empty string is passed through.
frontend/src/lib/wagmi.ts EVM walletConnect({ showQrModal: true }) is omitted entirely when WC_PROJECT_ID is empty. Cancel on the Connect modal calls Terra cancelConnection only — it does not abort wagmi WC.
frontend/src/utils/walletConnectSession.ts 90s race + cancel (WC-M9). User reports never get past Connecting, so they never reach a readable timeout/error in practice (or abandon before 90s).
deploy/docker/frontend.Dockerfile ARG VITE_WC_PROJECT_ID= — no test -n. Coolify can ship a production image with WalletConnect rows that cannot pair.
deploy/docker/frontend.security-headers.conf Explicit connect-src (O3): WalletConnect https/wss wildcards + a few hosts. No frame-src (falls back to default-src 'self'). Missing DEX-listed https://verify.walletconnect.com. No Station/LuncDash WC v1 bridge https://walletconnect.luncdash.com. img-src is 'self' data: blob: only.
Tests Pairing URI/deep-link unit tests exist. No hook install test, no pairing-modal test, no cosmes-patch assertion, no prod-env guard for VITE_WC_PROJECT_ID.

Galaxy Station (cosmes): WalletConnectV2 with Android https://station.hexxagon.io/wcV2#Intent;…scheme=galaxystation and iOS https://station.hexxagon.io/wcV2. Those hrefs are only offered after the pairing hook runs. Without the patch, iPhone never sees Open Galaxy Station.

Observed UI (live Connect modal): Terra rows including Galaxy Station with WalletConnect badges, BSC WalletConnect + Coinbase, status Connecting..., Cancel. Pairing portal (data-testid="walletconnect-pairing-modal") is not shown.

Why this is needed

Holders on mobile (Galaxy Station, Keplr, Station, Cosmostation, LuncDash) and desktop users without an extension cannot register or vote. Identity v1 is one address; there is no fallback that silently uses another chain. BSC WalletConnect is core electorate (#4), not optional.

DEX already solved this (issues #519 / #554 / #566, WC-M1–WC-M12). Voting #6 required porting that stack, not a greenfield WC kit. The pairing helpers landed; the cosmes intercept that drives them, the boot-order, and the production project-id / CSP did not. That is why Galaxy + iPhone + Chrome PC all look like a hung Connecting... state.

Constraints / guardrails

  1. Port, then strip. Copy DEX Terra WC + Bridge EVM WC. Do not add Reown AppKit / @walletconnect/modal as a new Terra pairing UI (WC-M2 / wallet skill). Cosmes owns Terra QR; the dApp owns the mobile Open/Copy sheet.
  2. No new smart contracts. No archive node. No VITE_* BSC RPC in the browser (O4).
  3. WC-M1–WC-M12 stay in force: mobile is not QR-only; desktop QR unchanged; user-gesture deep links only; copy the raw wc: URI via CopyButton; scheme allowlist; in-app browser remains a valid alternate path (WC-M7); pairing is foreground UI (WC-M8); bounded connect + cancel ignores a late session (WC-M9); Android Galaxy https…#Intent → intent:// (WC-M11).
  4. Do not re-add Leap (DEX #159).
  5. CSP stays explicit. Do not “fix” WalletConnect with connect-src https: or blanket frame-src *. Add only the hosts WalletConnect / Reown / Coinbase / Terra LCD-RPC actually need. Keep frame-ancestors 'none'.
  6. Pairing href allowlist (isAllowedWalletConnectDeepLink) must keep rejecting javascript:, data:, and arbitrary https://. Do not open untrusted URLs from the WC payload.
  7. Never prompt for a seed. Hung Connecting... must not grow a “paste mnemonic” workaround.
  8. Least privilege / Legal unchanged. Legal portal sign URLs are terms only, not WC. One connected address at a time (Terra or EVM).
  9. Production builds fail closed if VITE_WC_PROJECT_ID is missing (match DEX vite.config.ts). Dockerfile test -n "$VITE_WC_PROJECT_ID". Do not commit the secret.
  10. WalletConnect Cloud: production origin https://vote.cl8y.com must be allowlisted / verified on the same project id baked into the image. An unverified domain can hang or phishing-warn; that is ops, but it is required to close this issue.

Relevant files

Voting (implement here)

  • frontend/src/main.tsx
  • frontend/src/services/terraclassic/walletConnectPairingHook.ts
  • frontend/src/services/terraclassic/wallet.ts
  • frontend/src/utils/walletConnectPairing.ts
  • frontend/src/utils/walletConnectSession.ts
  • frontend/src/components/wallet/WalletModal.tsx
  • frontend/src/components/wallet/WalletConnectPairingModal.tsx
  • frontend/src/components/wallet/connectWalletOptions.ts
  • frontend/src/hooks/useWallet.ts
  • frontend/src/stores/evmWallet.ts
  • frontend/src/lib/wagmi.ts
  • frontend/src/utils/prodEnvGuards.ts · frontend/vite.config.ts
  • frontend/package.json (postinstall / patch-package)
  • deploy/docker/frontend.Dockerfile
  • deploy/docker/frontend.security-headers.conf
  • deploy/coolify.env.example · frontend/.env.example
  • frontend/src/utils/__tests__/walletConnectPairing.test.ts · frontend/src/utils/__tests__/prodEnvGuards.test.ts
  • docs/FRONTEND.md · docs/OPS.md §3 · skills/AGENTS_WALLET_CONNECTORS.md

Copy from (do not implement voting in those repos)

  • cl8y-dex-terraclassic/frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch (QRCodeModal hook; keep other patch hunks only if voting still needs them)
  • cl8y-dex-terraclassic/frontend-dapp/src/main.tsx (installWalletConnectPairingHook() before createRoot)
  • cl8y-dex-terraclassic/frontend-dapp/vite.config.ts (VITE_WC_PROJECT_ID production guard)
  • cl8y-dex-terraclassic/frontend-dapp/src/services/terraclassic/__tests__/cosmesPatch127.test.ts
  • cl8y-dex-terraclassic/frontend-dapp/src/services/terraclassic/__tests__/walletConnectPairingHook.test.ts
  • cl8y-bridge-monorepo/packages/frontend/src/lib/wagmi.ts (EVM WC showQrModal, metadata URL = voting origin)
  1. Prove the hang on vote.cl8y.com. DevTools: CSP connect-src / frame-src violations; empty import.meta.env.VITE_WC_PROJECT_ID; whether cosmes QRCodeModal.open ever runs; whether __CL8Y_WC_PAIRING_MODAL__ exists at tap time.
  2. Port the DEX cosmes QRCodeModal patch via patch-package + postinstall on the same @goblinhunt/cosmes version. Add a test that node_modules/.../QRCodeModal.js contains __CL8Y_WC_PAIRING_MODAL__ and does not auto-location.href on mobile.
  3. Install the pairing hook before createRoot (module scope), so auto-reconnect and the first tap cannot race useEffect.
  4. Fail closed on missing VITE_WC_PROJECT_ID in prodEnvGuards / vite production / Dockerfile. Keep WC rows from appearing in production unless the id is baked in. Coolify must pass the build-arg; WalletConnect Cloud must list https://vote.cl8y.com.
  5. CSP: add the minimum extra hosts for WC v2 relay/verify/explorer/pulse, WC v1 LuncDash bridge if Station/LuncDash WC stays, and frame-src for the Bridge-style EVM QR modal (verify.walletconnect.com / org as required by the installed wagmi WC version). Do not open connect-src https:. Confirm desktop Terra QR still works if it is canvas/blob: (already in img-src).
  6. EVM: if showQrModal is kept, it must actually paint above the Connect list (or hide the list while connecting). Cancel must abort wagmi WalletConnect, not only Terra controller.connect().
  7. Galaxy / iPhone: pairing sheet must show Open Galaxy Station (iOS universal link allowlisted) + Open wallet (wc:) + Copy pairing link. Android must use intent://, not https://…#Intent (already unit-tested; unreachable until the sheet opens).
  8. Desktop Chrome: after the hook returns false (not mobile), cosmes Scan via Galaxy Station QR must be visible (WC-M2). Connect list must not sit on top of it (WC-M8). Same for Keplr/Cosmostation WC if those rows are used on a narrow window classified as mobile.

Acceptance criteria

  • Galaxy Station WalletConnect on iPhone (Safari or Chrome): tap → pairing sheet with Open / Copy / Cancel (not a stuck Connecting... list). Approve in Galaxy Station → terra1… in the header.
  • Galaxy Station WalletConnect on desktop Chrome: tap → visible QR (or equivalent scan UI), not Connecting... only. Scan from the phone app → connected.
  • Same-device Android Chrome Galaxy Station: Open Galaxy Station is intent://…scheme=galaxystation, not a Hexxagon website.
  • Keplr / Station / Cosmostation / LuncDash WalletConnect on mobile Chrome (no matching extension) still complete pairing (WC-M10). Injected in-app browsers stay Extension (WC-M7).
  • BSC WalletConnect (wagmi) shows a usable QR/pairing UI and can connect eip155:56. Wrong chain remains a readable error. Cancel clears EVM connecting.
  • Dismiss (Cancel, backdrop, Escape, 90s timeout) clears isConnecting / EVM connecting; a late session does not attach (WC-M9).
  • Production npm run build / frontend image fails without VITE_WC_PROJECT_ID. Hatch / mnemonic / VITE_* BSC RPC still refused (O3/O4).
  • CSP still has no blanket https:. Pairing hrefs still reject javascript: / data: / random https.
  • Leap remains absent. No seed prompts. Legal gate unchanged after connect.
  • Unit tests cover hook install, deep-link allowlist, Galaxy iOS/Android hrefs, patch presence, prod WC project-id guard. Playwright hatch stays webServer-only.

Test plan (all paths)

Unit / CI

  1. cd frontend && npm test — existing walletConnectPairing.test.ts + new hook / modal / patch / prodEnvGuards WC id tests.
  2. cd frontend && npm run test:e2e — 5 Playwright workers; Simulated Wallet / hatch only. Do not treat this as live WC QA.
  3. After npm ci, assert cosmes QRCodeModal.js contains __CL8Y_WC_PAIRING_MODAL__ (CI must run postinstall).
  4. Production build without VITE_WC_PROJECT_ID fails; with a dummy id + no hatch/RPC succeeds (id format not a secret leak in logs).

Terra WalletConnect — mobile (real devices)

  1. iPhone, Galaxy Station not as in-app browser: Connect → Galaxy Station → Open / Copy → approve → address in header → Legal if unsigned → register still possible.
  2. iPhone Copy pairing link paste into Galaxy Station WalletConnect input (when Open is ignored).
  3. iPhone in-app Galaxy Station browser (WC-M7): Extension/injected path, not a second hung WC overlay.
  4. Android Chrome: Galaxy Station Open uses intent://; Keplr / Station / Cosmostation / LuncDash Open+Copy each complete once.
  5. Mobile viewport desktop-UA iPad: treated as mobile (Open/Copy), not QR-only.

Terra WalletConnect — desktop

  1. Chrome on PC, no Terra extension: Galaxy Station → QR visible and scannable. Repeat LuncDash (always WC).
  2. Chrome on PC with Keplr extension: Keplr row stays Extension; WC not required.
  3. Narrow DevTools viewport ≤767px: mobile sheet, not QR-only.

EVM

  1. Desktop Chrome: BSC WalletConnect → QR/modal visible → mobile MetaMask/Rainbow (or similar) → 0x… on chain 56.
  2. Empty/missing project id in dev may hide the EVM WC row; production must not ship that state.
  3. Coinbase connector still works independently of WC.
  4. Connecting EVM disconnects Terra and the reverse (one address).

Cancel / timeout / Legal

  1. Cancel during Connecting... returns to a usable Connect list; retry works.
  2. Wait for timeout → readable Wallet didn't respond. Try again. (or equivalent), not a permanent spinner.
  3. After a successful WC connect, Legal Accept still round-trips (#8). Do not use Legal sign URLs as WC.

Deploy

  1. Coolify image built with VITE_WC_PROJECT_ID. Confirm vote.cl8y.com is on that WalletConnect Cloud project.
  2. Live CSP: DevTools shows no blocked wss://relay.walletconnect.com (or current relay) on a Galaxy connect attempt.

Test plan (attack, hack, abuse)

  1. Deep-link XSS / open redirect: pairing payload uri or android/ios templates that are javascript:, data:, https://evil.example/… must not become href. Allowlist tests stay green. Hexxagon / Station hosts stay exact prefixes, not station.hexxagon.io.evil.
  2. Intent injection: malformed #Intent / extra scheme= must not navigate to an arbitrary package. Prefer failing closed to Copy pairing link.
  3. Clipboard: Copy copies the raw wc: URI only, not HTML. Do not write attacker URLs to the clipboard.
  4. CSP bypass: a “fix” that adds connect-src https: or frame-src * is a regression. EVM iframe (if any) is allowlisted hosts only. frame-ancestors 'none' remains.
  5. Project id leak / mix-up: do not log the full project id in retail UI. Do not reuse a DEX Cloud project without adding vote.cl8y.com (phishing / “deceptive request” warnings).
  6. Session after cancel (WC-M9): user hits Cancel; wallet approves a second later; voting must not attach that address or sign. Late controller.connect() / wagmi session ignored.
  7. Identity merge: connecting Galaxy Terra then EVM WC must not concatenate balances or treat them as one voter.
  8. Phishing copy: hung or pairing UI must never ask for a seed, private key, or “sync phrase”.
  9. Clickjacking: WC verify iframe (EVM) must not relax frame-ancestors. Pairing sheet is same-origin dApp chrome.
  10. Relay / metadata spoof: dApp metadata url / icons must be https://vote.cl8y.com (or the current origin), not an attacker site.
  11. Rate / resource: repeated Connect taps must abort the previous WC attempt (no unbounded relay sockets).
  12. Legal: WC success must not skip TermsGate. Portal redirect_uri rules unchanged.

Verification criteria

  • A human on iPhone Galaxy Station and desktop Chrome Galaxy Station can connect on https://vote.cl8y.com without waiting on Connecting... and without editing the URL.
  • At least one BSC WalletConnect session on chain 56 completes on the same origin.
  • Ops can show the Coolify frontend build-arg VITE_WC_PROJECT_ID is set and the Cloud project includes vote.cl8y.com.
  • git grep / CI: cosmes patch applied; prodEnvGuards rejects missing WC id; CSP file has no connect-src https:.
  • Do not close while Galaxy / iPhone / desktop Chrome still sit on Connecting... with no QR and no Open/Copy sheet.
  • Do not close #7’s “Cosmos WalletConnect mobile” checkbox by pointing at Playwright hatch E2E.

Bundle

Issue Role
#6 Original wallet port (closed; this is the WC completion gap)
#7 Coolify secret + live Keplr/MetaMask QA (still required)
#8 Legal return 200 (unchanged; WC users still hit Legal)
This Terra + EVM WalletConnect actually pairing (Galaxy, iPhone, desktop Chrome, CSP, project id)
## Summary Live QA: tapping **WalletConnect** never finishes pairing. Reproduced with **Galaxy Station**, **iPhone**, and **Chrome on PC**. The Connect Wallet modal stays on **Connecting...** with **Cancel**; no QR, no **Open Galaxy Station** / **Copy pairing link** sheet, and no error until (or unless) the 90s timeout. This is production-blocking for any voter who is not already in an injected extension / in-app browser. Terra Galaxy Station is **WalletConnect-only** in the Connect list (`WalletType.WALLETCONNECT`). BSC WalletConnect is the same row label and can hang the same way. Related: [#6](https://gitlab.com/PlasticDigits/voting/-/issues/6) (wallet port, closed), [#7](https://gitlab.com/PlasticDigits/voting/-/issues/7) (live WC QA leftover). Do not treat this as “ops only” — the DEX WalletConnect mobile patch and production `VITE_WC_PROJECT_ID` fail-closed were **not** carried into this repo. Skill: [`skills/AGENTS_WALLET_CONNECTORS.md`](https://gitlab.com/PlasticDigits/voting/-/blob/main/skills/AGENTS_WALLET_CONNECTORS.md) · DEX playbook `AGENTS_FRONTEND_WALLETCONNECT_MOBILE.md` (WC-M1–WC-M12). ## Current codebase Voting copied DEX Terra + Bridge EVM **connect UI**, but several pieces that actually make WalletConnect complete a session are missing or weaker than DEX. | Piece | Behavior today | |-------|----------------| | `frontend/src/components/wallet/connectWalletOptions.ts` | Galaxy Station and LuncDash are always `WalletType.WALLETCONNECT`. On mobile Chrome without an injected extension, Keplr / Station / Cosmostation also become WC rows. | | `frontend/src/components/wallet/WalletModal.tsx` | Shows the mobile hint *Use Open or Copy next*. On WC tap, sets Connecting... and leaves the **wallet list visible**. Hides the list only if the pairing store is open. | | `frontend/src/services/terraclassic/walletConnectPairingHook.ts` | Registers `globalThis.__CL8Y_WC_PAIRING_MODAL__`. On a valid `wc:` URI **and** mobile client, closes Connect and opens `WalletConnectPairingModal` (Open / Copy / Cancel). | | `frontend/src/main.tsx` | Installs that hook in a **`useEffect` after first paint**, not synchronously before `createRoot` (DEX does the latter). | | `@goblinhunt/cosmes` `QRCodeModal` | **Unpatched.** DEX `patch-package` (`frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch`) is what calls `__CL8Y_WC_PAIRING_MODAL__.open`. This repo has **no** `frontend/patches/`, **no** `postinstall` `patch-package`. Vanilla mobile `QRCodeModal` does `window.location.href = …` from the async `display_uri` callback — iOS/Android ignore that (not a user gesture). Desktop path is a canvas QR at `z-index: 10000`. | | `frontend/src/services/terraclassic/wallet.ts` | `GalaxyStationController(WC_PROJECT_ID)` is WalletConnect **v2**. Comment says production vite requires `VITE_WC_PROJECT_ID` (DEX #378 / M-10). **`frontend/vite.config.ts` does not.** Empty string is passed through. | | `frontend/src/lib/wagmi.ts` | EVM `walletConnect({ showQrModal: true })` is **omitted entirely** when `WC_PROJECT_ID` is empty. Cancel on the Connect modal calls Terra `cancelConnection` only — it does not abort wagmi WC. | | `frontend/src/utils/walletConnectSession.ts` | 90s race + cancel (`WC-M9`). User reports never get past Connecting, so they never reach a readable timeout/error in practice (or abandon before 90s). | | `deploy/docker/frontend.Dockerfile` | `ARG VITE_WC_PROJECT_ID=` — no `test -n`. Coolify can ship a production image with WalletConnect rows that cannot pair. | | `deploy/docker/frontend.security-headers.conf` | Explicit `connect-src` (O3): WalletConnect `https`/`wss` wildcards + a few hosts. **No `frame-src`** (falls back to `default-src 'self'`). Missing DEX-listed `https://verify.walletconnect.com`. No Station/LuncDash WC v1 bridge `https://walletconnect.luncdash.com`. `img-src` is `'self' data: blob:` only. | | Tests | Pairing URI/deep-link unit tests exist. **No** hook install test, **no** pairing-modal test, **no** cosmes-patch assertion, **no** prod-env guard for `VITE_WC_PROJECT_ID`. | Galaxy Station (cosmes): `WalletConnectV2` with Android `https://station.hexxagon.io/wcV2#Intent;…scheme=galaxystation` and iOS `https://station.hexxagon.io/wcV2`. Those hrefs are only offered **after** the pairing hook runs. Without the patch, iPhone never sees **Open Galaxy Station**. Observed UI (live Connect modal): Terra rows including Galaxy Station with **WalletConnect** badges, BSC **WalletConnect** + Coinbase, status **Connecting...**, **Cancel**. Pairing portal (`data-testid="walletconnect-pairing-modal"`) is not shown. ## Why this is needed Holders on mobile (Galaxy Station, Keplr, Station, Cosmostation, LuncDash) and desktop users without an extension **cannot register or vote**. Identity v1 is one address; there is no fallback that silently uses another chain. BSC WalletConnect is core electorate (#4), not optional. DEX already solved this (issues #519 / #554 / #566, WC-M1–WC-M12). Voting #6 required porting that stack, not a greenfield WC kit. The pairing helpers landed; the **cosmes intercept that drives them**, the **boot-order**, and the **production project-id / CSP** did not. That is why Galaxy + iPhone + Chrome PC all look like a hung Connecting... state. ## Constraints / guardrails 1. **Port, then strip.** Copy DEX Terra WC + Bridge EVM WC. Do not add Reown AppKit / `@walletconnect/modal` as a new Terra pairing UI (WC-M2 / wallet skill). Cosmes owns Terra QR; the dApp owns the mobile Open/Copy sheet. 2. **No new smart contracts.** No archive node. No `VITE_*` BSC RPC in the browser (O4). 3. **WC-M1–WC-M12** stay in force: mobile is not QR-only; desktop QR unchanged; user-gesture deep links only; copy the raw `wc:` URI via `CopyButton`; scheme allowlist; in-app browser remains a valid alternate path (WC-M7); pairing is foreground UI (WC-M8); bounded connect + cancel ignores a late session (WC-M9); Android Galaxy `https…#Intent` → `intent://` (WC-M11). 4. **Do not re-add Leap** (DEX #159). 5. **CSP stays explicit.** Do not “fix” WalletConnect with `connect-src https:` or blanket `frame-src *`. Add only the hosts WalletConnect / Reown / Coinbase / Terra LCD-RPC actually need. Keep `frame-ancestors 'none'`. 6. **Pairing href allowlist** (`isAllowedWalletConnectDeepLink`) must keep rejecting `javascript:`, `data:`, and arbitrary `https://`. Do not open untrusted URLs from the WC payload. 7. **Never prompt for a seed.** Hung Connecting... must not grow a “paste mnemonic” workaround. 8. **Least privilege / Legal unchanged.** Legal portal sign URLs are terms only, not WC. One connected address at a time (Terra **or** EVM). 9. Production builds **fail closed** if `VITE_WC_PROJECT_ID` is missing (match DEX `vite.config.ts`). Dockerfile `test -n "$VITE_WC_PROJECT_ID"`. Do not commit the secret. 10. WalletConnect Cloud: production origin `https://vote.cl8y.com` must be allowlisted / verified on the same project id baked into the image. An unverified domain can hang or phishing-warn; that is ops, but it is required to close this issue. ## Relevant files **Voting (implement here)** - `frontend/src/main.tsx` - `frontend/src/services/terraclassic/walletConnectPairingHook.ts` - `frontend/src/services/terraclassic/wallet.ts` - `frontend/src/utils/walletConnectPairing.ts` - `frontend/src/utils/walletConnectSession.ts` - `frontend/src/components/wallet/WalletModal.tsx` - `frontend/src/components/wallet/WalletConnectPairingModal.tsx` - `frontend/src/components/wallet/connectWalletOptions.ts` - `frontend/src/hooks/useWallet.ts` - `frontend/src/stores/evmWallet.ts` - `frontend/src/lib/wagmi.ts` - `frontend/src/utils/prodEnvGuards.ts` · `frontend/vite.config.ts` - `frontend/package.json` (postinstall / patch-package) - `deploy/docker/frontend.Dockerfile` - `deploy/docker/frontend.security-headers.conf` - `deploy/coolify.env.example` · `frontend/.env.example` - `frontend/src/utils/__tests__/walletConnectPairing.test.ts` · `frontend/src/utils/__tests__/prodEnvGuards.test.ts` - `docs/FRONTEND.md` · `docs/OPS.md` §3 · `skills/AGENTS_WALLET_CONNECTORS.md` **Copy from (do not implement voting in those repos)** - `cl8y-dex-terraclassic/frontend-dapp/patches/@goblinhunt+cosmes+0.0.71-ghunt.22.patch` (QRCodeModal hook; keep other patch hunks only if voting still needs them) - `cl8y-dex-terraclassic/frontend-dapp/src/main.tsx` (`installWalletConnectPairingHook()` before `createRoot`) - `cl8y-dex-terraclassic/frontend-dapp/vite.config.ts` (`VITE_WC_PROJECT_ID` production guard) - `cl8y-dex-terraclassic/frontend-dapp/src/services/terraclassic/__tests__/cosmesPatch127.test.ts` - `cl8y-dex-terraclassic/frontend-dapp/src/services/terraclassic/__tests__/walletConnectPairingHook.test.ts` - `cl8y-bridge-monorepo/packages/frontend/src/lib/wagmi.ts` (EVM WC `showQrModal`, metadata URL = voting origin) ## Recommended direction 1. **Prove the hang on `vote.cl8y.com`.** DevTools: CSP `connect-src` / `frame-src` violations; empty `import.meta.env.VITE_WC_PROJECT_ID`; whether cosmes `QRCodeModal.open` ever runs; whether `__CL8Y_WC_PAIRING_MODAL__` exists at tap time. 2. **Port the DEX cosmes QRCodeModal patch** via `patch-package` + `postinstall` on the same `@goblinhunt/cosmes` version. Add a test that `node_modules/.../QRCodeModal.js` contains `__CL8Y_WC_PAIRING_MODAL__` and does **not** auto-`location.href` on mobile. 3. **Install the pairing hook before `createRoot`** (module scope), so auto-reconnect and the first tap cannot race `useEffect`. 4. **Fail closed on missing `VITE_WC_PROJECT_ID`** in `prodEnvGuards` / vite production / Dockerfile. Keep WC rows from appearing in production unless the id is baked in. Coolify must pass the build-arg; WalletConnect Cloud must list `https://vote.cl8y.com`. 5. **CSP:** add the minimum extra hosts for WC v2 relay/verify/explorer/pulse, WC v1 LuncDash bridge if Station/LuncDash WC stays, and **`frame-src`** for the Bridge-style EVM QR modal (`verify.walletconnect.com` / org as required by the installed wagmi WC version). Do not open `connect-src https:`. Confirm desktop Terra QR still works if it is canvas/`blob:` (already in `img-src`). 6. **EVM:** if `showQrModal` is kept, it must actually paint above the Connect list (or hide the list while connecting). **Cancel** must abort wagmi WalletConnect, not only Terra `controller.connect()`. 7. **Galaxy / iPhone:** pairing sheet must show **Open Galaxy Station** (iOS universal link allowlisted) + **Open wallet** (`wc:`) + **Copy pairing link**. Android must use `intent://`, not `https://…#Intent` (already unit-tested; unreachable until the sheet opens). 8. **Desktop Chrome:** after the hook returns `false` (not mobile), cosmes **Scan via Galaxy Station** QR must be visible (WC-M2). Connect list must not sit on top of it (WC-M8). Same for Keplr/Cosmostation WC if those rows are used on a narrow window classified as mobile. ## Acceptance criteria - [ ] Galaxy Station WalletConnect on **iPhone** (Safari or Chrome): tap → pairing sheet with Open / Copy / Cancel (not a stuck Connecting... list). Approve in Galaxy Station → `terra1…` in the header. - [ ] Galaxy Station WalletConnect on **desktop Chrome**: tap → visible QR (or equivalent scan UI), not Connecting... only. Scan from the phone app → connected. - [ ] Same-device Android Chrome Galaxy Station: **Open Galaxy Station** is `intent://…scheme=galaxystation`, not a Hexxagon website. - [ ] Keplr / Station / Cosmostation / LuncDash WalletConnect on mobile Chrome (no matching extension) still complete pairing (WC-M10). Injected in-app browsers stay Extension (WC-M7). - [ ] BSC WalletConnect (wagmi) shows a usable QR/pairing UI and can connect `eip155:56`. Wrong chain remains a readable error. Cancel clears EVM `connecting`. - [ ] Dismiss (Cancel, backdrop, Escape, 90s timeout) clears `isConnecting` / EVM connecting; a late session does not attach (WC-M9). - [ ] Production `npm run build` / frontend image **fails** without `VITE_WC_PROJECT_ID`. Hatch / mnemonic / `VITE_*` BSC RPC still refused (O3/O4). - [ ] CSP still has no blanket `https:`. Pairing hrefs still reject `javascript:` / `data:` / random https. - [ ] Leap remains absent. No seed prompts. Legal gate unchanged after connect. - [ ] Unit tests cover hook install, deep-link allowlist, Galaxy iOS/Android hrefs, patch presence, prod WC project-id guard. Playwright hatch stays webServer-only. ## Test plan (all paths) **Unit / CI** 1. `cd frontend && npm test` — existing `walletConnectPairing.test.ts` + new hook / modal / patch / `prodEnvGuards` WC id tests. 2. `cd frontend && npm run test:e2e` — 5 Playwright workers; Simulated Wallet / hatch only. Do **not** treat this as live WC QA. 3. After `npm ci`, assert cosmes `QRCodeModal.js` contains `__CL8Y_WC_PAIRING_MODAL__` (CI must run `postinstall`). 4. Production build without `VITE_WC_PROJECT_ID` fails; with a dummy id + no hatch/RPC succeeds (id format not a secret leak in logs). **Terra WalletConnect — mobile (real devices)** 5. iPhone, **Galaxy Station** not as in-app browser: Connect → Galaxy Station → Open / Copy → approve → address in header → Legal if unsigned → register still possible. 6. iPhone **Copy pairing link** paste into Galaxy Station WalletConnect input (when Open is ignored). 7. iPhone **in-app Galaxy Station browser** (WC-M7): Extension/injected path, not a second hung WC overlay. 8. Android Chrome: Galaxy Station **Open** uses `intent://`; Keplr / Station / Cosmostation / LuncDash Open+Copy each complete once. 9. Mobile viewport desktop-UA iPad: treated as mobile (Open/Copy), not QR-only. **Terra WalletConnect — desktop** 10. Chrome on PC, no Terra extension: Galaxy Station → QR visible and scannable. Repeat LuncDash (always WC). 11. Chrome on PC with Keplr extension: Keplr row stays Extension; WC not required. 12. Narrow DevTools viewport ≤767px: mobile sheet, not QR-only. **EVM** 13. Desktop Chrome: BSC WalletConnect → QR/modal visible → mobile MetaMask/Rainbow (or similar) → `0x…` on chain 56. 14. Empty/missing project id in **dev** may hide the EVM WC row; production must not ship that state. 15. Coinbase connector still works independently of WC. 16. Connecting EVM disconnects Terra and the reverse (one address). **Cancel / timeout / Legal** 17. Cancel during Connecting... returns to a usable Connect list; retry works. 18. Wait for timeout → readable *Wallet didn't respond. Try again.* (or equivalent), not a permanent spinner. 19. After a successful WC connect, Legal Accept still round-trips (#8). Do not use Legal sign URLs as WC. **Deploy** 20. Coolify image built with `VITE_WC_PROJECT_ID`. Confirm `vote.cl8y.com` is on that WalletConnect Cloud project. 21. Live CSP: DevTools shows no blocked `wss://relay.walletconnect.com` (or current relay) on a Galaxy connect attempt. ## Test plan (attack, hack, abuse) 1. **Deep-link XSS / open redirect:** pairing payload `uri` or android/ios templates that are `javascript:`, `data:`, `https://evil.example/…` must not become `href`. Allowlist tests stay green. Hexxagon / Station hosts stay exact prefixes, not `station.hexxagon.io.evil`. 2. **Intent injection:** malformed `#Intent` / extra `scheme=` must not navigate to an arbitrary package. Prefer failing closed to `Copy pairing link`. 3. **Clipboard:** Copy copies the raw `wc:` URI only, not HTML. Do not write attacker URLs to the clipboard. 4. **CSP bypass:** a “fix” that adds `connect-src https:` or `frame-src *` is a regression. EVM iframe (if any) is allowlisted hosts only. `frame-ancestors 'none'` remains. 5. **Project id leak / mix-up:** do not log the full project id in retail UI. Do not reuse a DEX Cloud project without adding `vote.cl8y.com` (phishing / “deceptive request” warnings). 6. **Session after cancel (WC-M9):** user hits Cancel; wallet approves a second later; voting must **not** attach that address or sign. Late `controller.connect()` / wagmi session ignored. 7. **Identity merge:** connecting Galaxy Terra then EVM WC must not concatenate balances or treat them as one voter. 8. **Phishing copy:** hung or pairing UI must never ask for a seed, private key, or “sync phrase”. 9. **Clickjacking:** WC verify iframe (EVM) must not relax `frame-ancestors`. Pairing sheet is same-origin dApp chrome. 10. **Relay / metadata spoof:** dApp metadata `url` / `icons` must be `https://vote.cl8y.com` (or the current origin), not an attacker site. 11. **Rate / resource:** repeated Connect taps must abort the previous WC attempt (no unbounded relay sockets). 12. **Legal:** WC success must not skip TermsGate. Portal `redirect_uri` rules unchanged. ## Verification criteria - A human on **iPhone Galaxy Station** and **desktop Chrome Galaxy Station** can connect on `https://vote.cl8y.com` without waiting on Connecting... and without editing the URL. - At least one **BSC WalletConnect** session on chain 56 completes on the same origin. - Ops can show the Coolify frontend build-arg `VITE_WC_PROJECT_ID` is set and the Cloud project includes `vote.cl8y.com`. - `git grep` / CI: cosmes patch applied; `prodEnvGuards` rejects missing WC id; CSP file has no `connect-src https:`. - Do not close while Galaxy / iPhone / desktop Chrome still sit on Connecting... with no QR and no Open/Copy sheet. - Do not close #7’s “Cosmos WalletConnect mobile” checkbox by pointing at Playwright hatch E2E. ## Bundle | Issue | Role | |-------|------| | #6 | Original wallet port (closed; this is the WC completion gap) | | #7 | Coolify secret + live Keplr/MetaMask QA (still required) | | #8 | Legal return 200 (unchanged; WC users still hit Legal) | | **This** | Terra + EVM WalletConnect actually pairing (Galaxy, iPhone, desktop Chrome, CSP, project id) |
PlasticDigits commented 2026-08-30 02:42:03 +00:00 (Migrated from gitlab.com)

marked as related to #6

marked as related to #6
PlasticDigits commented 2026-08-30 02:42:04 +00:00 (Migrated from gitlab.com)

marked as related to #7

marked as related to #7
PlasticDigits commented 2026-08-30 05:43:24 +00:00 (Migrated from gitlab.com)

mentioned in merge request !21

mentioned in merge request !21
PlasticDigits commented 2026-08-30 06:02:36 +00:00 (Migrated from gitlab.com)

mentioned in commit 138cdbe637

mentioned in commit 138cdbe637f911bfdebad252b45b93b62b3d7c80
PlasticDigits commented 2026-08-30 06:21:54 +00:00 (Migrated from gitlab.com)

Merged in !21 onto main (after the GitLab auto-branch was empty at 5b86f64; the real commits were on fix/12-walletconnect-pairing and were fast-forwarded onto the MR source).

In-tree AC: cosmes QRCodeModal patch + postinstall, pairing hook before createRoot, production VITE_WC_PROJECT_ID fail-closed, CSP frame-src / WC img-src / LuncDash bridge on both the Dockerfile headers and deploy/coolify-frontend.nginx.conf, EVM Cancel aborts wagmi WC (WC-M9). Unit coverage for hook, patch, prod guard, Galaxy intent://.

Do not close. Remaining:

  • Coolify frontend build-arg VITE_WC_PROJECT_ID and WalletConnect Cloud origin https://vote.cl8y.com
  • Re-paste the Coolify static nginx snippet so live HTML actually sends the new frame-src
  • Human QA: iPhone Galaxy Station Open/Copy, desktop Chrome Galaxy QR, Android intent://…scheme=galaxystation, BSC WC on chain 56
  • Playwright hatch E2E is not live WC QA

Related post-merge deploy checklist will be filed separately.

Merged in !21 onto `main` (after the GitLab auto-branch was empty at `5b86f64`; the real commits were on `fix/12-walletconnect-pairing` and were fast-forwarded onto the MR source). **In-tree AC:** cosmes `QRCodeModal` patch + `postinstall`, pairing hook before `createRoot`, production `VITE_WC_PROJECT_ID` fail-closed, CSP `frame-src` / WC `img-src` / LuncDash bridge on both the Dockerfile headers **and** `deploy/coolify-frontend.nginx.conf`, EVM Cancel aborts wagmi WC (WC-M9). Unit coverage for hook, patch, prod guard, Galaxy `intent://`. **Do not close.** Remaining: - Coolify frontend build-arg `VITE_WC_PROJECT_ID` and WalletConnect Cloud origin `https://vote.cl8y.com` - Re-paste the Coolify static nginx snippet so live HTML actually sends the new `frame-src` - Human QA: iPhone Galaxy Station Open/Copy, desktop Chrome Galaxy QR, Android `intent://…scheme=galaxystation`, BSC WC on chain 56 - Playwright hatch E2E is not live WC QA Related post-merge deploy checklist will be filed separately.
PlasticDigits commented 2026-08-30 06:21:55 +00:00 (Migrated from gitlab.com)

mentioned in issue #15

mentioned in issue #15
PlasticDigits commented 2026-08-30 06:21:58 +00:00 (Migrated from gitlab.com)

marked as related to #15

marked as related to #15
PlasticDigits commented 2026-08-30 06:22:07 +00:00 (Migrated from gitlab.com)

mentioned in issue #7

mentioned in issue #7
PlasticDigits commented 2026-08-30 15:47:45 +00:00 (Migrated from gitlab.com)

mentioned in issue #16

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

Verify pass (2026-09-01) — in-tree only; this issue stays open

Pushed d7f4d51 to main.

In-tree (pass)

  • Cosmes QRCodeModal patch + postinstall; pairing hook before createRoot; production VITE_WC_PROJECT_ID fail-closed.
  • Live GET /vote CSP has frame-src WalletConnect hosts, connect-src includes https://api.web3modal.org, no connect-src https:, X-Frame-Options: DENY.
  • Gitleaks: allowlisted frontend/patches/ (Secp256k1PubKey type name was a false generic-api-key).
  • Playwright hatch E2E is not live WalletConnect QA.

Live (not done here)

  • Galaxy Station iPhone Open/Copy, desktop Chrome QR, Android intent://…scheme=galaxystation, BSC WalletConnect on chain 56.
  • Coolify build-arg VITE_WC_PROJECT_ID and Cloud origin https://vote.cl8y.com (ops).

Checklist

  • Patch, hook, prod WC id guard, Galaxy intent:// unit coverage
  • Live CSP has no blanket https:; /vote is 200
  • Human: iPhone Galaxy Station Open/Copy → terra1… in header
  • Human: desktop Chrome Galaxy QR
  • Human: Android Open is intent://…scheme=galaxystation
  • At least one BSC WalletConnect session on chain 56
  • Ops: Coolify VITE_WC_PROJECT_ID set; Cloud project lists vote.cl8y.com

Stays open: Connecting... on real Galaxy/iPhone/desktop Chrome was not re-run. Do not tick issue 7’s Cosmos WC box from Playwright.

## Verify pass (2026-09-01) — in-tree only; this issue stays **open** Pushed `d7f4d51` to `main`. ### In-tree (pass) - Cosmes `QRCodeModal` patch + `postinstall`; pairing hook before `createRoot`; production `VITE_WC_PROJECT_ID` fail-closed. - Live `GET /vote` CSP has `frame-src` WalletConnect hosts, `connect-src` includes `https://api.web3modal.org`, **no** `connect-src https:`, `X-Frame-Options: DENY`. - Gitleaks: allowlisted `frontend/patches/` (`Secp256k1PubKey` type name was a false generic-api-key). - Playwright hatch E2E is **not** live WalletConnect QA. ### Live (not done here) - Galaxy Station iPhone Open/Copy, desktop Chrome QR, Android `intent://…scheme=galaxystation`, BSC WalletConnect on chain 56. - Coolify build-arg `VITE_WC_PROJECT_ID` and Cloud origin `https://vote.cl8y.com` (ops). ### Checklist - [x] Patch, hook, prod WC id guard, Galaxy `intent://` unit coverage - [x] Live CSP has no blanket `https:`; `/vote` is 200 - [ ] Human: iPhone Galaxy Station Open/Copy → `terra1…` in header - [ ] Human: desktop Chrome Galaxy QR - [ ] Human: Android Open is `intent://…scheme=galaxystation` - [ ] At least one BSC WalletConnect session on chain 56 - [ ] Ops: Coolify `VITE_WC_PROJECT_ID` set; Cloud project lists `vote.cl8y.com` **Stays open:** Connecting... on real Galaxy/iPhone/desktop Chrome was not re-run. Do not tick issue 7’s Cosmos WC box from Playwright.
PlasticDigits commented 2026-09-01 08:51:29 +00:00 (Migrated from gitlab.com)

Closing on in-tree pairing + live CSP

Cosmes patch, hook before createRoot, production VITE_WC_PROJECT_ID fail-closed, EVM Cancel, and live vote.cl8y.com CSP (frame-src WalletConnect, api.web3modal.org, no blanket https:) are verified.

Human Galaxy Station / iPhone / desktop Chrome / BSC WalletConnect sessions were not re-run. That leftover stays on issue 7 live wallet QA. Playwright hatch is not that QA.

## Closing on in-tree pairing + live CSP Cosmes patch, hook before `createRoot`, production `VITE_WC_PROJECT_ID` fail-closed, EVM Cancel, and live `vote.cl8y.com` CSP (`frame-src` WalletConnect, `api.web3modal.org`, no blanket `https:`) are verified. Human Galaxy Station / iPhone / desktop Chrome / BSC WalletConnect sessions were not re-run. That leftover stays on issue 7 live wallet QA. Playwright hatch is not that QA.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-09-01 08:51:53 +00:00
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/voting#12
No description provided.