Skip to content

Show every cash currency behind "Show more" on Deposit with cash - #2606

Closed
mul53 wants to merge 27 commits into
qafrom
claude/fewer-options-displayed-e0991d
Closed

mul53 wants to merge 27 commits into
qafrom
claude/fewer-options-displayed-e0991d

Conversation

@mul53

@mul53 mul53 commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

What changed

"Show more" on Deposit with cash now lists all 16 local currencies instead of adding only MXN.

  • Collapsed: unchanged. USD, EUR, BRL, BDT, PHP.
  • Expanded: those, then every other corridor in CARD_FUND_LOCAL_CURRENCIES in that list's order (AED, ARS, COP, GHS, IDR, KES, MXN, MYR, NGN, PEN, UGX, ZMW).
  • DEPOSIT_CASH_CURRENCY_COUNT is derived from the full list, so the "+N" circle on the chooser's Cash row goes from +4 to +15.

Why

DepositCashOptions kept its own allowlist (ADDITIONAL_LOCAL_CURRENCY_CODES = ['MXN']). It dates from when each row fetched its payment-method chips from TransFi, so every extra currency cost a request. Chips now come from the committed CARD_FUND_LOCAL_PAYMENT_METHODS map, so that reason no longer applies, and the card-fund "Cash deposit" group already shows all 16. A new corridor added to localCurrencies.tsx now shows up here too.

For reviewers

  • The lists are computed once at module level, which replaces the useMemo.
  • I haven't checked this in the running app (it needs a signed-in session). DepositOption jest suites pass; tsc, eslint and prettier are clean for the file.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

MayankMittal1 and others added 27 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
"Deposit with cash" only expanded to MXN: the screen kept its own
allowlist of codes, left over from when each row fetched its payment
methods from TransFi. Chips now come from the committed corridor list,
so "Show more" lists every currency in localCurrencies.tsx after the
featured four, and the chooser's "+N" counts them all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 29, 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 Sep 29, 2026 10:29pm UTC
solid-app-staging Ignored Ignored Preview Sep 29, 2026 10:29pm UTC

Request Review

@mul53
mul53 changed the base branch from master to qa September 29, 2026 22:30
@claude

claude Bot commented Sep 29, 2026

Copy link
Copy Markdown

Code review

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

@mul53

mul53 commented Sep 29, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #2607, which has the same change rebuilt on qa. This branch was built on master by mistake.

@mul53 mul53 closed this Sep 29, 2026
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