feat(rescue-token): add /rescue-token page to recover stuck USDC#2103
Closed
MusabShakeel576 wants to merge 155 commits into
Closed
feat(rescue-token): add /rescue-token page to recover stuck USDC#2103MusabShakeel576 wants to merge 155 commits into
MusabShakeel576 wants to merge 155 commits into
Conversation
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
Add info toast type with blue styling
…r4-master style(toast): style info toast to match success/error (master)
…n-rhp3I Refactor passkey auth flow to decouple user selection from authentication
…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
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
5 tasks
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.
Superseded by #2104, which is based on
qadirectly to avoid pulling in unrelated default-branch commits.