Commit 5e4dfb8
authored
* feat(frontend+backend): WCAG 2.2 AA audit (#272), WalletConnect Stellar bridge (#273), privacy-safe Sentry + web-vitals telemetry (#277)
Closes #272, #273, #277 as assigned to @Hicent2 in the Official Campaign | FWC26.
Issue #272 — WCAG 2.2 AA audit:
- globals.css adds a global :focus-visible ring using the brand sky color and provides skip-link / sr-only utilities.
- Button.jsx, Form.jsx, Withdraw.jsx, ActionModal.jsx, TokenSelector.jsx, BalanceCards.jsx, MultiplierSelector.jsx were upgraded for proper label associations, aria-invalid/aria-describedby, role=alert announcements, role=radiogroup + arrow-key navigation, role=slider with aria-valuemin/max/now/text, dialog focus trap with Esc-to-close and focus restoration, definition-list semantics, and non-color error icons.
- Withdraw.jsx additionally fixes a class= -> className= JSX bug that prevented the Withdraw button from rendering its class.
Issue #273 — Mobile wallet flow (WalletConnect for Stellar):
- frontend/src/services/walletconnect.js mirrors the Freighter API (connectWallet, getWalletPublicKey, signStellarTransaction) but pairs via standard wc: URI + QR rendered by the qrcode package.
- frontend/src/components/wallet/WalletConnectModal.jsx provides the accessible QR modal with auto-generation on open, AbortController cleanup on unmount, and disconnectWallet() fired on close after a successful pairing.
- web_app/api/walletconnect.py is a new FastAPI router with /pair, /poll/{sid}, /sign, /complete/{sid}, and /session/{sid}. All endpoints use Redis setex with a 5-minute TTL mirroring the existing session.py pattern. /complete validates Stellar StrKey shape (via StrKey.isValidEd25519PublicKey) and rejects XDR envelopes whose source account does not match the claimed public key (403). Network is constrained to Literal["PUBLIC","TESTNET","FUTURENET"]. All writes are gated by @limiter.limit(WRITE_LIMIT) and the poll endpoint by READ_LIMIT.
- web_app/api/main.py registers the new walletconnect_router.
Issue #277 — Privacy-safe telemetry pipeline (web-vitals + Sentry):
- frontend/src/services/telemetry.js initializes Sentry only when VITE_SENTRY_DSN is set, with sendDefaultPii:false plus a beforeSend scrubber that redacts Stellar public keys (G.../S..., 56 chars) and 64-hex transaction hashes. web-vitals hooks (LCP/CLS/INP/FCP/TTFB) push every change into Sentry via addBreadcrumb + setMeasurement; reportAllChanges:true is documented inline.
- frontend/src/main.jsx is the new entry point (renamed from index.jsx, which is deleted). It calls initTelemetry() before the React mount, and again from App.jsx is useEffect — the idempotent initialized flag keeps it single-shot.
- frontend/src/hooks/useConnectWallet.js and frontend/src/services/transaction.js wrap wallet.connect and position.open in Sentry transactions tagged with source / token so timings appear alongside web-vitals.
Tests:
- frontend/test/telemetry.test.jsx covers the scrubber (56-char PubKey, 56-char Seed, mixed charset, 64-hex hashes, nested objects, weird inputs) and confirms initTelemetry is a no-op without VITE_SENTRY_DSN.
- frontend/test/walletconnect.test.jsx covers URI shape, pairing poll approve/reject/expire paths, signStellarTransaction happy + missing-session paths, and disconnectWallet teardown.
- frontend/test/ActionModal.test.jsx covers role=dialog, aria-labelledby/describedby, Escape close while not loading (and no-op while loading), and the Tab focus trap.
- frontend/test/Form.a11y.test.jsx covers proper input id/htmlFor labelling, aria-invalid flip after validation, and role=alert announcements.
- web_app/tests/test_walletconnect_router.py mocks the async Redis client and verifies pair/poll/sign/complete/delete round-trips, rejects invalid Stellar public keys (422), rejects XDR envelopes whose source account does not match the claimed public key (403), and rejects unknown network values via the Literal type guard.
Deps: added @sentry/react@^9.0.0 and qrcode@^1.5.4 to package.json; yarn.lock updated to match.
* fix(backend): resolve CI PydanticUndefinedAnnotation in walletconnect router
The CI migration-roundtrip workflow was failing at module import with:
pydantic.errors.PydanticUndefinedAnnotation: name 'PairRequest' is not defined
Root cause: `from __future__ import annotations` in
quantara/web_app/api/walletconnect.py combined with the `LazyLimiter.limit()`
decorator in quantara/web_app/api/rate_limiter.py. The LazyLimiter wraps the
async route in an inner `wrapper()` whose `__globals__` point at
rate_limiter.py; FastAPI/Pydantic resolves function annotations through
get_type_hints() against those wrapper globals and cannot find
PairRequest / SignRequest / CompleteRequest defined in the walletconnect
module once PEP 563 has turned the annotations into strings.
Fix: drop `from __future__ import annotations` from the walletconnect
module so annotations are evaluated to class references at function
definition time. A short comment above the imports documents the
intentional absence so a follow-up contributor does not re-introduce
the same bug.
No behavioural change to the WalletConnect endpoints; the rest of the
PR (#272, #273, #277) is unaffected. Validated by re-running frontend
vitest (88/88 pass) and re-reading the affected code paths.
* fix(test): make VALID_STELLAR_PUBKEY fixture length deterministic
The CI failure on commit e273cdf was a hand-rolled Stellar StrKey fixture
string in test_walletconnect_router.py that miscounted to 61 chars instead of
56. The module-load assertion `len(VALID_STELLAR_PUBKEY) == 56` failed on
Python 3.12 and 3.13 with AssertionError on the literal string.
Replace the hardcoded string with `G + A * 55`, which is guaranteed to
be 56 chars, and add a useful assertion message so a regression
blows up loudly next time. No production code changes.
* fix(backend): await every redis call in walletconnect router
CI on PR #343 was failing on Test Suite (Python 3.12) and Test Suite (Python
3.13) with:
TypeError: the JSON object must be str, bytes or bytearray, not coroutine
The walletconnect route handlers were calling
`_redis().{get,setex,delete}()` without `await`. The local test fixture
`_FakeRedis` defined those methods as `async def`, so the missing
await was masked in CI-build-friendly test runs. In production with
real `redis.asyncio`, every `.get()`/`.setex()`/`.delete()` returns a
coroutine, and the route handlers were passing the un-awaited
coroutine into `json.loads()`.
Fix:
- `await` every `_redis().get/setex/delete()` call in the five route
handlers + the two helper lookup paths.
- `_validate_session` is now `async def` and `open_signing_sub_session`
awaits it.
- `submit_signed_envelope` parent-XDR retrieval rewritten from
`json.loads(_redis().get(...) or "{}")` (which evaluated
`coroutine or "{}" == coroutine` then crashed inside `json.loads`)
into an explicit form: await the get, then conditional json.loads.
No other production files touched; frontend tests unrelated.
---------
Co-authored-by: Hicent2 <hicent2@users.noreply.github.com>
1 parent da3ab96 commit 5e4dfb8
26 files changed
Lines changed: 2508 additions & 159 deletions
File tree
- quantara
- frontend
- src
- components
- ui
- action-modal
- balance-cards
- custom-button
- multiplier-selector
- token-selector
- wallet
- hooks
- pages
- form
- withdraw
- services
- test
- web_app
- api
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| 8 | + | |
8 | 9 | | |
9 | 10 | | |
10 | 11 | | |
| |||
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
18 | 22 | | |
| 23 | + | |
19 | 24 | | |
20 | 25 | | |
| 26 | + | |
21 | 27 | | |
22 | 28 | | |
23 | 29 | | |
24 | 30 | | |
25 | 31 | | |
26 | 32 | | |
27 | 33 | | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
| 34 | + | |
33 | 35 | | |
34 | 36 | | |
35 | 37 | | |
| |||
74 | 76 | | |
75 | 77 | | |
76 | 78 | | |
| 79 | + | |
77 | 80 | | |
78 | 81 | | |
79 | 82 | | |
80 | 83 | | |
81 | 84 | | |
82 | | - | |
83 | | - | |
| 85 | + | |
84 | 86 | | |
85 | 87 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| |||
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
40 | 48 | | |
41 | 49 | | |
42 | 50 | | |
| |||
Lines changed: 89 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
6 | 17 | | |
7 | 18 | | |
8 | 19 | | |
| |||
16 | 27 | | |
17 | 28 | | |
18 | 29 | | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
19 | 89 | | |
20 | 90 | | |
21 | 91 | | |
| |||
25 | 95 | | |
26 | 96 | | |
27 | 97 | | |
28 | | - | |
| 98 | + | |
| 99 | + | |
29 | 100 | | |
30 | 101 | | |
| 102 | + | |
| 103 | + | |
31 | 104 | | |
32 | 105 | | |
33 | 106 | | |
34 | 107 | | |
35 | 108 | | |
36 | | - | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
37 | 113 | | |
38 | 114 | | |
39 | 115 | | |
40 | | - | |
| 116 | + | |
41 | 117 | | |
42 | 118 | | |
43 | | - | |
| 119 | + | |
44 | 120 | | |
45 | | - | |
| 121 | + | |
46 | 122 | | |
47 | 123 | | |
48 | 124 | | |
| |||
51 | 127 | | |
52 | 128 | | |
53 | 129 | | |
54 | | - | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
55 | 137 | | |
56 | 138 | | |
57 | 139 | | |
| |||
Lines changed: 44 additions & 36 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
11 | 18 | | |
12 | 19 | | |
| 20 | + | |
13 | 21 | | |
14 | 22 | | |
15 | 23 | | |
| |||
18 | 26 | | |
19 | 27 | | |
20 | 28 | | |
21 | | - | |
22 | | - | |
23 | | - | |
24 | | - | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
25 | 33 | | |
26 | 34 | | |
27 | 35 | | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
32 | 45 | | |
33 | | - | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
34 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
35 | 53 | | |
36 | | - | |
37 | | - | |
38 | | - | |
39 | | - | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
40 | 63 | | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
45 | 68 | | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
64 | 72 | | |
65 | 73 | | |
66 | 74 | | |
| |||
Lines changed: 48 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | | - | |
57 | | - | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
64 | 97 | | |
65 | 98 | | |
0 commit comments