Skip to content

Merge master into qa to clear the release PR's conflicts - #2614

Merged
MusabShakeel576 merged 34 commits into
qafrom
claude/pr-merge-conflicts-gs6mxf
Oct 1, 2026
Merged

MusabShakeel576 merged 34 commits into
qafrom
claude/pr-merge-conflicts-gs6mxf

Conversation

@MusabShakeel576

@MusabShakeel576 MusabShakeel576 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Solid-Money/solid-ui#2598 (qa → master) is dirty: master moved on and three files conflict. This branch is qa with master merged into it and those conflicts resolved, so merging it into qa makes #2598 mergeable again. Everything else in the diff against qa is master's own work coming across unchanged — this PR adds no new feature work of its own.

The three conflicts

  • components/PageLayout.tsx — both sides added props at the same three spots. Kept both: qa's showsVerticalScrollIndicator / sidebarTopGutter and master's onRefresh / refreshing / refreshIndicatorOffset. PullToRefreshScrollView already accepts all of them.
  • lib/assets.ts — the generated registry gained entries on both sides. Took the union, in the generator's sort order.
  • components/Rewards/NewRewards/RewardsBenefitsScreenNew.tsx — qa's tier redesign rewrote this screen and moved its copy into tierBenefitsPresentation.ts, which already resolves the live yield boost and its balance cap from the tier-benefits endpoint. Master's conflicting edit did the same job against the old TIER_CONTENT table, so it is superseded — kept qa's version. Master's companion change to JoinTierClubCard.tsx merged cleanly and is untouched.

Validation

  • npx jest — 1684 tests pass, 149/150 suites. The one failing suite (CardFundOptions) fails identically on qa's head before this merge: react-native-mmkv needs a native binary the CI-less sandbox doesn't have.
  • npx tsc --noEmit — 9 errors, every one in a file byte-identical in both parents, so untouched by this merge. None in the three resolved files.
  • npx expo lint — 8 errors, all prettier / import-sort in files likewise identical in both parents, and the lint job runs --fix.
  • lib/assets.ts cross-checked against the files on disk: every entry resolves, hashes match, sorted, no duplicates. postinstall then regenerated it and produced only insertions — no change to any entry from the conflict, confirming the union was exact. Those insertions are assets both branches had already left unregistered, so they are not included here; install regenerates them.

Worth a look, not changed here

lib/tierYieldBoost.ts on master sets TIER_YIELD_BOOST_RATES[ULTRA] = 5, while the backend default (YIELD_BOOST_DEFAULTS.tier3.apy = 0.03) and qa's redesign fallback both say 3%. After this merge JoinTierClubCard advertises "+5% Yield boost" as its pre-request fallback while the tier detail screen says "+3%". Pre-existing on master, so left alone — happy to align it if wanted.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JFyKA1v52WcPWPETeN2dfR

MayankMittal1 and others added 30 commits September 24, 2026 23:13
Reads the policy ID from EXPO_PUBLIC_PIMLICO_SPONSORSHIP_POLICY_ID and
passes it as paymasterContext on the smart account client, so Pimlico
applies it in pm_sponsorUserOperation. Unset means no policy is sent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ship-policy

feat: attach a Pimlico sponsorship policy to every userop
…o v2

The Fuse v2 module was redeployed on 2026-09-24 (0xE2d4…7b2B → 0xa98f…A999),
but a shipped build kept the old address and moved Safes onto the retired core.
The backend reads only the new module, so those cards decline every payment
with SAFE_NOT_REGISTERED, while the old lens still tells the app they are set up.

Once a build has the new address, those Safes show the enable-spending banner.
The set-up and mode-switch batches now also disable any retired v2 core still
on the Safe. Disables are computed against the module list as each earlier call
leaves it, so v1 and the retired core can come off in one batch without GS103.

Retired cores are listed in EXPO_PUBLIC_RETIRED_CASH_MODULE_V2_ADDRESSES,
which defaults to 0xE2d4… and never includes the live core.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ule-v2

fix(card-spend): disable the retired v2 module when moving a Safe onto v2
65138da made card set-up read the Safe's module list on every v2 set-up, to
disable any retired v2 core. A new cardholder's Safe is often still
counterfactual on Fuse — the set-up batch is what deploys it — so
`getModulesPaginated` returns `0x`, viem throws, and activation fails with
"Card not activated" before anything is signed.

A Safe with no code has no modules, so there is nothing to disable: return an
empty cleanup and let the batch deploy and register the Safe as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…module-read

fix(card-spend): skip the module read on a Safe not yet deployed on Fuse
… exists

buildCardSteps fell back to the bridge.xyz "cards" endorsement for every
non-Rain issuer, even when /cards/status already reported a kycStatus.
For an old Bridge customer applying for a Wirex card, an approved Bridge
endorsement marked the KYC step complete and offered "Activate card"
before they had verified with Sumsub. The endorsement now only applies
to Bridge-only users, who have no card customer and so no kycStatus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PmSmmwX1kxkmMBEP7pVKPG
fix(card): ignore the retired Bridge endorsement once a card customer exists
…quota

Production has been getting 429s with "Your payg app has exceeded its limit
of 10000 token_price requests per 1 hours" (Sentry SOLID-PJ, SOLID-DV). That
quota counts requests, and one request can carry 25 tokens, but every price
was its own request and nothing was reused:

- useBalances refreshed every 5s, and each refresh sent five by-symbol GETs
  (ETH once per chain, FUSE, BNB) plus one per token still unpriced. That is
  about 3,600 requests an hour for a single open app, so two or three users
  used up the whole app's quota.
- fusePriceUsd / ethPriceUsd polled every 5s on top of that.

Changes:
- lib/batchedLoader merges lookups started in the same tick into requests
  of up to 25. It caches prices for a minute (misses for five), keeps serving
  the last price while a refresh fails, and pauses both Prices endpoints
  after a 429.
- fetchTokenPriceUsd and the new fetchTokenPricesBySymbol go through it.
  By-address requests stay within the documented limits of 25 addresses and
  3 networks.
- Balance fallback poll goes from 5s to 30s (SSE already invalidates on
  every balance event), native price refresh from 5s to 60s, and vault
  balance poll from 3s to 30s.
- publicClient keeps one client per chain with Multicall3 batching, so reads
  made together share one eth_call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
Remove the iOS-only gates so Swap is available on every platform: the
SwapModal trigger and SwapModalProvider no longer return null on iOS,
the home Swap pill shows, and the Swaps fee row, Swap earning method and
wallet tooltip copy are no longer hidden on iOS. Geo-restriction and
disclaimer gates are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KtULm55FhtV8QpE98RAuqC
The vault balance card set its 26px value on a 24px line box and the
rewards summary set its 26px value on a 26px one; iOS clips glyphs that
overflow the line box, cutting the tops of the digits and the `$`.
Both values now get a 32px line, with the vault card's margins trimmed
so its layout height is unchanged.

The vault card also relied on Intl compact notation, which Hermes on iOS
ignores, so balances showed in full ("6,759.16 USD") and wrapped inside
the fixed-height card. The compact figure is now formatted by hand, the
value stays on one line and shrinks to fit on native, and the card uses
a min height so wrapped text on web is no longer cut off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FB6mLN2hZfRTDENZCHKhw
…o Alchemy

Prices now come from POST /accounts/v1/prices, which serves one cache shared
by every user and every pod. So the app key's Alchemy token_price quota no
longer scales with the number of open apps.

The backend is only an optimisation for the app. On any failure it asks
Alchemy directly with its own key, exactly as before, and skips the backend
for five minutes. Failures include a network error, a timeout, an older
backend without the route (404), an expired session (401), the per-user rate
limit (429), a token the backend can't price right now (503), or a reply
that isn't the expected shape.

That keeps every combination safe:
- an app build or OTA that ships before the backend route is deployed
- a backend rollback
- web and native (cookie or Bearer auth)

Old builds keep calling Alchemy directly.

Symbols and addresses the route's validation would reject are left out of
the request, so one malformed token can't fail the lookup for the rest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
Mona Sans has a 1.41em natural line (1.09em ascent, 0.32em descent).
When a lineHeight is shorter, iOS keeps the full descent and trims the
top of the first line, while Android trims both edges evenly, cutting
the bottom of the last line; both then clip the glyphs to the view.
Web does neither, which is why only the native apps showed it.

The vault card's exact-USD line gets the same ~1.15em headroom as the
balance figure (its `$` sat within a pixel of the edge), and the
rewards card and its value row use min heights so larger system font
sizes, common on Android, grow the card instead of clipping the text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013FB6mLN2hZfRTDENZCHKhw
…fixes-auys1s

Fix vault balance formatting and layout on iOS
The backend's price route leaves out tokens on chains it has no Alchemy
network for, and the app takes a token left out as having no price and
remembers that for five minutes. A chain added to ALCHEMY_NETWORKS (which
an OTA can do) before the backend knows it would lose its prices that way.

Address lookups are now chunked by whether the backend covers the chain
(BACKEND_PRICE_CHAIN_IDS, mirroring its ALCHEMY_PRICE_NETWORKS), and tokens
on any other chain are priced from Alchemy directly, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
… polls

The slower polls were there to save the Prices API quota. Price lookups are
now cached for a minute, in the app and by the backend's shared cache, so
polling more often no longer costs token_price requests, and the slower
intervals only made balance updates visibly lag.

Balances poll every 5s again (staleTime 5s), native-token prices every 5s,
and vault balances every 3s, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMuZxhszcH7Kc5s7dc2NNq
…mit-emails-k9buga

Batch and cache Alchemy price API requests to reduce quota usage
The deposit address screens quoted a flat 0.03% everywhere, so a rate an
admin set for one route and chain on the Deposit fees page never reached
the app. The notice now asks /deposit/fee-quote, which runs the same
assessment the deposit workflows charge with and answers 0.03% for any
route and chain with no rate of its own.

The client's route table still decides which deposits can be charged at
all, so an address that is never bridged is never asked about. While the
quote loads the line is left off; if it cannot be fetched the notice
falls back to 0.03% rather than to free. Rates move to parts per million,
as the backend quotes them, so a fractional basis point prints exactly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dation

A JS number cannot hold 18 decimals, so a soETH balance of
0.361164894291325699 became 0.3611648942913257. Max filled that in, it
passed the balance check, and the withdrawal reverted for asking more than
the Safe holds. The soFUSE and soETH balance hooks now return wei, and the
form validates and fills max in wei.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ce images

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…m-backend

feat(deposit): quote the deposit fee the backend charges for each route
The recovery screen opened on an empty email field. The user this came
from reached it twice on a phone, sat on that field both times, and left
without typing anything — then told support the recovery interface would
not open. Their own screen recording shows it opening fine.

Prefill it from the account this device last knew about. Logging out and
a session expiring both leave that row behind — only "Forget all users"
clears it — so on the device someone is locked out of, it is almost
always the account they are reaching for.

The field now takes a username as well, for the roughly one account in
ten that carries no email and for anyone who cannot remember which inbox
they used. Validation only holds an entry to the email rules once it
contains an "@"; which account it names is the server's call. The OTP
step then says where the code actually went, from the masked hint the
backend returns, because a username recovery never learns the address —
and for the same reason it no longer sends one back on verify.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HRh59soi7iX4J3UwxVVGRS
…w-7gma44

feat(recovery): prefill the identifier, and accept a username
…-new

Refresh wallet activity and improve cashback and card interactions
claude and others added 3 commits October 1, 2026 07:30
Two reported copy bugs, both cases of a surface naming a figure it was not
actually showing.

The spend-mode sheet labelled its Cash figure "Your USDC balance", but the
number behind it is `useCardSpendableBalanceUSD`, which sums every asset in
`CARD_SPENDABLE_ASSETS` — USDC, USDT and soUSD on Fuse. A cardholder funded in
USDT read their own balance as somebody else's. The label, the caption, the
help slide and the empty-state segment now say USD (and "Add funds") rather
than naming one of the three.

The tier comparison screen printed its own yield boost constants, and they had
drifted from the backend's: the rewards tab's "+N% Yield Boost" card reads
`yieldBoostPercentage` off the API and showed Ultra's 5%, while the stats band
and perk row on the tier detail still said 3%. The rewards service's table is
Core 0 / Prime 2 / Ultra 5, so 5% was the true figure.

Added `lib/tierYieldBoost.ts`, which resolves the rate from the tier-benefits
endpoint and falls back to a copy of that table only while the request is in
flight or against a backend that does not send the field — a live 0 is taken as
an answer, so a boost switched off in admin config stops being advertised. The
tier detail screen and the "Join Prime/Ultra Club" teaser both read through it,
substituting by label rather than by index so the columns cannot be relabelled
if a tier gains a benefit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S4gKxuxYsKWtWKUF5Lo6Dm
…nhwni

Unify yield boost rates across rewards screens
Three files conflicted between the qa release branch and master:

- components/PageLayout.tsx: both sides added props. Kept both —
  qa's showsVerticalScrollIndicator/sidebarTopGutter and master's
  onRefresh/refreshing/refreshIndicatorOffset. PullToRefreshScrollView
  already accepts all of them.
- lib/assets.ts: the generated registry gained entries on both sides.
  Took the union, in the generator's sort order (upgrade-card-* from
  master before qa's rewards-tiers/v4/*). Verified against the files on
  disk: every entry resolves, hashes match, no duplicates.
- components/Rewards/NewRewards/RewardsBenefitsScreenNew.tsx: qa's tier
  redesign rewrote this screen and moved its copy into
  tierBenefitsPresentation, which already resolves the live yield boost
  (and its balance cap) from the tier-benefits endpoint. Master's edit
  did the same job against the old TIER_CONTENT table, so it is
  superseded — kept qa's version. Master's companion change to
  JoinTierClubCard merged cleanly and is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JFyKA1v52WcPWPETeN2dfR
@vercel

vercel Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

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

2 Skipped Deployments
Project Deployment Actions Updated
solid-app Ignored Ignored Preview Oct 1, 2026 11:47am UTC
solid-app-staging Ignored Ignored Preview Oct 1, 2026 11:47am UTC

Request Review

@MusabShakeel576 MusabShakeel576 changed the title Optimize token pricing and improve activity refresh UX Merge master into qa to clear the release PR's conflicts Oct 1, 2026
@claude

claude Bot commented Oct 1, 2026

Copy link
Copy Markdown

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

Review scope:

  • Scanned 102 files (+4,205 / -549 lines) for bugs, security issues, and development artifacts
  • Verified merge conflict resolution in the three conflicted files (PageLayout.tsx, lib/assets.ts, RewardsBenefitsScreenNew.tsx)
  • Checked for debugging leftovers, commented-out code, test shortcuts, and hardcoded secrets

Notes:

  • No CLAUDE.md files exist in this repository
  • The console.error in useActivityRefresh.ts is appropriate error handling (not a debugging leftover)
  • The .env.example placeholder value is appropriate for an example file

TIER_YIELD_BOOST_RATES had Ultra at 5, citing a `TIER_YIELD_BOOST_PCT`
table in the accounts service's rewards.service.ts. No such table exists.
The boost the backend actually pays comes from YIELD_BOOST_DEFAULTS in
app-config.constants.ts, read through yieldBoostForTier and
grantedBoostPercentage: tier2 0.02 and tier3 0.03, so Prime 2 and Ultra 3.

The figure is only a fallback — the tier-benefits endpoint wins whenever
it has answered — but it is the one the rewards tab's JoinTierClubCard
shows while that request is in flight, so Ultra was being teased as "+5%
Yield boost" next to a tier detail screen quoting "+3%", and neither
matched what accrues.

Also point tierBenefitsPresentation's fallback at the same table instead
of its own 2/3 literals. Two tables are how these drifted apart; the
numbers were already equal, so nothing rendered changes. The display
strings in TIER_PRESENTATION stay literal and stay guarded by the
existing `tierPresentationContent(tier) === TIER_PRESENTATION[tier]`
test, which now fails loudly if the table and the copy disagree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JFyKA1v52WcPWPETeN2dfR
@MusabShakeel576
MusabShakeel576 merged commit 060da07 into qa Oct 1, 2026
5 checks passed
@MusabShakeel576
MusabShakeel576 deleted the claude/pr-merge-conflicts-gs6mxf branch October 1, 2026 11:50
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