Skip to content

feat(rescue-token): add /rescue-token page to recover stuck USDC#2103

Closed
MusabShakeel576 wants to merge 155 commits into
qafrom
claude/trusting-tesla-xaC2V
Closed

feat(rescue-token): add /rescue-token page to recover stuck USDC#2103
MusabShakeel576 wants to merge 155 commits into
qafrom
claude/trusting-tesla-xaC2V

Conversation

@MusabShakeel576
Copy link
Copy Markdown
Contributor

@MusabShakeel576 MusabShakeel576 commented May 28, 2026

Superseded by #2104, which is based on qa directly to avoid pulling in unrelated default-branch commits.

MayankMittal1 and others added 30 commits April 16, 2026 22:39
fix: savings chart, redirect to strategy on click
The card purchase activity header used justify-between, which offset
the merchant title based on the ChevronLeft width, and the location
was pinned with ml-10. Absolutely position the back button so the
title truly centers, and drop the spacer. Also center the location
text and trim merchant_name/description so stray whitespace from the
upstream data doesn't misalign the header.
…splay-xL5yx-master

fix: center merchant name/location and trim whitespace
…escrow-to-master

feat: show cashback escrow status and time left in card activity
…rkflows

Rename useDepositFromEOA -> useDepositFromWallet and add a category prop
('SAVINGS' | 'CARD'). The CARD branch is sponsor-only, uses TransactionType.CARD_DEPOSIT,
and calls createDeposit/bridgeDeposit with category='CARD' so the backend
Connect-Wallet workflows route funds to the Rain Base funding address instead
of the Ethereum vault.

Wire the new hook into CardDepositInternalForm for the production WALLET source
so 'Deposit to card from wallet' works from all 5 supported chains.

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
…Amount

Replace the hardcoded '100' minimum amount passed to useDepositFromWallet
in CardDepositInternalForm with EXPO_PUBLIC_MINIMUM_SPONSOR_AMOUNT from
lib/config, matching how the Savings flow sources the same threshold.

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
Move the CARD category support off the EOA-pull hook and onto
useDepositFromSolidUsdc so card deposits pull from the user's Solid
Turnkey Safe AA (the account is the approver and allowance-granter),
matching the rest of the new deposit flow.

- Revert the useDepositFromEOA rename; restore the original hook as
  unused dead code and revert DepositToVaultForm to import it again.
- useDepositFromSolidUsdc: add category: 'SAVINGS' | 'CARD' prop. When
  category=CARD: target chain is Base, activity type is CARD_DEPOSIT,
  and createDeposit / bridgeDeposit are called with category='CARD'
  so the backend routes funds to the Rain Base funding address.
- CardDepositInternalForm: production WALLET path now uses
  useDepositFromSolidUsdc(USDC, EXPO_PUBLIC_MINIMUM_SPONSOR_AMOUNT, 'CARD').

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
…Category enum

Add DepositCategory enum to lib/types.ts mirroring the backend enum, and
use it in place of the string-literal union in:
- Deposit / BridgeDeposit request types (lib/types.ts)
- useDepositFromSolidUsdc category prop + comparisons + API passes
- CardDepositInternalForm's category argument

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
…ins-earUz-master

feat(card-deposit): support Rain card deposits from all 5 chains
Addresses two issues in the card deposit from Solid wallet flow:

1. When useDepositStore.srcChainId is 0/unset (e.g. after resetDepositFlow
   persisted an unset value), useDepositFromSolidUsdc silently returned
   undefined from deposit(), so the form opened the transaction-status
   screen with no approve UserOp ever submitted and no Turnkey passkey
   prompt. The hook now throws a clear error when srcChainId is falsy,
   and CardDepositInternalForm defaults srcChainId to Base (the card
   funding chain) whenever the stored value is missing or unsupported.

2. Card deposit form now enforces EXPO_PUBLIC_MINIMUM_SPONSOR_AMOUNT via
   a zod refine on the WALLET source (disables the submit button and
   surfaces a validation message) and shows a gasless deposit hint row
   under the destination mirroring DepositToVaultForm's Fuel-icon line.

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
…ins-earUz-master

fix(card-deposit): prevent silent no-op and add minimum-amount UI
Extract the reusable wallet-token picker from SavingsDepositTokenSelector
into a shared WalletTokenSelectorScreen component that accepts filter
props (supportedChainIds, supportedTokenSymbols, onSelect, empty text)
so it can be reused unchanged by the card deposit flow.

- New components/WalletTokenSelector/WalletTokenSelectorScreen.tsx hosts
  the aggregation + filter + render logic previously inlined in the
  savings selector.
- SavingsDepositTokenSelector becomes a thin wrapper that passes the
  active vault's supportedChains / supportedTokens and navigates back to
  DEPOSIT_MODAL.OPEN_FORM on select. No behavioural change.
- New components/Card/CardDepositTokenSelector.tsx wraps the same screen
  with a USDC + 5-chains filter and navigates back to
  CARD_DEPOSIT_MODAL.OPEN_INTERNAL_FORM.
- constants/modals.ts adds CARD_DEPOSIT_MODAL.OPEN_TOKEN_SELECTOR.
- CardDepositModalProvider renders CardDepositTokenSelector when the new
  state is active and wires up the Select-token title + back-button
  routing to the internal form.
- CardDepositInternalForm replaces the earlier "default to Base" fallback
  with a WalletTokenButton that opens the selector. The deposit button is
  disabled until the user picks a token, and the selected token is shown
  in the button label (symbol + chain name).

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
…ins-earUz-master

refactor(card-deposit): reuse savings wallet token selector screen
Drop the separate "Token" row and surface the wallet token selector in
the same flex-row as the amount input, matching the savings deposit
modal layout.

AmountInput now accepts an optional rightSlot prop; CardDepositInternalForm
passes WalletTokenButton as the slot when the WALLET source is active on
production, replacing the static USDC/USDC.e image + label.

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
… WALLET source

- CardDepositTokenSelector was calling useDepositStore.setModal (the savings
  modal store) when a token was selected, which routed the user to the
  savings-flow's Add Funds screen instead of the card deposit internal form.
  Switch to useCardDepositStore.setModal so the card modal transitions back
  to OPEN_INTERNAL_FORM, and set CardDepositSource.WALLET on select so the
  form re-mounts on the Wallet option.
- CardDepositModalProvider's back button from the token selector also now
  explicitly sets source=WALLET before navigating back, so the internal
  form's defaultValues.from reads WALLET (not the BORROW default).

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
…ins-earUz-master

fix(card-deposit): render WalletTokenButton inline inside amount input
Adds "External Wallet" as a fourth option in the card deposit dropdown
(after Borrow against Savings, Savings, and Wallet). When selected, the
card funding address is rendered via the reusable DepositPublicAddress
screen from the Add funds modal with a Base chain USDC description that
links to the token contract on Basescan.

- Extend DepositPublicAddress with optional `address` and `description`
  props so the "Your Solid address" screen can be reused for arbitrary
  deposit addresses.
- Render the address screen inline in CardDepositInternalForm when the
  EXTERNAL source is chosen, hiding amount input, destination, error
  and submit UI for that mode.
…deposit-AtDYW-master

feat(card-deposit): add external wallet option to internal form dropdown
Production "From Wallet" card deposit previously:
- Read USDC balance from the Fuse-hardcoded USDC_STARGATE address, so
  users who picked Ethereum/Polygon/Base/Arbitrum USDC in the token
  selector always saw 0 balance and the Max button pasted 0.
- Displayed "USDC.e" as the symbol on every chain, but the ".e" suffix
  only applies to Fuse's Stargate-bridged USDC; the other four chains
  use canonical USDC.

Read the balance from the Solid Safe AA on the chain picked in the
selector (falling back to the legacy Fuse USDC_STARGATE read when the
user hasn't picked a token yet), and show "USDC.e" only when the
selected chain is Fuse, otherwise "USDC". Consolidate the duplicate
cardDepositSrcChainId/tokenAddress derivations onto the single
balance-read source of truth.

https://claude.ai/code/session_01TDpHy9uTrVX3PTRp9xS9S5
…ins-earUz-master

fix(card-deposit): balance + symbol track the selected wallet chain
The unconditional <View className="flex-1" /> spacer was rendered as a
sibling of the EXTERNAL branch's own flex-1 content wrapper, creating
two stacked flex-1 children in the column. The empty sibling overlaid
the address card and intercepted pointer events, making the "See token
address" link unclickable. Restrict the spacer to non-external modes.
…deposit-AtDYW-master

fix(card-deposit): hide flex spacer when external wallet is selected
…omponent-nmVLt

Replace custom back button with BackButton component
…omponent-nmVLt-master

feat(settings): use rounded BackButton in mobile header
Wrap ResponsiveModal's ScrollView in a KeyboardAvoidingView so the
keyboard pushes modal content up instead of obscuring the focused input
(e.g. the Two-Factor Authentication code entry).

https://claude.ai/code/session_01MGVdxsdT6kXH5nPgJJFLJo
…ut-rWhty-master

fix(modal): keyboard no longer covers OTP input on native
…r4-master

style(toast): style info toast to match success/error (master)
…n-rhp3I

Refactor passkey auth flow to decouple user selection from authentication
MusabShakeel576 and others added 27 commits May 20, 2026 11:58
…pup-43f6c-master

feat(card-onboard): redesign page and add fees and charges popup
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.
- Normalize Lottie tab icon colors to white and drive size from the tab bar so the icons render consistently across platforms
- Render page headers inline instead of via PageLayout's stickyHeader on Activity, Card, Points, and Savings (old)
- Shrink desktop section titles from text-5xl to text-3xl on Card, Rewards, and Savings empty state
- Reword home empty state CTA to "Make your first deposit" and lighten welcome headings to medium
- Nudge the home deposit illustration up slightly on desktop
- Drop unused nested zod entries from package-lock.json

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

chore(ui): polish tab icons, page headers, and empty state copy
…loader-oX5Fh

Fix vault switching UX and add inline loading states
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.
…-master

feat(balances): show native ETH on Arbitrum
…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-master

fix(card): drop Latin-only name validation from Rain KYC and align physical card shipping rule
…/activate

Didit's new duplicate IP and duplicate device filters surface as
DUPLICATED_IP / DUPLICATED_DEVICE risks in the ip_analyses block, which
the backend already extracts into kycWarnings. Map both tags (plus
POSSIBLE_DUPLICATED_USER, ID_DOCUMENT_IN_BLOCKLIST) to UNRETRYABLE_FRAUD_RISKS
so step 1 shows "We couldn't approve your application" with the specific
reason and a "Contact support" button instead of the default "Retry KYC"
that would just hit the same filter again.
Didit's duplicate IP / duplicate device filters are configured to send
suspicious applicants to In Review (not Declined) so they don't reach
Rain. Backend already maps that to kycStatus=UNDER_REVIEW with the
warnings preserved, but the UI was returning the same 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.
…nings

The duplicate IP / duplicate device filters in Didit send applicants to
In Review, never to Declined, so the REJECTED-with-fraud branch added in
fc97af8 never fires in practice. Pull the UNRETRYABLE_FRAUD_RISKS set,
hasUnretryableFraudWarning helper, handleContactSupport callback, and
the alternate REJECTED description; keep the new DUPLICATED_IP /
DUPLICATED_DEVICE entries in DIDIT_WARNING_DESCRIPTIONS and the
UNDER_REVIEW description tweak from f0109dd — those are the pieces that
actually surface the duplicate reason on /card/activate.
Improve KYC duplicate detection messaging and review status
@didit-protocol/sdk-react-native's Expo plugin injects the Podfile entry
pointing at .../sdk-ios/main/DiditSDK.podspec. On 2026-05-25 main was
bumped to 4.0.0, which no longer satisfies the SdkReactNative.podspec
constraint of `DiditSDK ~> 3.2`, so pod install fails on every iOS EAS
build with a CocoaPods version conflict.

Patch the plugin to use the 3.2.13 tagged podspec — the latest 3.2.x
release that still ships the flat DiditSDK.xcframework + OpenSSL.xcframework
the react-native-quick-crypto patch relies on for OpenSSL.
Update DiditSDK podspec URL to use stable version 3.2.13
Commit 7270042 dropped 11 nested zod@3.25.76 entries from
package-lock.json (under @reown/appkit-*, @walletconnect/*, etc.) as
"unused", but they aren't — they're required peer-deps that npm's lock
file resolver records.

Since 7270042 only touched JS, no fresh native iOS build ran (the EAS
fingerprint stayed the same and the workflow reused the cached build).
PR #2093 added a file under patches/, bumping the fingerprint and
triggering the first fresh build since May 20. That build now fails at
"npm ci --include=dev" with "Missing: zod@3.25.76 from lock file".

Regenerate with "npm install --package-lock-only" to put the entries
back. Purely additive diff; no package versions change.
…ries-GDDOC

fix(lockfile): restore nested zod 3.25.76 entries needed by npm ci
…ments

#2093 pinned the DiditSDK pod to 3.2.13, which gets pod install past the
version constraint but introduces a new fault: VerificationError gained a
.retryBlocked case starting in 3.2.10, and @didit-protocol/sdk-react-native@3.2.8's
DiditSdkBridge.swift mapErrorType() switch only handles
.sessionExpired / .networkError / .cameraAccessDenied / .unknown. Xcode
errors out with "switch must be exhaustive" during the Fastlane step.

3.2.9 is the latest DiditSDK iOS release before the enum was extended, so
the wrapper's 4-case switch is still exhaustive against it. Verified by
diffing the swiftinterface across 3.2.8 / 3.2.9 / 3.2.10 / 3.2.13 — the
.retryBlocked case lands in 3.2.10. The wrapper's other switches
(VerificationStatus, VerificationResult) are unchanged across 3.2.x.
…GDDOC

fix(ios): repin DiditSDK pod to 3.2.9 to match wrapper's switch statements
The send form pulled balance from wagmi's useBalance, which has a 5-minute
default staleTime in the app's QueryClient and isn't invalidated by the
safe-account send flow (useSend doesn't go through useTransactionAwait).
After a previous send, the token picker — fed by useWalletTokens (5s
polling + SSE-invalidated) — showed the fresh balance, but selecting a
token brought the user back to a SendForm displaying wagmi's stale cache.

Drop wagmi's useBalance and source the balance from useWalletTokens
instead, looking up the live token by contractAddress + chainId. The
picker and the send screen now read from the same query.

Max button also lost precision: it did `balanceAmount.toString()` where
balanceAmount was Number(formatUnits(wei, decimals)). For balances whose
low-order wei round up when squeezed into a float64, parseUnits later
produced a BigInt above the actual on-chain balance and the transfer
reverted — while Send stayed enabled because the Zod check was a JS-Number
compare. Build the max string from the wei BigInt via formatUnits so it
round-trips through parseUnits exactly, and validate amount <= balance in
wei so the button can't enable for amounts that exceed the balance after
rounding.
…vZYP-master

fix(send): use live token balance and preserve precision on Max
The form uses react-hook-form's onChange mode, which only re-runs the
Zod schema when the Controller's TextInput fires onChange. handleMaxPress
just calls setValue('amount', maxAmount), which writes the value but
doesn't run validation, so isValid stays at its prior value (false if
the user hadn't typed first) and the Review button remains disabled.

Call trigger('amount') after setValue — matches the pattern already
used in CardRepayForm and RegularWithdrawForm for the same problem.
fix(send): trigger validation on Max so Review enables immediately
Adds a /rescue-token screen that lets users move USDC accidentally
sent to their Turnkey signer address (user.walletAddress) into their
Safe (user.safeAddress) on Ethereum, signed via the existing passkey
flow. Displays the stuck balance, destination Safe address, and the
ETH gas required with an info tooltip explaining users can reach out
to the Solid team if they can't cover gas.

https://claude.ai/code/session_01U9boKN8kBrUytLY72BfSbB
Adds a RESCUE_TOKEN transaction type (incoming wallet transfer) and
wires useRescueToken through createActivity/updateActivity so the
recovery shows up in the activity feed: PENDING when the user signs,
PROCESSING once the tx hash is broadcast (with Etherscan link), then
SUCCESS or FAILED based on the receipt.

https://claude.ai/code/session_01U9boKN8kBrUytLY72BfSbB
@vercel
Copy link
Copy Markdown

vercel Bot commented May 28, 2026

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 28, 2026 8:20am
solid-app-staging Ignored Ignored May 28, 2026 8:20am

Request Review

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