Skip to content

feat(card): surface Didit duplicate-filter review reasons on /card/activate (qa) - #2091

Open
MusabShakeel576 wants to merge 291 commits into
masterfrom
claude/dreamy-ptolemy-9J0nJ-qa
Open

feat(card): surface Didit duplicate-filter review reasons on /card/activate (qa)#2091
MusabShakeel576 wants to merge 291 commits into
masterfrom
claude/dreamy-ptolemy-9J0nJ-qa

Conversation

@MusabShakeel576

Copy link
Copy Markdown
Contributor

Summary

Cherry-pick of #2090 onto qa.

Didit's new duplicate IP and duplicate device filters are configured to send suspicious applicants to In Review (so they don't reach Rain). The backend already maps that to kycStatus: UNDER_REVIEW and persists the warnings on cardCustomer.diditWarnings, but step 1 of /card/activate returned a fixed generic copy regardless. This surfaces the actual reason.

  • Adds DUPLICATED_IP / DUPLICATED_DEVICE to DIDIT_WARNING_DESCRIPTIONS and tightens POSSIBLE_DUPLICATED_USER wording.
  • getStepDescription UNDER_REVIEW branch renders the warnings list when any are present; falls back to the original "takes a few minutes" copy when warnings are empty.

Button stays "Under Review" + disabled — applicants wait for the reviewer's decision; we don't pre-judge a duplicate as fraud.

Test plan

  • Trigger a Didit session that hits the duplicate IP filter → backend webhook stores kycStatus: UNDER_REVIEW with DUPLICATED_IP in kycWarnings.
  • Land on /card/activate?kycStatus=under_review; verify step 1 description renders:
    Your application is under additional review:
    - This network has already been used to verify another account
    
    We'll update you when the review is complete.
    
  • Same flow with duplicate device → reads "This device has already been used to verify another account".
  • UNDER_REVIEW without warnings (manual review with no surfaced reasons) still shows "Your information is being reviewed. This usually takes a few minutes."
  • Step-1 button stays "Under Review" and disabled in all the above.

Generated by Claude Code

MusabShakeel576 and others added 30 commits April 9, 2026 18:40
…n-back-eXrdB

Simplify navigation logic in country selection flows
Add `freezeOnBlur` to tab screen options on native platforms. Without
this, inactive tab screens continue re-rendering during tab switches,
causing the previous page content to briefly flash before the new tab
appears. `freezeOnBlur` uses react-freeze to suspend rendering of
inactive screens, keeping them visually frozen in their last state.

https://claude.ai/code/session_016VaxaMHLP2fYi3vjVd5U2n
…flash-IqLoo-qa

Fix native tab navigation flashing previous page
Co-authored-by: Musab Shakil <MusabShakeel576@users.noreply.github.com>
Co-authored-by: Musab Shakil <MusabShakeel576@users.noreply.github.com>
…token-sync-db12

Fix vault/principal token mismatches in deposit modal
Co-authored-by: Musab Shakil <MusabShakeel576@users.noreply.github.com>
…-qa-fff4

Restore missing checkCardAccess and LayerZero helpers in qa
Co-authored-by: Musab Shakil <MusabShakeel576@users.noreply.github.com>
(cherry picked from commit a0fd135)
…idit completion

Previously, completing Didit KYC would always redirect to /card/ready
even when Rain KYC was not yet approved (e.g. needsInformation,
needsVerification, pending). Now only redirect to /card/ready when
both Didit and Rain KYC are approved. Otherwise redirect to
/card/activate so the user sees the dynamic step-one button to provide
additional information.

https://claude.ai/code/session_01LnsDaCsxy56ZcmsFaEZKJ7
h-auto works on web (lets flex stretch apply) but not native.
h-full works on native but breaks web layout. Use native:!h-full
and web:!h-auto to handle both platforms correctly.

https://claude.ai/code/session_01QhMD9PevR5HwX1uWbUqShv
…dge quotes

The old Stargate API (stargate.finance/api/v1/quotes) returns 410 Gone,
breaking all deposit-to-card operations (Wallet, Savings, Borrow). Migrated
to the new LayerZero Value Transfer API (transfer.layerzero-api.com/v1/quotes)
which uses POST instead of GET and different parameter/response formats.
The response is transformed back to the legacy format to avoid changes in
all consuming hooks.

https://claude.ai/code/session_01UdFXo6FkqQhCozNm7xXm7U
…ic-IBW3s

Fix KYC redirect logic to show activate page when Rain not approved
…-qa-fff4

Gate Rain card country flow to allowed test users on qa
…ull-master

Use platform-specific height overrides for toast close button
…or-HS5lC

Migrate Stargate API to LayerZero Value Transfer API
…ist-TJWER

Add mul53 to test features whitelist for Rain card testing
The Rain card onboarding, activation, and withdraw flows were gated
behind a hardcoded username whitelist (TEST_FEATURES_ALLOW_LIST).
This removes the isUserAllowedToUseTestFeature check from all card
flows, now relying solely on the backend accessCheck.hasAccess.

https://claude.ai/code/session_0175xemWqTv9ZPJdEqReBkYU
…e API

Replace the HTTP API call to the deprecated stargate.finance endpoint (and
the LayerZero VT API that required auth) with a direct on-chain call to the
Stargate OFT contract's quoteSend function on Fuse, mirroring the backend
approach from commit 5ac004e. This eliminates external API dependencies
for bridge fee quoting.

https://claude.ai/code/session_01UdFXo6FkqQhCozNm7xXm7U
…ad of hardcoded Arbitrum

Rain card uses Base as the funding chain, not Arbitrum (which was for the old
Bridge.xyz provider). Replaced hardcoded dstEid (30110/Arbitrum) with
getStargateChainId(EXPO_PUBLIC_CARD_FUNDING_CHAIN_ID) and hardcoded
to_chain (42161) with the config value in all three card deposit hooks.
Also fixed the default for EXPO_PUBLIC_CARD_FUNDING_CHAIN_KEY from
'arbitrum' to 'base'.

https://claude.ai/code/session_01UdFXo6FkqQhCozNm7xXm7U
… abis/

Since getStargateQuote is now a direct contract call (not an API call),
moved it to lib/utils/stargate.ts alongside the other Stargate utilities
and extracted the quoteSend ABI to lib/abis/StargateOFT.ts. Updated all
6 consumer imports accordingly.

https://claude.ai/code/session_01UdFXo6FkqQhCozNm7xXm7U
…ting-7bsXv

Remove test feature flag checks from card access logic
…ixes-to-qa

fix: deposit to card fixes — direct quoteSend, Base chain, refactor
…ting-7bsXv

Remove unused useCardWithdrawAllowed hook
USDC_STARGATE (0xc6Bc...) is the ERC20 token, not the Stargate pool
contract. The actual pool/OFT at 0xAF54BE5B6eEc24d6BFACf1cce4eaF680A8239398
implements quoteSend and is what CardDepositManager.depositUsingStargate
expects as the oft parameter.

https://claude.ai/code/session_01UdFXo6FkqQhCozNm7xXm7U
When Didit KYC is rejected and Rain was never reached, the step
description showed the generic "Identity verification required"
message. Now uses the kycStatus field from the card status response
to detect Didit rejection and shows "Your identity verification was
declined. Please try again with a valid ID." with a "Retry KYC"
button.

https://claude.ai/code/session_01XVLDUZPDp97k9896H5Lkhj
Show appropriate messages for Didit INCOMPLETE (resubmission needed)
and UNDER_REVIEW states in the card activation step, instead of
falling through to the generic default message. Under review disables
the button so the user waits for the review to complete.

https://claude.ai/code/session_01XVLDUZPDp97k9896H5Lkhj
MusabShakeel576 and others added 27 commits May 17, 2026 16:49
Adds a debug shortcut so QA can preview the welcome popup without
re-activating a card. Only honored when EXPO_PUBLIC_ENVIRONMENT is not
'production'.
…2DeY

Make welcome card image cover full upper section of popup
Throttled info-level captureMessage emits the raw vs filtered Arbitrum
token counts, symbols, addresses, fetch status, and whitelist size each
time tokenBalances runs. Goal: identify whether Arbitrum USDC fails to
show on native because the fetch returns empty, the whitelist filter
drops it, or something downstream. Always emits on fetch rejection;
otherwise throttled to 1/min per session.

Sentry only enables in production (lib/_layout.tsx Sentry.init), so this
needs a prod build to surface events.

TODO: revert once diagnosis is complete.
…nfo on native

React Query's default focusManager only listens to `visibilitychange`,
which never fires on React Native. Its onlineManager has no native
transport at all. Consequences with the current useBalances config:

- `refetchOnWindowFocus: true` is a no-op when the app returns from
  background, so reopening the app doesn't refresh balances — users
  wait for the next 5s `refetchInterval` tick, or longer if JS was
  paused while backgrounded.
- `refetchOnReconnect: true` is a no-op after a network drop.

Bridge both managers in the root layout: AppState → focusManager,
NetInfo → onlineManager. NetInfo is already a direct dependency.
Web behavior is unchanged.
…splay-ktdN0

Wire React Query to native app lifecycle and connectivity
…ass backend validation

The activity API's CreateActivityDto enforces /^[A-Z]{2,10}$/ on `symbol`
(after .toUpperCase()), so 'USDC.e' became 'USDC.E' and was rejected with
400. The frontend's createActivityEvent call swallows errors via console.error,
so the borrow-and-deposit-to-card, bridge-to-card, and repay-and-withdraw
flows never landed in Mongo. Card balance webhooks then fell through to the
fallback path that creates synthetic 'card_transaction' rows.

Token identity is still preserved via metadata.tokenAddress (USDC_STARGATE)
and chainId (fuse.id).

https://claude.ai/code/session_01DQgmuLpKSnWQTDRogWXHZM
…vities pass backend validation"

This reverts commit 5ab3183.
The global axios request interceptor in lib/api.ts injects the Solid
backend Bearer JWT on iOS/Android. That header was leaking into Alchemy
JSON-RPC calls (same axios import) which made Alchemy 401 only on mobile
even when the env key is identical to web. Switch lib/alchemy.ts to a
dedicated axios.create() instance so it never picks up the interceptor.
Mobile is back on the alchemy source after the axios-isolation fix in
the previous commit; the Arbitrum diagnostic Sentry.captureMessage was
only needed to attribute empty balances during the investigation.
…splay-ktdN0-qa

fix(balances): isolate Alchemy axios from global JWT interceptor and drop diagnostics
Users only receive a Rain virtual card and do not order a physical card,
so the trailing clause about a physical card being on its way was misleading.
…up-lAial

Update CardWelcomePopup message text
…T instead of CARD_TRANSACTION

The borrow handler and the SAVINGS/bridge fallthrough were both creating
generic CARD_TRANSACTION "Card Deposit" rows. The backend's card-balance
webhook (findPendingCardDepositActivity) only looks for
[BORROW_AND_DEPOSIT_TO_CARD, BRIDGE_DEPOSIT] to flip pending rows to SUCCESS,
so these rows stayed pending forever and were hidden by the 24h stuck-tx
filter in ActivityTransactions.tsx.

Writing the correct type from the form lets the existing webhook lookup
match, so the activity reaches SUCCESS and stays visible.

https://claude.ai/code/session_01DQgmuLpKSnWQTDRogWXHZM
…-display-P5EGg

Update USDC symbol references from USDC.e to USDC
…y persist

updateActivityEvent was sending `txHash` (not a backend DTO field — silently
dropped by mongoose's strict schema) and omitting `url` entirely. The
backend's UpdateActivityDto declares `hash` and `url`; the schema persists
both. So neither field ever made it into the DB, even though local Zustand
held the values.

Symptom: after a hard refresh / cache clear, the activity-detail page lost
its LayerZero explorer-row link, because the backend's GET /activity
returned an activity with no `hash` or `url` and the page no longer had
the local fallback.

Side effect: useCardDepositPoller, which keys off activity.hash, can now
actually pick up pending card-deposit rows as a fallback when the webhook
is delayed (separate but related fix to broaden its activity types).

https://claude.ai/code/session_01DQgmuLpKSnWQTDRogWXHZM
…fields

fix(activity): send hash and url to backend on update so they actuall…
PageLayout was unmounting the entire savings page whenever isBalanceLoading
or isTransactionsLoading flipped true — which now fires on every vault tab
switch since both queries are keyed by the current vault. On Android this
remounts the reanimated vault cards and analytics chart and feels stuck.

Only show the full-page loader on the very first load (tracked via a ref).
Subsequent vault switches keep the page mounted and render skeletons in
the Total Value / Interest Earned slots while the new vault's data loads.
Also surface a loading state on the Vault Breakdown tab so it stops
flashing "No vault breakdown data available" during the switch.
…loader-oX5Fh-qa

fix(savings): keep page mounted on vault switch, show inline skeletons
…ysical card shipping with Rain's actual displayName rule

Rain's only character restriction is on configuration.displayName for
physical cards (alphanumeric, spaces, periods, hyphens), and is ignored
for virtual cards entirely. The Rain KYC form was rejecting valid
applicants with non-Latin names (Cyrillic, Arabic, CJK) for both virtual
and physical cards.

- RainKyc schema: removed Latin-only regex on firstName/lastName.
- OrderPhysicalCardModal: relaxed the shipping name regex to match Rain's
  documented rule (also allow digits and periods) since these names are
  embossed as the physical card's displayName.
…tion-rain-AeJe0

Refactor name validation for Rain KYC and physical card forms
Fetch and display the safe's native ETH balance on Arbitrum alongside
Ethereum, Fuse, and Base. Users who sent ETH to their Arbitrum safe
address can now see and transfer those funds from the Send flow.
Add Arbitrum network support to balance fetching
Didit's duplicate IP / duplicate device filters are configured to send
suspicious applicants to In Review so they don't reach Rain. Backend
already maps that to kycStatus=UNDER_REVIEW with the warnings preserved
on cardCustomer.diditWarnings, but step 1 of /card/activate returned
the generic "being reviewed, takes a few minutes" copy regardless.
Render the per-warning reasons when present so the applicant knows
what's being checked; keep the original copy as the empty-warnings
fallback. Also adds DUPLICATED_IP / DUPLICATED_DEVICE entries (and
tightens POSSIBLE_DUPLICATED_USER) in DIDIT_WARNING_DESCRIPTIONS.

Cherry-pick of #2090 onto qa.
@vercel

vercel Bot commented May 24, 2026

Copy link
Copy Markdown

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

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
solid-app Ignored Ignored May 24, 2026 1:41pm
solid-app-staging Ignored Ignored May 24, 2026 1:41pm

Request Review

Base automatically changed from qa to master August 13, 2026 11:12
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.

4 participants