Skip to content

chore: back-merge main → dev (SP-153 + hotfixes) - #2586

Open
Hugo0 wants to merge 28 commits into
devfrom
backmerge-main-to-dev-20260729
Open

chore: back-merge main → dev (SP-153 + hotfixes)#2586
Hugo0 wants to merge 28 commits into
devfrom
backmerge-main-to-dev-20260729

Conversation

@Hugo0

@Hugo0 Hugo0 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Back-merge maindev. Brings dev up to date with everything that shipped to production on 29 July. Pairs with peanut-api-ts#1258.

What comes across (27 commits)

Conflicts (4), all resolved as unions

Every one was the same shape: dev had translated a string main still carries in English, or each side added a different dependency to the same callback.

File Resolution
GeneralRecipientInput dep array unioned — dev's t kept alongside chainId
card/page.tsx [advanceFromApplyResponse, pendingTerms, t]. Verified handleApply really reads both pendingTerms (line 356) and t (line 367)
withdraw/crypto/page.tsx kept dev's t(...) over main's hardcoded English; kept main's comment explaining why the resolved address is surfaced
Initial.withdraw.view.tsx same — dev's useTranslations import and t('resolvesTo')

Confirmed after resolving: 0 conflict markers, the chain-switch re-resolution effect survived, and the re-consent modal is still the escapable version — 0 occurrences of preventClose/hideModalCloseButton, Not now present.

⚠️ One test broke that neither branch could have caught alone

LockCardModal.test.tsx arrived with main's collateral-routing hotfix (#2571) and was written against the pre-i18n component. dev had since added useTranslations('card') to both modals. Git merged both sides cleanly and the result threw on render — 6 failures, all useTranslations with no next-intl context.

This is the class of break a back-merge exists to surface: two independently-correct changes that never touch the same line.

Fixed by wrapping the test in NextIntlClientProvider with the real src/i18n/app/messages/en.json, matching the convention in ActivationCTAs.test.tsx. A key-echoing stub (useTranslations: () => k => k) would have made the suite pass while silently gutting it — the assertions match user-visible strings (Slide to Lock, Card locked, Card canceled), and en.json carries exactly those.

⚠️ Two PRs against dev are now redundant

#2456 (consent FE) and #2501 (ENS) — their content shipped to main as cherry-picks, so it lands here with different SHAs. Once this merges, both should be closed, not merged.

QA

  • 181/181 suites, 2349 tests pass.
  • tsc --noEmit: 0 errors.
  • prettier --check . clean.

Risk

No new product code — every line already runs in production; this only moves dev to match. The one authored change is the test wrapper. The real risk is the opposite: leaving dev 27 commits behind, so the next release branch silently omits today's work.

Summary by CodeRabbit

  • New Features

    • Added re-consent prompts for updated legal documents, including snoozing and acceptance flows.
    • Added legal document version tracking and consent recording during signup and card applications.
    • Added faster display of cached primary names while names are revalidated.
    • Added redirects for card-related legal pages.
  • Bug Fixes

    • Improved card lock and cancellation safety when details are still loading.
    • Corrected address links and payment receipts when recipient names are unavailable.
    • Ensured withdrawals use the appropriate collateral routing strategy.

0xkkonrad and others added 28 commits July 29, 2026 10:43
ENS names can hold a different address per chain (ENSIP-11). The
withdraw flow resolved every name to its mainnet record and sent on
the selected chain — funds loss when the name points elsewhere on
that chain (reported by an external ENS-savvy tester with
test.ses.eth: mainnet and Arbitrum records differ).

- resolveEns/validateAndResolveRecipient accept the destination
  chainId and forward it to /ens/:name?chainId= (api-ts #1236).
- Switching chains after typing a name re-resolves it for the new
  chain instead of silently keeping the old address.
- The resolved address is now shown under the input as soon as the
  name validates, and again in the compatibility warning modal — the
  user sees where funds go BEFORE any warning/confirm step.

(cherry picked from commit f2c0aac)
…flight

Between switching chains and the new resolution landing, recipient.address
still holds the previous chain's address and Review stayed clickable —
the exact wrong-chain send this PR fixes, in a narrow race window.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 679c359)
crypto-withdraw-confirm.test.tsx (new on dev) mocks general.utils with only
the functions the page used at the time; the compatibility-modal change now
also renders printableAddress from that module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 4e56b63)
The backend's new consent_records ledger stores what the user was
actually shown, so every acceptance surface now echoes the displayed
document versions instead of letting the server assume: signup sends
terms+privacy via the x-accepted-legal header (the ZeroDev SDK owns the
register/verify body), card apply sends the exact regional doc set from
CardTermsScreen, and versions+hashes are generated from the src/content
legal frontmatter at build time — no hardcoded versions anywhere.

Adds the ToS §17 re-consent flow: a blocking click-through modal when a
published document version moves past the user's last provably-accepted
one. Fails open — a dead consent endpoint never locks the app.

(cherry picked from commit e1bdcf9)
The single-line entries exceeded printWidth 120, so every predev run
left a prettier-dirty tree; one field per line keeps the generated file
byte-stable under formatting.

(cherry picked from commit c0a3448)
A logout followed by login as a different account in the same SPA
session skipped the second user's consent-status check.

(cherry picked from commit a0bccaf)
Every dev/build entrypoint now regenerates the legal-version constants
(dev:clean, dev:fallback, analyze bypassed the predev hook and could
ship stale version/hash echoes). ReConsentModal state is fully isolated
per account: switching users clears the doc list, checkbox, and error,
a slow status response for the previous account is discarded, and the
checkbox resets after a successful acceptance.

(cherry picked from commit be5545b)
…ent failures to Sentry

Review follow-ups (Hugo on #2456):

- ReConsentModal tests: fail-open on status failure (must never lock the
  app), accept failure keeps the retry path, stale-response discard on
  account switch (regression already caught once in review), undisplayable
  slug filtering, once-per-user-per-session check.
- consent service tests: pin the exact signup/card doc sets per region and
  that every echoed entry carries its own generated version+hash — silent
  drift here mis-ledgers legal consent.
- Both catch blocks now Sentry.captureException instead of console.error:
  a systematic /accept failure traps every user behind an undismissable
  modal, and prod must be able to see that.

(cherry picked from commit f06fd71)
Lock/cancel sign a withdrawal whose purpose is draining Rain collateral
back to the wallet, but since cb302d3 removed the smartBalance:0n
input, routing fell through to live-balance strategy selection: any
user whose wallet USDC covered their spending power routed smart-only,
tripped the modals' own strategy check, and could neither lock nor
cancel ('Unexpected withdrawal strategy', prod, 3 users affected).

Also fail closed when the card overview hasn't loaded: undefined read
as zero spending power, silently skipping the withdrawal and getting
the action rejected server-side ('Withdrawal signature required').

Port of the dev-based #2570 (closed in favor of this main-based hotfix;
that branch holds the i18n-ified variant for the dev back-merge).
external-wallet withdrawals have no recipient username or parsed
identifier, so the drawer's userName was undefined and the header
rendered the literal string. fall back to the recipient address
(shortened by printableUserHandle) like recipientName already does.
…lash

a cold mount (mobile reopening the pwa reloads the page) showed the raw
address for a beat while the lookup ran — pay the 404, engage the client
fallback, resolve, flip. persist resolved names for 24h and paint them
immediately on mount while the lookup revalidates. an authoritative
server "no name" evicts the entry so stale names can't mask reality.
coderabbit: JSON.parse('null') passes the try/catch but null[address]
throws during render. treat any non-object root as an empty cache.
…k href in sync

code-review findings: (1) with the server route erroring (today's prod
state) the only eviction path was server null, which never fires — a
client lookup settling '' (justaname's not-found) now also masks and
evicts, so a released/transferred ens name can't keep painting from
cache. (2) AddressLink's effect never reset urlAddress on the
name→address downgrade, leaving the href pointing at a name the address
may no longer own — reachable now that cached names can be evicted
mid-mount. (3) server '' responses treated same as null.
…0729-155858

content: publish latest to production (src/content → peanut-content@fc31f07)
…0729-160449

content: publish latest to production (src/content → peanut-content@f33d67f)
…ollateral-only

fix(card): force collateral-only routing on lock/cancel
…undefined-recipient

fix(withdraw): stop success receipt showing 'Sent to undefined'
fix(ens): warm-cache resolved names to stop the address→name flash on mount
The modal shipped as a hard gate: preventClose, hidden close button, and a
single Accept CTA. It mounts in the mobile-ui layout, so it covered the whole
app, including /withdraw.

That conflicts with the terms it enforces. §17.2 gives material changes a
30-day runway and offers the click-through as a way to accept sooner, by
choice. We published on 2026-07-15, so the new terms take effect around
2026-08-14; a hard gate enforces them about two weeks early. §17.3 says a user
who does not agree must stop using the Services. For a non-custodial wallet
that must still leave a path to their own funds.

So the prompt now defers instead of blocking:

- "Not now" (plus close, backdrop and Escape) dismisses it. The checkbox does
  not gate the exit.
- A dismissal snoozes the prompt for 3 days per user, in localStorage. It
  never writes a ledger row, because a refusal is not consent.
- Accept reports modal_cta_clicked, not modal_dismissed. Both events existed
  already. We need the accept-vs-refuse ratio to measure the rollout.
- Copy no longer says "To keep using Peanut". That claim is no longer true.

Accept stays the primary action: purple, shadow, first in the stack.
…interval

"Not now" now snoozes until the documents actually take effect — the
frontmatter version plus the 30 days our own ToS §17.2 promises — instead of a
flat 3 days. A document posted today buys the user its full notice period.

Past that date §17.3 already makes continued use acceptance, so the prompt only
still asks in order to record explicit consent. That earns a gentle cadence,
not a prompt on every app open, so the snooze floors at MIN_SNOOZE_DAYS.
The committed file said card-terms-international was 2026-06-01. The content
submodule this branch pins says 2026-07-14 — the §8.4 Nigerian Users clause.
`predev`/`prebuild` regenerate this file, so any dev server or Vercel build
already produced the newer value and left the tree dirty.

WARNING, cross-repo: peanut-api CURRENT_LEGAL_VERSIONS still says 2026-06-01
for the same document. sanitizeEchoedDocuments treats a client version newer
than the server's as an attack and clamps it, dropping the hash. So the ledger
would record 2026-06-01 for users who were shown 2026-07-14, and the clause
update would never trigger re-consent. The constant needs the same bump in
peanut-api-ts#1255.
fix(re-consent): make the terms prompt escapable, per our own ToS §17
[TASK-20903] Prod release SP-153 (cherry-picks) — ENS + consent client → main
/card-terms-us etc. 404'd while /en/card-terms-us worked — same
redirect the terms/privacy slugs already have.
hotfix: redirect bare card legal slugs to /en pages
Brings dev up to date with everything that shipped to production on 29 July:
the consent-ledger echo and escapable re-consent modal (tos-v1 phase 2),
chain-aware ENS resolution with the visible resolved address in withdraw, and
four hotfixes (card legal redirects, ENS warm cache, withdraw receipt, card
lock collateral routing).

Four conflicts, all the same shape: dev had translated strings that main still
carries in English, or each side added a different dependency to the same
callback. Resolutions keep both sides:

- GeneralRecipientInput / card page: dependency arrays unioned. Verified each
  dep is really used — handleApply reads both `pendingTerms` and `t`.
- withdraw/crypto page + Initial.withdraw.view: kept dev's `t(...)` calls over
  main's hardcoded English, and kept main's comment explaining why the resolved
  address is surfaced before the review step.

One test needed a fix that neither branch could have caught alone.
LockCardModal.test.tsx arrived with main's collateral-routing hotfix and was
written against the pre-i18n component, so it has no next-intl context. dev had
since added useTranslations('card') to both modals. Git merged both cleanly and
the result threw on render. Wrapped the test in NextIntlClientProvider with the
real en.json — a key-echoing stub would break the assertions, which match on
user-visible strings ("Slide to Lock", "Card locked").
@cursor

cursor Bot commented Jul 29, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview Jul 29, 2026 11:17pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds legal-document version generation, consent submission and re-consent prompting, card lock/cancel safeguards, ENS name caching, receipt fallbacks, localized legal redirects, and a withdrawal destination clarification.

Changes

Legal consent and document versioning

Layer / File(s) Summary
Legal metadata generation and routing
package.json, scripts/generate-legal-versions.mjs, redirects.json
Build lifecycle scripts generate deterministic legal versions and hashes, while card legal slugs redirect to localized routes.
Consent contracts and acceptance wiring
src/services/consent.ts, src/services/__tests__/consent.test.ts, src/hooks/useZeroDev.ts, src/services/rain.ts, src/app/(mobile-ui)/card/page.tsx
Signup and card flows construct generated legal records and send them through passkey registration or card application requests.
Re-consent prompt and snooze lifecycle
src/components/Global/ReConsentModal/*, src/app/(mobile-ui)/layout.tsx, src/constants/analytics.consts.ts
The layout renders a per-user re-consent modal with acceptance, postponement, analytics, error handling, and localStorage snoozing behavior covered by tests.

Card lock and cancellation controls

Layer / File(s) Summary
Fail-closed card control flows
src/components/Card/LockCardModal.tsx, src/components/Card/CancelCardModal.tsx, src/components/Card/__tests__/LockCardModal.test.tsx
Lock and cancellation reject unloaded card overviews, force collateral-only withdrawal routing, and test signed, unloaded, and zero-spending-power cases.

Name resolution and receipt display

Layer / File(s) Summary
ENS warm-cache behavior
src/hooks/usePrimaryNameServer.ts, src/hooks/__tests__/usePrimaryNameServer.test.tsx
Name lookups use a one-day localStorage cache, revalidate results, and evict cached names after definitive no-name responses.
Address and payment display fallbacks
src/components/Global/AddressLink/index.tsx, src/features/payments/shared/components/PaymentSuccessView.tsx
Address links reset stale ENS targets and payment receipts use additional external-recipient fallbacks.

Supporting UI clarification

Layer / File(s) Summary
Withdrawal destination clarification
src/app/(mobile-ui)/withdraw/crypto/page.tsx
The compatibility modal comment explains that the displayed destination is the resolved concrete address.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ReConsentModal
  participant consentApi
  participant localStorage
  User->>ReConsentModal: Open application layout
  ReConsentModal->>consentApi: getStatus()
  consentApi-->>ReConsentModal: Consent status
  User->>ReConsentModal: Accept updated documents
  ReConsentModal->>consentApi: accept(documents)
  User->>ReConsentModal: Select Not now
  ReConsentModal->>localStorage: Store snooze timestamp
Loading

Possibly related PRs

Suggested reviewers: innolope-dev, jjramirezn, kushagrasarathe

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the back-merge from main into dev and mentions the included hotfixes.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backmerge-main-to-dev-20260729

Comment @coderabbitai help to get the list of available commands.

@Hugo0

Hugo0 commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

Copy link
Copy Markdown
Contributor

Code-analysis diff

Painscore total: 6739.08 → 6764.48 (+25.4)
Findings: +5 net (+61 new, -56 resolved)

🆕 New findings (61)

  • critical complexity — src/app/(mobile-ui)/card/page.tsx — CC 126, MI 56.88, SLOC 491
  • critical complexity — src/services/rain.ts — CC 67, MI 59.94, SLOC 264
  • critical complexity — src/features/payments/shared/components/PaymentSuccessView.tsx — CC 58, MI 55.88, SLOC 171
  • high hotspot — src/app/(mobile-ui)/card/page.tsx — 45 commits, +1108/-512 lines since 6 months ago
  • high method-complexity — src/app/(mobile-ui)/layout.tsx:37 — CC 38 SLOC 65
  • high hotspot — src/app/(mobile-ui)/withdraw/crypto/page.tsx — 37 commits, +444/-247 lines since 6 months ago
  • high hotspot — src/constants/analytics.consts.ts — 36 commits, +323/-7 lines since 6 months ago
  • high hotspot — src/hooks/useZeroDev.ts — 35 commits, +297/-205 lines since 6 months ago
  • high complexity — src/hooks/usePrimaryNameServer.ts — CC 33, MI 58.08, SLOC 86
  • high complexity — src/components/Card/LockCardModal.tsx — CC 19, MI 49.02, SLOC 98
  • high complexity — src/constants/analytics.consts.ts — CC 1, MI 32.83, SLOC 175
  • medium react-long-component — src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 610 lines — split it
  • medium high-mdd — src/app/(mobile-ui)/card/page.tsx:56 — CardPage: MDD 148.3 (uses across many lines from declarations)
  • medium high-mdd — src/features/payments/shared/components/PaymentSuccessView.tsx:83 — PaymentSuccessView: MDD 100.6 (uses across many lines from declarations)
  • medium high-mdd — src/app/(mobile-ui)/layout.tsx:37 — Layout: MDD 70.8 (uses across many lines from declarations)
  • medium high-mdd — src/components/Card/CancelCardModal.tsx:26 — CancelCardModal: MDD 64.1 (uses across many lines from declarations)
  • medium high-mdd — src/hooks/useZeroDev.ts:47 — useZeroDev: MDD 61.6 (uses across many lines from declarations)
  • medium high-dlt — src/app/(mobile-ui)/card/page.tsx:56 — CardPage: DLT 60 (calls 60 distinct functions — high context load)
  • medium high-dlt — src/hooks/useZeroDev.ts:47 — useZeroDev: DLT 54 (calls 54 distinct functions — high context load)
  • medium high-mdd — src/components/Card/LockCardModal.tsx:45 — LockCardModal: MDD 47.5 (uses across many lines from declarations)

…and 41 more.

✅ Resolved (56)

  • src/app/(mobile-ui)/card/page.tsx — CC 125, MI 56.97, SLOC 488
  • src/services/rain.ts — CC 65, MI 60.08, SLOC 262
  • src/features/payments/shared/components/PaymentSuccessView.tsx — CC 56, MI 55.94, SLOC 171
  • src/app/(mobile-ui)/card/page.tsx — 44 commits, +1100/-510 lines since 6 months ago
  • src/app/(mobile-ui)/layout.tsx:36 — CC 38 SLOC 65
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx — 36 commits, +424/-245 lines since 6 months ago
  • src/constants/analytics.consts.ts — 35 commits, +322/-7 lines since 6 months ago
  • src/hooks/useZeroDev.ts — 34 commits, +292/-204 lines since 6 months ago
  • src/components/Card/LockCardModal.tsx — CC 18, MI 49.41, SLOC 95
  • src/constants/analytics.consts.ts — CC 1, MI 32.9, SLOC 174
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx:43 — WithdrawCryptoPage is 607 lines — split it
  • src/app/(mobile-ui)/card/page.tsx:55 — CardPage: MDD 146.7 (uses across many lines from declarations)
  • src/features/payments/shared/components/PaymentSuccessView.tsx:83 — PaymentSuccessView: MDD 97.8 (uses across many lines from declarations)
  • src/app/(mobile-ui)/layout.tsx:36 — Layout: MDD 70.8 (uses across many lines from declarations)
  • src/components/Card/CancelCardModal.tsx:26 — CancelCardModal: MDD 60.9 (uses across many lines from declarations)
  • src/hooks/useZeroDev.ts:46 — useZeroDev: MDD 61.0 (uses across many lines from declarations)
  • src/app/(mobile-ui)/card/page.tsx:55 — CardPage: DLT 59 (calls 59 distinct functions — high context load)
  • src/hooks/useZeroDev.ts:46 — useZeroDev: DLT 52 (calls 52 distinct functions — high context load)
  • src/components/Card/LockCardModal.tsx:45 — LockCardModal: MDD 42.8 (uses across many lines from declarations)
  • src/services/rain.ts:335 — rainRequest CC 29 SLOC 62

…and 36 more.

📈 Painscore deltas (top movers)

File Before After Δ
src/components/Global/ReConsentModal/index.tsx 0.0 7.8 +7.8
src/constants/legal-versions.generated.ts 0.0 5.0 +5.0
src/components/Global/ReConsentModal/utils.ts 0.0 4.9 +4.9
src/services/consent.ts 0.0 4.7 +4.7

@github-actions

Copy link
Copy Markdown
Contributor

🧪 UI test report — ✅ all green

Suites

  • unit: 2352 ran, 0 failed, 0 skipped, 38.9s

📊 Coverage (unit)

metric %
statements 61.4%
branches 44.7%
functions 51.0%
lines 61.9%
⏱ 10 slowest test cases
time test
3.1s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › never places two stickers in heavy overlap (broad seed sweep)
1.1s src/utils/__tests__/demo-api.test.ts › isDemoMode() is false when not running under Capacitor
0.4s src/components/Card/share-asset/__tests__/shareAssetLayout.test.ts › every sticker stays within canvas at any count
0.4s src/utils/__tests__/sentry.utils.test.ts › defaults to the client budget under a browser global
0.3s src/app/(mobile-ui)/withdraw/__tests__/withdraw-states.test.tsx › Bank withdrawal keeps the $1 minimum for sub-$1 amounts
0.3s src/app/actions/__tests__/api-headers.test.ts › should include Content-Type in validateInviteCode
0.3s src/utils/__tests__/sentry.utils.test.ts › still lets a per-call timeoutMs win over the default
0.2s src/app/actions/__tests__/api-headers-extended.test.ts › should not include apiKey in validateInviteCode body
0.2s src/utils/__tests__/auth-token.test.ts › returns the token hydrated from Preferences after authReady
0.2s src/utils/__tests__/url.utils.test.ts › uses the public BASE_URL in Capacitor, not the localhost WebView origin
📍 Inline annotations are in the **Unit test report** check above. Coverage artifact: `coverage-unit`. Generated by `.github/workflows/tests.yml`.

@Hugo0
Hugo0 marked this pull request as ready for review July 29, 2026 23:16
@Hugo0
Hugo0 requested a review from jjramirezn July 29, 2026 23:16

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/features/payments/shared/components/PaymentSuccessView.tsx (1)

175-181: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Raw address fallback may bypass the already-formatted recipientName.

recipientName (lines 117-128) already resolves through resolveRecipientDisplay(...) using the same chargeDetails.requestLink?.recipientAddress when no username/identifier exists. Placing the raw chargeDetails.requestLink?.recipientAddress ahead of recipientName in the userName fallback means the drawer could show an untruncated raw address while the receipt header shows the formatted recipientName for the same case — a display inconsistency between the two receipt surfaces.

Consider reordering to prefer the already-resolved recipientName (which itself falls back to the address) instead of duplicating the raw address ahead of it:

♻️ Suggested reorder
             userName:
                 user?.username ||
                 parsedPaymentData?.recipient?.identifier ||
-                chargeDetails.requestLink?.recipientAddress ||
-                recipientName,
+                recipientName ||
+                chargeDetails.requestLink?.recipientAddress,

Please confirm whether resolveRecipientDisplay/TransactionDetailsDrawer already format/truncate raw addresses before deciding if this reorder is needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/payments/shared/components/PaymentSuccessView.tsx` around lines
175 - 181, Update the userName fallback in PaymentSuccessView to prefer the
already-resolved recipientName before
chargeDetails.requestLink?.recipientAddress, preserving the existing username
and parsed recipient identifier precedence. Reuse recipientName’s
resolveRecipientDisplay formatting and avoid displaying the raw address directly
when the formatted value is available.
src/hooks/usePrimaryNameServer.ts (1)

36-51: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Stale cache entries are never pruned, only ignored.

getCachedName skips expired entries but readNameCache/writeCachedName never remove them, so ens-primary-name-cache grows monotonically with every distinct address ever viewed (e.g. transaction history counterparties) and is only ever trimmed when the same address resolves again. Low practical risk given the small per-entry size, but a cheap fix.

♻️ Proposed fix: prune expired entries on read
 function readNameCache(): NameCache {
     if (typeof window === 'undefined') return {}
     try {
         // guard the root shape — JSON.parse("null") etc. passes but would blow up on lookup
         const cache: unknown = JSON.parse(window.localStorage.getItem(CACHE_KEY) ?? '{}')
-        return cache && typeof cache === 'object' && !Array.isArray(cache) ? (cache as NameCache) : {}
+        if (!cache || typeof cache !== 'object' || Array.isArray(cache)) return {}
+        const now = Date.now()
+        return Object.fromEntries(
+            Object.entries(cache as NameCache).filter(([, entry]) => now - entry.ts < PRIMARY_NAME_TTL_MS)
+        )
     } catch {
         return {}
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/usePrimaryNameServer.ts` around lines 36 - 51, Prune expired cache
entries when loading the cache in readNameCache, using PRIMARY_NAME_TTL_MS and
each entry’s timestamp, and persist the cleaned cache through the existing
cache-writing flow. Keep invalid or unavailable storage behavior returning an
empty cache, and preserve getCachedName’s current lookup semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/Card/LockCardModal.tsx`:
- Around line 69-74: The loading failure messages in LockCardModal and
CancelCardModal are hard-coded English and must use the existing card
translations. In both files, update the overview-loading error paths to throw
the same appropriate t('errors.…') translation key, reusing each modal’s
existing t function and preserving the current fail-closed behavior.

---

Nitpick comments:
In `@src/features/payments/shared/components/PaymentSuccessView.tsx`:
- Around line 175-181: Update the userName fallback in PaymentSuccessView to
prefer the already-resolved recipientName before
chargeDetails.requestLink?.recipientAddress, preserving the existing username
and parsed recipient identifier precedence. Reuse recipientName’s
resolveRecipientDisplay formatting and avoid displaying the raw address directly
when the formatted value is available.

In `@src/hooks/usePrimaryNameServer.ts`:
- Around line 36-51: Prune expired cache entries when loading the cache in
readNameCache, using PRIMARY_NAME_TTL_MS and each entry’s timestamp, and persist
the cleaned cache through the existing cache-writing flow. Keep invalid or
unavailable storage behavior returning an empty cache, and preserve
getCachedName’s current lookup semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d6e4d53a-9463-41c9-8411-713d985a9dfd

📥 Commits

Reviewing files that changed from the base of the PR and between 7a27377 and 54fcd01.

⛔ Files ignored due to path filters (1)
  • src/constants/legal-versions.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (21)
  • package.json
  • redirects.json
  • scripts/generate-legal-versions.mjs
  • src/app/(mobile-ui)/card/page.tsx
  • src/app/(mobile-ui)/layout.tsx
  • src/app/(mobile-ui)/withdraw/crypto/page.tsx
  • src/components/Card/CancelCardModal.tsx
  • src/components/Card/LockCardModal.tsx
  • src/components/Card/__tests__/LockCardModal.test.tsx
  • src/components/Global/AddressLink/index.tsx
  • src/components/Global/ReConsentModal/__tests__/index.test.tsx
  • src/components/Global/ReConsentModal/index.tsx
  • src/components/Global/ReConsentModal/utils.ts
  • src/constants/analytics.consts.ts
  • src/features/payments/shared/components/PaymentSuccessView.tsx
  • src/hooks/__tests__/usePrimaryNameServer.test.tsx
  • src/hooks/usePrimaryNameServer.ts
  • src/hooks/useZeroDev.ts
  • src/services/__tests__/consent.test.ts
  • src/services/consent.ts
  • src/services/rain.ts

Comment on lines +69 to +74
// An unloaded overview reads as zero spending power below, which
// would skip the withdrawal and get the lock rejected by the
// backend ("Withdrawal signature required"). Fail closed instead.
if (!overview) {
throw new Error('Card details still loading — please retry in a moment')
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Localize the loading failure message.

Both modals expose a hard-coded English error through setError, bypassing the existing card translations.

  • src/components/Card/LockCardModal.tsx#L69-L74: replace the literal with a t('errors.…') key.
  • src/components/Card/CancelCardModal.tsx#L63-L68: use the same translated key.

Based on supplied library context, card errors are localized through t(...).

📍 Affects 2 files
  • src/components/Card/LockCardModal.tsx#L69-L74 (this comment)
  • src/components/Card/CancelCardModal.tsx#L63-L68
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Card/LockCardModal.tsx` around lines 69 - 74, The loading
failure messages in LockCardModal and CancelCardModal are hard-coded English and
must use the existing card translations. In both files, update the
overview-loading error paths to throw the same appropriate t('errors.…')
translation key, reusing each modal’s existing t function and preserving the
current fail-closed behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants