Rewards v3 prod release: merge master, drop points copy where points can't unlock - #2579
Merged
Merged
Conversation
…thdraw Two symptoms from one support report (Solid Operations 13039057237): the Swap and Send buttons had disappeared from home, and withdrawing from the card did not work. The reporter holds $4.87 on a Rain card and $0 in their wallet — every deposit they have ever made went to the card's own deposit address, which never touches their Safe. Home: the action row asked `depositCompleted`, which only ever knows about wallet funding (a deposit on record, a token balance, a vault balance). A cardholder who funds their card directly satisfies none of it, so the row collapsed to a lone "Add Funds" on a screen that was showing them a balance right above it. It now asks whether the user holds anything in any of the pots the breakdown shows, with `depositCompleted` still in front so an erroring or not-yet-settled balance query cannot hide the buttons either. Swap and Send work off wallet tokens, so a card-only balance opens Send on an empty asset list — which now says why, and points at Add Funds, instead of rendering a blank list under "Select an asset". Card withdraw: the asset picker was a portalled dropdown menu on web. Inside the withdraw sheet on a phone browser it covered the "Withdraw" button and swallowed taps on its own rows — in the recording the user spent nine seconds tapping the asset they wanted and gave up with the sheet still open. Both platforms now render the in-flow list native already had, from one shared file. Two more things the same screen got wrong: - Every token the user's Rain contracts support was offered, so the two assets they could actually withdraw sat behind eight $0 rows, including a duplicate "USDC" and "USDT". Only funded assets are listed now (plus the selected one, and the full list when nothing is funded), and a symbol held on two chains names its chain. - Picking the asset already selected cleared the amount, so "press Max, then confirm the destination" ended with an empty field. Only an actual change of asset clears it now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FxQs7MKViUX9AL5bzUzj9o
…rvnpw Fix card withdrawal asset picker and fund visibility issues
…ncel fix(withdraw): prevent user cancellations from being reported as Sentry errors
…retry fix: retryable issuer decline code
A login whose passkey the backend cannot tie to an account was answered by replacing the screen with signup. The user had just passed Face ID, so what they saw was their account being offered back to them as a new one — and signup was a dead end anyway, refusing their own address as already registered. Reported from Italy with a screen recording: three authentications, then Create your account. The 404 does not mean "new user". It means the passkey the authenticator just signed with reaches no account: unknown to Turnkey, or belonging to a sub-organization whose account row a failed signup never wrote. The account itself is usually fine — it is the passkey that isn't on it — so the way out is recovery, which attaches a working passkey to the account they already have. Keep them on onboarding, name what happened, and put the recovery prompt in front of them. A login started from the landing step raises the Welcome sheet, which is where that prompt and an explicit "Create an account" live; the landing hero has room for neither, so until now those attempts failed to a bare hero with no way forward. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRh59soi7iX4J3UwxVVGRS
handleLogin re-throws the original error so callers keep its `status` and `code` — which left every caller holding the raw API message alongside the sanitized one, and every toast read the raw one. The sanitized copy went into `loginInfo.message`, which nothing reads. So the screen still said "User not found": the bare 404 of a backend that predates the typed codes, under a heading telling the user their passkey isn't linked to an account. That is the near-term default, not an edge case — the backend half of this fix ships separately. Decide the copy in one exported place and call it from each toast: onboarding, legacy onboarding, and the multi-account welcome screen, which showed the same raw message next to the account row the user had just tapped. Reported by Seer on #2549. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HRh59soi7iX4J3UwxVVGRS
…w-7gma44 Fix passkey login redirecting existing users to signup
feat: EURC spending on base
fix: eurc spend
fix: eurc spend enable cache
Adds the repay step to the borrow-position sheet. Repay slides the sheet into an amount + "Repay with" view; a successful repayment slides back to the updated position. - Wallet sources use repayFromSafe (approve + repay + approve(0) when the module is revoked); collateral sources use repayFromCollateral. - A full repayment also withdraws the remaining escrowed collateral in the same user operation, gated on a buffer for interest and price moves so the withdraw can never revert the batch. - Tender is read from repayTender per token. The deployed build predates it, so where the call fails the list falls back to USDC.e + soUSD. - ABI entries generated with forge inspect from the v2 sources. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat: repay card credit from wallet or collateral
Desktop web only (the ResponsiveModal branch of CardBottomSheet); native and the phone-width web bottom sheet render as before. - Modal is 480px with a 24px inset all round and the body drops its 17pt inset: ~432px of content instead of ~306px, so the repay amount no longer clips and labels stop wrapping. - The modal's own header row is hidden. Bodies draw a 36px close button in their first row (Repay: back · title · close), cutting ~122px of space above the first element to 24px. - MAX stays an outlined pill instead of inverting to white. - Repay shows the token amount under the dollar figure: exact for a partial wallet repayment, an estimate (≈) where the module sizes it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-layout fix: tighten card sheets on desktop web, show repay token amount
- Home: "Spend mode › <mode> [toggle]" strip under the card (Figma 26134:22854) for cardholders who pass canChangeMode (Wirex, spend-mode cohort, v2 reachable). Tapping flies the card to the pane and opens the spend-mode sheet once it lands. Toggle is on for Credit/Smart, off for Cash, and only reports the mode. - Card pane: "Card spending isn't set up" banner when spending was set up and both modules are now off. Enable re-enables the module the Safe is registered on (v2 if migrated, else v1) via register(); hidden when v2 is configured but unreadable so it never guesses v1 for a migrated Safe. - Spend-mode drawer: selected pill is always white; Add funds opens WirexCardFundModal (hidden when funds can't be added); panel area holds Smart's height so the drawer no longer resizes between modes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-and-enable feat: home spend-mode strip, re-enable card spending, drawer fixes
The referral target moved from $75 to $150 while rewards that qualified on $75 were still in their 30-day payout delay. Their rows read "$145.63/$150 spent" next to "Qualified", which looked like a mistake and was reported as one. The referral summary now sends each row's own bar (spendTargetUsd, merchantTarget): the one a qualified friend cleared, today's for everyone else. The row uses it for the spent line, the "to go" chip and the progress bar, and falls back to the program-level target when talking to a backend that doesn't send it yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C4DDGA1KnJ6ADwWHFgUX4Q
…fixes-6xxe0w Support row-level spend/merchant targets in referral rewards
The Credit segment, Smart's figure and "Available to borrow" all read the lens's availableToBorrowUsd, which is also clamped by the Safe's remaining spending limit. A Safe with a $1,000 daily limit therefore showed $1,000 of credit beside a $2,890 line, while Cash showed the unclamped balance. Quote the line minus debt, still clamped by the per-Safe and global debt caps (totalDebtUsd added to the ABI for the latter). The lens figure stays a floor, which keeps a limits waiver correct. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
After the v2 redeploy every cohort Safe has all modules off and is unknown to the new core. The registration read then reported it as v1, so the "Enable" row re-enabled v1 (or registered a fresh Safe on v1), putting a cohort cardholder back on cash with Credit and Smart out of reach. For a spend-mode cohort member with no working module and no v2 registration, report the Safe as awaiting v2: the enable row shows, and the batch disables v1 if it is on, enables v2 and registers with the v1 caps clamped to v2's ceilings (v2 defaults when there were none). The read waits for the cohort answer so it cannot report v1 first, and the backend confirm is now told which module was registered. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…-headroom fix: show credit headroom, not the daily limit, as available to borrow
feat: set up spend-mode cohort cards on v2 when no module is enabled
Drop the spend-mode cohort gate: the home "Spend mode" strip, the card page's spend-mode row and picker, and the borrow position card now show for any Wirex card with working spending on a build that reaches v2. The backend only used the cohort to answer spend-mode/access; ExtAuth never checks it. A Safe with no working module is now set up on v2 for every cardholder, not only the cohort: new cards, a disabled v1 module, and Safes from a replaced v2 core. v2's registerSafe starts every Safe in Debit, so no setMode is needed. A working v1 Safe stays on v1 and migrates through the picker; a build without v2 still falls back to v1. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…tice
Implements Figma 26974:12806. The picker offers Cash and Credit only:
Smart is listed in HIDDEN_SPEND_MODES rather than removed, and a Safe
already on Smart still sees its own segment. Segment widths, the pill's
position and the reserved panel height all follow the offered modes, so
the hidden one leaves no gap.
Credit now shows a yellow notice between the caption and the Borrowed
card ("Currently credit mode works only with your USD yield balance"),
using the design's background and badge assets. Gaps follow the frame:
17 caption→notice, 42 notice→card, 34 card→button. The notice's rendered
height feeds the reserved height, so a three-line wrap on a narrow sheet
does not move the button between tabs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat: open spend modes to every cardholder and register new Safes on v2
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VhnDQhJoWUKneB9KFcZgfV
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. Review scope:
🤖 Generated with Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings
masterintoqafor #2576. The merge had no conflicts.It also changes the rewards benefits screen. Each tier page said "Unlocks at 5M / 35M points" and opened a "how to earn points" sheet even when points could no longer reach that tier. The page now follows the membership state's
pointsUnlockEnabled, which https://github.com/Solid-Money/solid-backend/pull/1861 reports per user. When points can't take the user higher, the line reads "Unlocks with a membership" if a lock or annual charge is on sale for that tier, is hidden otherwise, and the points sheet goes.No other app change is needed. The rewards-page card, the skip-the-line sheet, the Buy FUSE targets and the upgrade flow already follow
fuseSkipLine.enabledandpointsUnlockEnabled, and the backend now sets both per user.The Sentry date finding is not a bug: #2576 (comment)
tsc --noEmit: 9 errors, the same 9 as master.jest components/Rewards lib/__tests__/tierUpgrade.test.ts: 124 tests, all passing.🤖 Generated with Claude Code
https://claude.ai/code/session_01VhnDQhJoWUKneB9KFcZgfV