Skip to content

fix: unify all KYC flows through Sumsub via useMultiPhaseKycFlow#1690

Merged
kushagrasarathe merged 11 commits intopeanut-wallet-devfrom
fix/kyc2.0-bugs
Feb 25, 2026
Merged

fix: unify all KYC flows through Sumsub via useMultiPhaseKycFlow#1690
kushagrasarathe merged 11 commits intopeanut-wallet-devfrom
fix/kyc2.0-bugs

Conversation

@kushagrasarathe
Copy link
Contributor

  • Delete Bridge iframe integration: useBridgeKycFlow, InitiateBridgeKYCModal
  • Delete Manteca widget integration: useMantecaKycFlow, InitiateMantecaKYCModal
  • Extract shared useMultiPhaseKycFlow hook + SumsubKycModals component
  • Replace all KYC entry points with inline Sumsub flow:
    • Bridge bank flows use regionIntent: 'STANDARD'
    • Manteca flows use regionIntent: 'LATAM'
  • Add polling fallback in useSumsubKycFlow for missed WebSocket events
  • Relocate KycHistoryEntry type + isKycStatusItem guard to KycStatusItem
  • Net reduction: ~970 lines

- Delete Bridge iframe integration: useBridgeKycFlow, InitiateBridgeKYCModal
- Delete Manteca widget integration: useMantecaKycFlow, InitiateMantecaKYCModal
- Extract shared useMultiPhaseKycFlow hook + SumsubKycModals component
- Replace all KYC entry points with inline Sumsub flow:
  - Bridge bank flows use regionIntent: 'STANDARD'
  - Manteca flows use regionIntent: 'LATAM'
- Add polling fallback in useSumsubKycFlow for missed WebSocket events
- Relocate KycHistoryEntry type + isKycStatusItem guard to KycStatusItem
- Net reduction: ~970 lines

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link

vercel bot commented Feb 25, 2026

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Feb 25, 2026 10:45am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaces provider‑specific KYC hooks/modals with a unified multi‑phase Sumsub KYC flow: adds useMultiPhaseKycFlow, SumsubKycModals, and InitiateKycModal; removes legacy Bridge/Manteca hooks and modals; migrates Add/Withdraw/Claim/Profile flows to the new flow and exposes KYC history types/guard.

Changes

Cohort / File(s) Summary
New flow & modal primitives
src/hooks/useMultiPhaseKycFlow.ts, src/components/Kyc/SumsubKycModals.tsx, src/components/Kyc/InitiateKycModal.tsx
Adds a multi‑phase Sumsub orchestration hook, composed modal renderer, and a small initiate modal component (flow API, ToS handling, phases, timeouts).
Replaced/removed legacy KYC hooks & modals
src/hooks/useBridgeKycFlow.ts, src/hooks/useMantecaKycFlow.ts, src/components/Kyc/InitiateBridgeKYCModal.tsx, src/components/Kyc/InitiateMantecaKYCModal.tsx
Deletes provider‑specific hooks and modal components (WebSocket/message/iframe orchestration and public interfaces).
Flow migration in app pages
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx, src/app/(mobile-ui)/withdraw/manteca/page.tsx, src/app/(mobile-ui)/history/page.tsx
Rewires pages to use useMultiPhaseKycFlow/SumsubKycModals; removes inline KYC modal state and old initiation handlers; updates imports for KYC status guard.
Component migrations (Add/Withdraw/Claim)
src/components/AddMoney/.../MantecaAddMoney.tsx, src/components/AddWithdraw/AddWithdrawCountriesList.tsx, src/components/Claim/Link/*.tsx, src/components/Claim/Link/views/BankFlowManager.view.tsx
Replaces local KYC modal state and WebSocket listeners with sumsubFlow, InitiateKycModal, and SumsubKycModals; initiates KYC via flow.handleInitiateKyc(...).
KYC subsystem refactors
src/components/Kyc/SumsubKycFlow.tsx, src/components/Kyc/KycStatusDrawer.tsx, src/components/Kyc/BridgeTosStep.tsx, src/components/Kyc/KycStatusItem.tsx
Consolidates UIs to the new flow model, standardizes on flow.isLoading/flow.error, replaces many wrappers with SumsubKycModals, and adds KycHistoryEntry + isKycStatusItem.
Home/Profile/history wiring
src/components/Home/HomeHistory.tsx, src/components/Profile/views/RegionsVerification.view.tsx, src/app/(mobile-ui)/history/page.tsx
Updates imports to consume public KYC history/type guard from KycStatusItem; Profile/Regions switched to use the multi‑phase flow and flow error/loading states.
Sumsub flow tweaks
src/hooks/useSumsubKycFlow.ts
Adds a polling fallback while verification progress modal is open (polls Sumsub every 5s) to complement realtime updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: kyc activity updates #1218 — Overlapping KYC flow changes (introduces Sumsub multi‑phase flow, KycStatusItem exports and replaces provider‑specific hooks/modals).

Suggested reviewers

  • jjramirezn
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: unifying all KYC flows through Sumsub via useMultiPhaseKycFlow. It's concise, specific, and reflects the core objective of the PR.
Description check ✅ Passed The description is well-related to the changeset, providing clear details about deleted integrations, new abstractions, regional flow configuration, reliability improvements, and the net reduction in lines. It effectively communicates the scope and intent of the changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/kyc2.0-bugs

Comment @coderabbitai help to get the list of available commands and usage tips.

When user clicks "Accept terms of service" from the activity feed,
auto-fetch the ToS link and open the iframe directly instead of
showing a redundant "Accept Terms" confirmation modal first. The
modal now only appears as an error fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
src/components/AddMoney/components/MantecaAddMoney.tsx (1)

67-70: No onKycSuccess or onManualClose callbacks — user gets no post-KYC feedback.

The sumsubFlow is created without onKycSuccess or onManualClose. After KYC completes, completeFlow() in the hook will reset internal state but won't trigger any UI transition in this component. The user will return to the amount input step with no toast, modal, or visual confirmation that verification succeeded.

If this is intentional (user just continues filling the form), it works because isUserMantecaKycApproved will update after the hook's internal fetchUser(). But it may feel abrupt to the user.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/AddMoney/components/MantecaAddMoney.tsx` around lines 67 - 70,
The sumsubFlow created by useMultiPhaseKycFlow lacks onKycSuccess and
onManualClose handlers so users receive no post-KYC feedback; add onKycSuccess
to call completeFlow(), trigger a success toast/modal (or advance to the next UI
step) and refresh or re-fetch user state (so isUserMantecaKycApproved updates
immediately), and add onManualClose to gracefully close the KYC UI and show a
cancellation/next-step hint; update the sumsubFlow instantiation to include
onKycSuccess and onManualClose callbacks that perform these actions.
src/components/Profile/views/RegionsVerification.view.tsx (1)

175-177: Error display rendered outside the drawer/modal hierarchy.

The flow.error paragraph (line 175) renders at the bottom of the page layout, below all modals. If the error occurs while a modal is open (e.g., StartVerificationModal), the user won't see it since it's behind the modal overlay. Consider displaying the error inside the relevant modal or as a toast.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Profile/views/RegionsVerification.view.tsx` around lines 175 -
177, The error paragraph currently renders outside modal hierarchy so it's
hidden behind overlays; remove the external "{flow.error}" <p> and instead
surface errors inside the modal component or via the app toast system: pass the
error down to SumsubKycModals (e.g., add an error prop like error={flow.error})
or into the specific modal component such as StartVerificationModal, and render
the message within that modal's markup (or call the toast helper from where
flow.error is set) so users see it while a modal is open.
src/app/(mobile-ui)/withdraw/manteca/page.tsx (1)

190-193: KYC guard returns early without user feedback.

When !isUserMantecaKycApproved, the handler calls sumsubFlow.handleInitiateKyc() and returns. The Sumsub SDK will open, which serves as implicit feedback. After successful KYC the user needs to click "Review" again — consider whether a brief toast or inline message would improve the UX, but this is a minor polish item.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/withdraw/manteca/page.tsx around lines 190 - 193, The
guard for isUserMantecaKycApproved returns immediately after calling
sumsubFlow.handleInitiateKyc(), leaving no explicit user feedback; update the
handler (the function that calls sumsubFlow.handleInitiateKyc()) to show a brief
UI feedback before returning — e.g., trigger your existing toast/notification
helper (or set a local state flag to render an inline message) saying something
like "KYC flow launched — please complete verification and tap Review again",
then call sumsubFlow.handleInitiateKyc() and return; reference
isUserMantecaKycApproved and sumsubFlow.handleInitiateKyc() to locate where to
add the notification.
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)

64-64: Remove unused isKycModalOpen state.

The state is only written to (lines 56, 59, 290) but never read. After migrating to the new KYC modal system, this state became obsolete and should be cleaned up along with its setter calls.

♻️ Remove dead state
-    const [isKycModalOpen, setIsKycModalOpen] = useState(false)

Remove the setter calls from:

  • Line 56: onKycSuccess callback in sumsubFlow
  • Line 59: onManualClose callback in sumsubFlow
  • Line 290: NavHeader onPrev handler
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx` at line 64, Remove
the dead React state isKycModalOpen and its setter setIsKycModalOpen from
AddWithdrawCountriesList: delete the useState declaration and remove all
invocations of setIsKycModalOpen in the sumsubFlow callbacks (onKycSuccess and
onManualClose) and the NavHeader onPrev handler so the component no longer
writes to a nonexistent/unused state; keep existing KYC modal integration logic
(sumsubFlow, NavHeader handlers) intact otherwise.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Kyc/SumsubKycModals.tsx`:
- Around line 18-46: SumsubKycModals currently can render nothing when KYC
initiation fails; add a visible error surface that displays flow.error whenever
flow.error is set and both flow.showWrapper and flow.isModalOpen are false so
callers get feedback. Implement this by conditionally rendering an error block
inside SumsubKycModals (e.g., a small Alert or div) when flow.error &&
!flow.showWrapper && !flow.isModalOpen, show the error text and provide actions
that call existing handlers like flow.refreshToken for "Retry" and
flow.handleSdkClose or flow.handleModalClose for "Dismiss"/close, ensuring the
UI is consistent with the other modal flows.

In `@src/hooks/useMultiPhaseKycFlow.ts`:
- Around line 84-89: The flow enters the 'bridge_tos' modal phase without
starting rail tracking, which can leave the phase stuck; update the
useMultiPhaseKycFlow effect so that when you setModalPhase('bridge_tos') (where
setForceShowModal(true) and clearPreparingTimer() are called) you also call
startTracking() immediately; apply the same change to the other branch
referenced around lines 167-185 so any transition into 'bridge_tos' always
invokes startTracking(), ensuring the rail-status watcher in SumsubKycFlow
resumes and drives subsequent phase transitions.

In `@src/hooks/useSumsubKycFlow.ts`:
- Around line 89-104: The polling function pollStatus can overlap because
setInterval fires regardless of async completion; change polling to serialize
requests by introducing a guard (e.g., isPollingRef or a local boolean) or
convert to a self-scheduling loop using await + setTimeout so you only call
initiateSumsubKyc when the previous call finished; update references to
initiateSumsubKyc, regionIntentRef, levelNameRef and setLiveKycStatus
accordingly and ensure you still clear the timeout/interval on cleanup to avoid
leaks.

---

Nitpick comments:
In `@src/app/`(mobile-ui)/withdraw/manteca/page.tsx:
- Around line 190-193: The guard for isUserMantecaKycApproved returns
immediately after calling sumsubFlow.handleInitiateKyc(), leaving no explicit
user feedback; update the handler (the function that calls
sumsubFlow.handleInitiateKyc()) to show a brief UI feedback before returning —
e.g., trigger your existing toast/notification helper (or set a local state flag
to render an inline message) saying something like "KYC flow launched — please
complete verification and tap Review again", then call
sumsubFlow.handleInitiateKyc() and return; reference isUserMantecaKycApproved
and sumsubFlow.handleInitiateKyc() to locate where to add the notification.

In `@src/components/AddMoney/components/MantecaAddMoney.tsx`:
- Around line 67-70: The sumsubFlow created by useMultiPhaseKycFlow lacks
onKycSuccess and onManualClose handlers so users receive no post-KYC feedback;
add onKycSuccess to call completeFlow(), trigger a success toast/modal (or
advance to the next UI step) and refresh or re-fetch user state (so
isUserMantecaKycApproved updates immediately), and add onManualClose to
gracefully close the KYC UI and show a cancellation/next-step hint; update the
sumsubFlow instantiation to include onKycSuccess and onManualClose callbacks
that perform these actions.

In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx`:
- Line 64: Remove the dead React state isKycModalOpen and its setter
setIsKycModalOpen from AddWithdrawCountriesList: delete the useState declaration
and remove all invocations of setIsKycModalOpen in the sumsubFlow callbacks
(onKycSuccess and onManualClose) and the NavHeader onPrev handler so the
component no longer writes to a nonexistent/unused state; keep existing KYC
modal integration logic (sumsubFlow, NavHeader handlers) intact otherwise.

In `@src/components/Profile/views/RegionsVerification.view.tsx`:
- Around line 175-177: The error paragraph currently renders outside modal
hierarchy so it's hidden behind overlays; remove the external "{flow.error}" <p>
and instead surface errors inside the modal component or via the app toast
system: pass the error down to SumsubKycModals (e.g., add an error prop like
error={flow.error}) or into the specific modal component such as
StartVerificationModal, and render the message within that modal's markup (or
call the toast helper from where flow.error is set) so users see it while a
modal is open.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c214dc2 and e8a9769.

📒 Files selected for processing (19)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/history/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/components/AddMoney/components/MantecaAddMoney.tsx
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/Claim/Link/MantecaFlowManager.tsx
  • src/components/Claim/Link/views/BankFlowManager.view.tsx
  • src/components/Home/HomeHistory.tsx
  • src/components/Kyc/InitiateBridgeKYCModal.tsx
  • src/components/Kyc/InitiateMantecaKYCModal.tsx
  • src/components/Kyc/KycStatusDrawer.tsx
  • src/components/Kyc/KycStatusItem.tsx
  • src/components/Kyc/SumsubKycFlow.tsx
  • src/components/Kyc/SumsubKycModals.tsx
  • src/components/Profile/views/RegionsVerification.view.tsx
  • src/hooks/useBridgeKycFlow.ts
  • src/hooks/useMantecaKycFlow.ts
  • src/hooks/useMultiPhaseKycFlow.ts
  • src/hooks/useSumsubKycFlow.ts
💤 Files with no reviewable changes (4)
  • src/hooks/useBridgeKycFlow.ts
  • src/components/Kyc/InitiateMantecaKYCModal.tsx
  • src/components/Kyc/InitiateBridgeKYCModal.tsx
  • src/hooks/useMantecaKycFlow.ts

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/Kyc/BridgeTosStep.tsx (1)

96-126: ⚠️ Potential issue | 🟡 Minor

isLoading is tracked but never surfaced — blank render during initial fetch.

When visible flips to true and handleAcceptTerms is in-flight (isLoading=true, error=null, tosLink=null, showIframe=false), both conditional branches evaluate to false and the component renders an empty fragment. There is no spinner, skeleton, or any visual cue for the user until either the iframe appears or an error is shown.

💡 Suggested fix — render a loading state
     return (
         <>
+            {isLoading && !showIframe && (
+                <ActionModal
+                    visible={true}
+                    onClose={onSkip}
+                    icon={'clock' as IconName}
+                    title="Loading terms…"
+                    description="Please wait while we fetch the terms of service."
+                    ctas={[]}
+                />
+            )}
             {/* only show modal on error — normal flow goes straight to iframe */}
             {error && !showIframe && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Kyc/BridgeTosStep.tsx` around lines 96 - 126, The component
currently renders nothing when isLoading is true because neither the error modal
nor the iframe are shown; update the JSX in BridgeTosStep (the render that uses
error, showIframe, tosLink, isLoading, ActionModal, and IframeWrapper) to render
a loading UI when isLoading is true — for example show a spinner/skeleton or a
disabled ActionModal/placeholder with a message while handleAcceptTerms is
in-flight; ensure the loading state appears before tosLink or error are set and
keep existing onSkip and handleIframeClose behavior unchanged.
🧹 Nitpick comments (1)
src/components/Kyc/BridgeTosStep.tsx (1)

27-35: Hoist handleAcceptTerms above the useEffect to eliminate the eslint-disable.

The useEffect on line 27 calls handleAcceptTerms, which is const-declared five lines later (line 37). This is safe at runtime (the effect callback executes after the full render, past the TDZ), but it forces the eslint-disable-line react-hooks/exhaustive-deps comment. Moving the useCallback declaration before the useEffect allows handleAcceptTerms to be listed in the dependency array cleanly.

♻️ Proposed reorder
+    const handleAcceptTerms = useCallback(async () => {
+        setIsLoading(true)
+        setError(null)
+        try {
+            const response = await getBridgeTosLink()
+            if (response.error || !response.data?.tosLink) {
+                setError(response.error || 'Could not load terms. You can accept them later from your activity feed.')
+                return
+            }
+            setTosLink(response.data.tosLink)
+            setShowIframe(true)
+        } catch {
+            setError('Something went wrong. You can accept terms later from your activity feed.')
+        } finally {
+            setIsLoading(false)
+        }
+    }, [])
+
     // auto-fetch ToS link when step becomes visible so the iframe opens directly
     // (skips the intermediate "Accept Terms" confirmation modal)
     useEffect(() => {
         if (visible) {
             handleAcceptTerms()
         } else {
             setShowIframe(false)
             setTosLink(null)
             setError(null)
         }
-    }, [visible]) // eslint-disable-line react-hooks/exhaustive-deps
+    }, [visible, handleAcceptTerms])
-
-    const handleAcceptTerms = useCallback(async () => {
-        ...
-    }, [])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Kyc/BridgeTosStep.tsx` around lines 27 - 35, The effect calls
handleAcceptTerms before its declaration which forced an eslint-disable; move
the handleAcceptTerms useCallback declaration above the useEffect so you can add
handleAcceptTerms to the dependency array (remove the eslint-disable-line), and
ensure handleAcceptTerms' own dependency array includes any values it reads
(e.g., setShowIframe, setTosLink, setError, visible or other referenced
state/props) so the hook dependencies are correct.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/components/Kyc/BridgeTosStep.tsx`:
- Around line 96-126: The component currently renders nothing when isLoading is
true because neither the error modal nor the iframe are shown; update the JSX in
BridgeTosStep (the render that uses error, showIframe, tosLink, isLoading,
ActionModal, and IframeWrapper) to render a loading UI when isLoading is true —
for example show a spinner/skeleton or a disabled ActionModal/placeholder with a
message while handleAcceptTerms is in-flight; ensure the loading state appears
before tosLink or error are set and keep existing onSkip and handleIframeClose
behavior unchanged.

---

Nitpick comments:
In `@src/components/Kyc/BridgeTosStep.tsx`:
- Around line 27-35: The effect calls handleAcceptTerms before its declaration
which forced an eslint-disable; move the handleAcceptTerms useCallback
declaration above the useEffect so you can add handleAcceptTerms to the
dependency array (remove the eslint-disable-line), and ensure handleAcceptTerms'
own dependency array includes any values it reads (e.g., setShowIframe,
setTosLink, setError, visible or other referenced state/props) so the hook
dependencies are correct.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8a9769 and 47b2661.

📒 Files selected for processing (1)
  • src/components/Kyc/BridgeTosStep.tsx

…on mount

- Add InitiateKycModal shown before opening Sumsub SDK in Manteca flows
- Remove regionIntent from hook initialization in all consumers to prevent
  useSumsubKycFlow from calling initiateSumsubKyc() on mount (which created
  backend UserKycVerification records even when user never started KYC)
- Pass regionIntent at call time: handleInitiateKyc('LATAM'/'STANDARD')

Fixes: KYC auto-opening without confirmation, phantom pending KYC entries

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/AddMoney/components/MantecaAddMoney.tsx (1)

1-225: ⚠️ Potential issue | 🟡 Minor

Formatting check is failing for this file.

CI reports style issues here. Please run Prettier and commit the formatted output before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/AddMoney/components/MantecaAddMoney.tsx` around lines 1 - 225,
Formatting in MantecaAddMoney.tsx is failing CI; run your project's Prettier
configuration and reformat this file (MantecaAddMoney component) then commit the
changes so the style checks pass. Specifically, run the project's formatting
script (e.g., npm/yarn prettier or the configured format command), ensure the
file containing the MantecaAddMoney component and its imports/JSX are
reformatted, and re-run CI/lint to confirm the formatting error is resolved
before merging.
🧹 Nitpick comments (2)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (1)

59-59: Remove obsolete local KYC modal state/handlers.

isKycModalOpen, handleKycSuccess, and handleKycModalClose are no longer part of the active modal rendering path after the Sumsub flow migration.

Also applies to: 77-82, 278-285

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx at line 59, Remove the
obsolete local KYC modal state and handlers: delete the useState declaration for
isKycModalOpen and the functions handleKycSuccess and handleKycModalClose from
the component (they are no longer used after the Sumsub flow migration), and
remove any JSX or props that reference these symbols (e.g., KYC modal component
props or event handlers). Ensure you also remove any related imports or
variables that become unused as a result and run a quick type/lint check to
clean up leftover references to isKycModalOpen, handleKycSuccess, and
handleKycModalClose.
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)

56-61: Consider removing leftover isKycModalOpen state.

This local state no longer controls modal rendering in the new flow and now adds noise to the component logic.

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx` around lines 56 -
61, Remove the now-unused local state isKycModalOpen and its setter
setIsKycModalOpen from the component; delete the useState declaration and every
usage (including in the onKycSuccess and onManualClose callbacks) and instead
rely solely on the new flow control (e.g., setView('form') inside onKycSuccess).
Update onKycSuccess to only call setView('form') and onManualClose to perform
the new flow-specific close behavior (or be removed if redundant), and ensure no
props or JSX still reference isKycModalOpen or setIsKycModalOpen.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/Claim/Link/MantecaFlowManager.tsx`:
- Around line 44-48: The useEffect currently only opens the KYC modal when
isUserMantecaKycApproved is false and never closes it when approval changes;
update the effect that references isUserMantecaKycApproved to set the modal
state to the boolean inverse of the approval status (e.g., call
setShowKycModal(!isUserMantecaKycApproved)) so showKycModal stays synchronized
in both directions; touch the useEffect that uses isUserMantecaKycApproved and
the setShowKycModal state updater to implement this change.

---

Outside diff comments:
In `@src/components/AddMoney/components/MantecaAddMoney.tsx`:
- Around line 1-225: Formatting in MantecaAddMoney.tsx is failing CI; run your
project's Prettier configuration and reformat this file (MantecaAddMoney
component) then commit the changes so the style checks pass. Specifically, run
the project's formatting script (e.g., npm/yarn prettier or the configured
format command), ensure the file containing the MantecaAddMoney component and
its imports/JSX are reformatted, and re-run CI/lint to confirm the formatting
error is resolved before merging.

---

Nitpick comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Line 59: Remove the obsolete local KYC modal state and handlers: delete the
useState declaration for isKycModalOpen and the functions handleKycSuccess and
handleKycModalClose from the component (they are no longer used after the Sumsub
flow migration), and remove any JSX or props that reference these symbols (e.g.,
KYC modal component props or event handlers). Ensure you also remove any related
imports or variables that become unused as a result and run a quick type/lint
check to clean up leftover references to isKycModalOpen, handleKycSuccess, and
handleKycModalClose.

In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx`:
- Around line 56-61: Remove the now-unused local state isKycModalOpen and its
setter setIsKycModalOpen from the component; delete the useState declaration and
every usage (including in the onKycSuccess and onManualClose callbacks) and
instead rely solely on the new flow control (e.g., setView('form') inside
onKycSuccess). Update onKycSuccess to only call setView('form') and
onManualClose to perform the new flow-specific close behavior (or be removed if
redundant), and ensure no props or JSX still reference isKycModalOpen or
setIsKycModalOpen.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 47b2661 and 9af38d1.

📒 Files selected for processing (7)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/app/(mobile-ui)/withdraw/manteca/page.tsx
  • src/components/AddMoney/components/MantecaAddMoney.tsx
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/Claim/Link/MantecaFlowManager.tsx
  • src/components/Claim/Link/views/BankFlowManager.view.tsx
  • src/components/Kyc/InitiateKycModal.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/AddMoney/components/MantecaAddMoney.tsx`:
- Around line 196-199: In onVerify handler (the onVerify prop in
MantecaAddMoney), don't call setShowKycModal(false) before starting KYC;
instead, first await sumsubFlow.handleInitiateKyc('LATAM'), check for success
(or catch errors), and only call setShowKycModal(false) after a successful
initiation; on failure keep the modal open and surface an error (e.g., set an
error state or show toast). Reference: onVerify handler, setShowKycModal, and
sumsubFlow.handleInitiateKyc.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9af38d1 and fd28c3b.

📒 Files selected for processing (1)
  • src/components/AddMoney/components/MantecaAddMoney.tsx

- Remove collectUserDetails step from bridge bank onramp flow (page.tsx)
  Non-KYC'd users now go directly to KYC step
- Remove updateUserById calls for name/email before handleInitiateKyc
  in AddWithdrawCountriesList and BankFlowManager
- Sumsub SDK now collects name and email, backend fills them in DB

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/components/Claim/Link/views/BankFlowManager.view.tsx (1)

69-70: ⚠️ Potential issue | 🟡 Minor

Remove the unused destructure of showVerificationModal; keep setShowVerificationModal calls as they properly communicate state to parent components.

showVerificationModal (destructured as isKycModalOpen) is never read within BankFlowManager, so the destructure can be removed. However, setShowVerificationModal (aliased as setIsKycModalOpen) is correctly used in both onKycSuccess and onManualClose to signal to parent components that the KYC modal should close. Keep the setter calls and remove only the unused state variable destructure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Claim/Link/views/BankFlowManager.view.tsx` around lines 69 -
70, In BankFlowManager remove the unused destructure showVerificationModal
(aliased as isKycModalOpen) from the props but keep the setter; keep
setShowVerificationModal (aliased as setIsKycModalOpen) intact since it’s used
in onKycSuccess and onManualClose to notify parents — update the component
props/destructure to only pull setIsKycModalOpen and remove isKycModalOpen
references.
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (1)

1-1: ⚠️ Potential issue | 🟡 Minor

Next.js version 16.0.10 does not exist — verify the pinned version.

The library context lists next@16.0.10, which is not a valid release. As of February 2026, the latest stable versions are v15.5.12 (main stable line) and v16.1.6 (16.x Active LTS line). If the package.json pins Next.js to 16.x, confirm that the intended version is v16.1.6 or another valid release in that line.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx at line 1, The project
lists an invalid Next.js version "next@16.0.10"; open package.json and update
the "next" dependency (and any lockfile or package-resolutions) to a valid
release in the 16.x line (for example "16.1.6") or to the correct intended
version, then run your package manager (npm/yarn/pnpm) to regenerate the
lockfile and verify no other files (e.g., package-lock.json, yarn.lock,
pnpm-lock.yaml) still reference 16.0.10; ensure package.json's
"dependencies.next" and any CI/engine/version checks are consistent.
🧹 Nitpick comments (4)
src/components/Claim/Link/views/BankFlowManager.view.tsx (1)

83-92: onKycSuccess inline function causes unnecessary churn inside useMultiPhaseKycFlow.

Because onKycSuccess is an unstabilized inline async arrow (no useCallback), useMultiPhaseKycFlow.completeFlow — which lists onKycSuccess in its useCallback dependency array — is recreated on every render of BankFlowManager. This cascades to every callback that depends on completeFlow inside the hook (handleSumsubApproved, handleSkipTerms, handleModalClose).

♻️ Proposed fix
+    const handleKycSuccess = useCallback(async () => {
+        if (justCompletedKyc) return
+        setIsKycModalOpen(false)
+        await fetchUser()
+        setJustCompletedKyc(true)
+        setClaimBankFlowStep(ClaimBankFlowStep.BankDetailsForm)
+    }, [justCompletedKyc, fetchUser, setIsKycModalOpen, setJustCompletedKyc, setClaimBankFlowStep])
+
     const sumsubFlow = useMultiPhaseKycFlow({
-        onKycSuccess: async () => {
-            if (justCompletedKyc) return
-            setIsKycModalOpen(false)
-            await fetchUser()
-            setJustCompletedKyc(true)
-            setClaimBankFlowStep(ClaimBankFlowStep.BankDetailsForm)
-        },
-        onManualClose: () => setIsKycModalOpen(false),
+        onKycSuccess: handleKycSuccess,
+        onManualClose: () => setIsKycModalOpen(false),
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/Claim/Link/views/BankFlowManager.view.tsx` around lines 83 -
92, The inline async onKycSuccess passed into useMultiPhaseKycFlow inside
BankFlowManager causes unstable references and forces recreate of
useMultiPhaseKycFlow.completeFlow and its dependents (handleSumsubApproved,
handleSkipTerms, handleModalClose); refactor onKycSuccess into a stable callback
using useCallback (e.g., define const onKycSuccess = useCallback(async () => {
if (justCompletedKyc) return; setIsKycModalOpen(false); await fetchUser();
setJustCompletedKyc(true);
setClaimBankFlowStep(ClaimBankFlowStep.BankDetailsForm); }, [justCompletedKyc,
fetchUser, setIsKycModalOpen, setJustCompletedKyc, setClaimBankFlowStep]) and
pass that to useMultiPhaseKycFlow so completeFlow and downstream callbacks stop
being recreated on every render.
src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (3)

174-178: Document the WebSocket fallback intent to clarify why two KYC-completion paths coexist.

Both onKycSuccess (lines 68–70) and this effect advance the step to 'inputAmount' on KYC approval. The duplication is intentional (SDK event vs. WebSocket update as fallback for missed events), but a brief comment prevents future readers from treating one path as dead code.

♻️ Suggested clarifying comment
+    // Fallback: advance past KYC via WebSocket status update in case the
+    // Sumsub SDK success event (onKycSuccess above) was missed or delayed.
     useEffect(() => {
         if (urlState.step === 'kyc' && liveKycStatus === 'approved') {
             setUrlState({ step: 'inputAmount' })
         }
     }, [liveKycStatus, urlState.step, setUrlState])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx around lines 174 -
178, Add a short inline comment above the useEffect that checks liveKycStatus
(the effect that updates urlState via setUrlState when urlState.step === 'kyc'
and liveKycStatus === 'approved') explaining that this is a WebSocket-based
fallback to advance the flow when the SDK callback onKycSuccess may be missed;
clarify that both onKycSuccess and this useEffect are intentional and
complementary (SDK event vs. WS update) to avoid future removal as dead code.

174-178: Duplicate KYC-completion advance — consider consolidating.

Both onKycSuccess (lines 68–70) and this useEffect advance the step to 'inputAmount' when KYC succeeds. onKycSuccess handles the Sumsub SDK success event; this effect handles the WebSocket liveKycStatus update. The WebSocket path is a useful fallback for missed SDK events, but the intent is clearer if a comment documents it as such.

♻️ Suggested clarifying comment
+    // Fallback: advance past KYC via WebSocket status update in case the
+    // Sumsub SDK success event (onKycSuccess) was missed.
     useEffect(() => {
         if (urlState.step === 'kyc' && liveKycStatus === 'approved') {
             setUrlState({ step: 'inputAmount' })
         }
     }, [liveKycStatus, urlState.step, setUrlState])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx around lines 174 -
178, The effect watching liveKycStatus duplicates the step-advance already
handled by onKycSuccess; update the useEffect (the block that checks if
urlState.step === 'kyc' && liveKycStatus === 'approved' and calls setUrlState({
step: 'inputAmount' })) to include a concise comment stating it's a fallback for
missed Sumsub SDK events (i.e., WebSocket-driven fallback) so intent is clear,
and keep the logic as-is to preserve robustness; reference onKycSuccess,
liveKycStatus, urlState.step and setUrlState when adding the comment.

274-278: eslint-disable masks a real missing dependency — memoize handleInitiateKyc in the hook or add it to deps.

sumsubFlow.handleInitiateKyc is called inside the effect but omitted from the dependency array with // eslint-disable-line react-hooks/exhaustive-deps. If useMultiPhaseKycFlow doesn't wrap handleInitiateKyc in useCallback, every render produces a new function reference and the effect captures a stale one (never re-runs to pick it up since only urlState.step is watched).

The lint suppression should be removed in favor of either:

  • ensuring handleInitiateKyc is a stable useCallback inside useMultiPhaseKycFlow and adding it to deps, or
  • memoizing the callback at the call site before passing it in.
♻️ Proposed fix
-    useEffect(() => {
-        if (urlState.step === 'kyc') {
-            sumsubFlow.handleInitiateKyc('STANDARD')
-        }
-    }, [urlState.step]) // eslint-disable-line react-hooks/exhaustive-deps
+    useEffect(() => {
+        if (urlState.step === 'kyc') {
+            sumsubFlow.handleInitiateKyc('STANDARD')
+        }
+    }, [urlState.step, sumsubFlow.handleInitiateKyc])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx around lines 274 -
278, The effect is suppressing a real dependency: remove the "//
eslint-disable-line react-hooks/exhaustive-deps" and ensure the function
reference is stable by either (A) updating useMultiPhaseKycFlow to wrap
sumsubFlow.handleInitiateKyc in useCallback so it can be safely added to the
effect deps, or (B) memoizing handleInitiateKyc at the call site (e.g., const
initiate = useCallback(() => sumsubFlow.handleInitiateKyc('STANDARD'),
[sumsubFlow]) ) and then change the effect to depend on urlState.step and the
stable function; reference sumsubFlow.handleInitiateKyc and the useEffect that
currently reads urlState.step to apply the fix.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Line 1: The project lists an invalid Next.js version "next@16.0.10"; open
package.json and update the "next" dependency (and any lockfile or
package-resolutions) to a valid release in the 16.x line (for example "16.1.6")
or to the correct intended version, then run your package manager
(npm/yarn/pnpm) to regenerate the lockfile and verify no other files (e.g.,
package-lock.json, yarn.lock, pnpm-lock.yaml) still reference 16.0.10; ensure
package.json's "dependencies.next" and any CI/engine/version checks are
consistent.

In `@src/components/Claim/Link/views/BankFlowManager.view.tsx`:
- Around line 69-70: In BankFlowManager remove the unused destructure
showVerificationModal (aliased as isKycModalOpen) from the props but keep the
setter; keep setShowVerificationModal (aliased as setIsKycModalOpen) intact
since it’s used in onKycSuccess and onManualClose to notify parents — update the
component props/destructure to only pull setIsKycModalOpen and remove
isKycModalOpen references.

---

Nitpick comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 174-178: Add a short inline comment above the useEffect that
checks liveKycStatus (the effect that updates urlState via setUrlState when
urlState.step === 'kyc' and liveKycStatus === 'approved') explaining that this
is a WebSocket-based fallback to advance the flow when the SDK callback
onKycSuccess may be missed; clarify that both onKycSuccess and this useEffect
are intentional and complementary (SDK event vs. WS update) to avoid future
removal as dead code.
- Around line 174-178: The effect watching liveKycStatus duplicates the
step-advance already handled by onKycSuccess; update the useEffect (the block
that checks if urlState.step === 'kyc' && liveKycStatus === 'approved' and calls
setUrlState({ step: 'inputAmount' })) to include a concise comment stating it's
a fallback for missed Sumsub SDK events (i.e., WebSocket-driven fallback) so
intent is clear, and keep the logic as-is to preserve robustness; reference
onKycSuccess, liveKycStatus, urlState.step and setUrlState when adding the
comment.
- Around line 274-278: The effect is suppressing a real dependency: remove the
"// eslint-disable-line react-hooks/exhaustive-deps" and ensure the function
reference is stable by either (A) updating useMultiPhaseKycFlow to wrap
sumsubFlow.handleInitiateKyc in useCallback so it can be safely added to the
effect deps, or (B) memoizing handleInitiateKyc at the call site (e.g., const
initiate = useCallback(() => sumsubFlow.handleInitiateKyc('STANDARD'),
[sumsubFlow]) ) and then change the effect to depend on urlState.step and the
stable function; reference sumsubFlow.handleInitiateKyc and the useEffect that
currently reads urlState.step to apply the fix.

In `@src/components/Claim/Link/views/BankFlowManager.view.tsx`:
- Around line 83-92: The inline async onKycSuccess passed into
useMultiPhaseKycFlow inside BankFlowManager causes unstable references and
forces recreate of useMultiPhaseKycFlow.completeFlow and its dependents
(handleSumsubApproved, handleSkipTerms, handleModalClose); refactor onKycSuccess
into a stable callback using useCallback (e.g., define const onKycSuccess =
useCallback(async () => { if (justCompletedKyc) return;
setIsKycModalOpen(false); await fetchUser(); setJustCompletedKyc(true);
setClaimBankFlowStep(ClaimBankFlowStep.BankDetailsForm); }, [justCompletedKyc,
fetchUser, setIsKycModalOpen, setJustCompletedKyc, setClaimBankFlowStep]) and
pass that to useMultiPhaseKycFlow so completeFlow and downstream callbacks stop
being recreated on every render.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd28c3b and 753b695.

📒 Files selected for processing (3)
  • src/app/(mobile-ui)/add-money/[country]/bank/page.tsx
  • src/components/AddWithdraw/AddWithdrawCountriesList.tsx
  • src/components/Claim/Link/views/BankFlowManager.view.tsx

No longer needed — name and email are now collected by Sumsub SDK.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kushagrasarathe and others added 2 commits February 25, 2026 14:38
- Add optimistic local state to BridgeTosReminder so it hides
  immediately after ToS acceptance (backend rail transition is async)
- Remove BridgeTosReminder from KycCompleted drawer (avoid duplication)
- Consolidate duplicate KYC activity entries into one per region
  (STANDARD, LATAM) via new groupKycByRegion() utility
- Add region prop to KycStatusItem/KycStatusDrawer for region-aware titles

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kushagrasarathe
Copy link
Contributor Author

@coderabbitai review in depth.

Evaluate:

  • risk of introducing new bugs and breaking existing functionality
  • performance impact and latency
  • code quality and DRY

Also, write a very concise testing guide for QA

@kushagrasarathe kushagrasarathe merged commit 4587440 into peanut-wallet-dev Feb 25, 2026
5 checks passed
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 25, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

echo "=== Examining useMultiPhaseKycFlow state machine ==="
cat src/hooks/useMultiPhaseKycFlow.ts

echo -e "\n=== Examining useSumsubKycFlow polling implementation ==="
cat src/hooks/useSumsubKycFlow.ts

echo -e "\n=== Checking all KYC entry points for consistency ==="
rg "handleInitiateKyc" --type tsx -A 2 -B 2

echo -e "\n=== Finding regionIntent usage patterns ==="
rg "regionIntent.*STANDARD|regionIntent.*LATAM" -A 1 -B 1

Length of output: 19501


🏁 Script executed:

#!/bin/bash

echo "=== Checking error handling patterns across entry points ==="
rg "handleInitiateKyc|sumsubFlow" -C 2 | head -100

echo -e "\n=== Checking cleanup and useEffect dependencies ==="
rg "useEffect.*return.*clearInterval|useEffect.*return.*stopTracking" -A 5

echo -e "\n=== Checking race condition risks with async state updates ==="
rg "setModalPhase|setForceShowModal|setIsVerificationProgressModalOpen" src/hooks/useMultiPhaseKycFlow.ts src/hooks/useSumsubKycFlow.ts

echo -e "\n=== Checking all navigation/redirect paths after KYC ==="
rg "onKycSuccess|completeFlow|router.push|navigate" src/hooks/useMultiPhaseKycFlow.ts src/hooks/useSumsubKycFlow.ts -A 2 -B 2

echo -e "\n=== Finding potential memory leaks with timers/intervals ==="
rg "setTimeout|setInterval" src/hooks/useMultiPhaseKycFlow.ts src/hooks/useSumsubKycFlow.ts -A 3

Length of output: 17223


@kushagrasarathe: I've conducted an in-depth review of PR #1690. Here's my comprehensive analysis:

🔴 Risk Assessment: HIGH-MEDIUM

Critical Risk Areas

1. Race Conditions in Multi-Phase Flow ⚠️

  • The async operations in useMultiPhaseKycFlow lack proper cancellation guards. The handleTosIframeClose has a hardcoded 2s retry delay without cleanup:
    await new Promise((resolve) => setTimeout(resolve, 2000))
    If the component unmounts during this delay, you'll get a memory leak.

Recommendation: Add cleanup flag:

useEffect(() => {
  let cancelled = false;
  
  const handleTosIframeClose = async (source) => {
    // ... existing logic
    if (source === 'tos_accepted') {
      await new Promise((resolve) => setTimeout(resolve, 2000))
      if (cancelled) return; // Add guard
      const retryResult = await confirmBridgeTos()
      // ...
    }
  }
  
  return () => { cancelled = true }
}, [])

2. Missing Dependency Arrays 🚨

  • Line 68 in src/app/(mobile-ui)/add-money/[country]/bank/page.tsx disables exhaustive-deps:
    }, [urlState.step]) // eslint-disable-line react-hooks/exhaustive-deps
    This could cause stale closures if sumsubFlow.handleInitiateKyc changes.

3. State Machine Complexity

  • useMultiPhaseKycFlow is 291 lines with 7 refs (isRealtimeFlowRef, closeVerificationModalRef, preparingTimerRef, regionIntentRef, levelNameRef, userInitiatedRef in useSumsubKycFlow, prevStatusRef) - this is hard to reason about and test.

4. Silent Failures

  • Multiple places silently catch errors:
    } catch {
      // silent — polling is a best-effort fallback
    }
    This could hide real issues in production.

Medium Risk Areas

5. Removed collectUserDetails Step

  • Name/email now collected by Sumsub SDK. If SDK fails or user cancels mid-flow, user data may not be saved to your backend.

6. Breaking Changes Not Documented

  • BridgeBankStep type changed (removed 'collectUserDetails')
  • Import paths changed for isKycStatusItem, KycHistoryEntry
  • All KYC modal components deleted

Low Risk (Well-Handled)

✅ Polling cleanup is correct with clearInterval
isRealtimeFlowRef pattern matches best practices for async guards
✅ Timer cleanup via clearPreparingTimer is comprehensive


⚡ Performance Analysis

Positive Impacts (+)

  • Reduced API calls: Removed redundant updateUserById before KYC initiation
  • Code size: ~970 lines removed = smaller bundle
  • Better DRY: Single flow replaces 3 implementations

Negative Impacts (-)

  • Polling overhead: 5s interval during modal lifetime

    • No exponential backoff
    • Could run for 30s+ if preparing phase times out
    • Each poll = 1 API call to initiateSumsubKyc
  • Multiple fetchUser() calls:

    // In handleSumsubApproved
    const updatedUser = await fetchUser()
    
    // In handleTosIframeClose  
    await fetchUser()
    
    // In useEffect for ACTION_REQUIRED/REJECTED
    fetchUser()

    Could be expensive if user object is large.

Latency Impact

  • Multi-phase adds latency: SDK → APPROVED → fetch user → check rails → (possibly) preparing (30s timeout) → (possibly) ToS → complete
  • ToS retry adds minimum 2s delay

Estimated worst-case flow time: 60-90 seconds (SDK submit → 30s preparing timeout → ToS → retry)


📊 Code Quality: GOOD with Concerns

Strengths ✅

  1. Excellent DRY: Unified 3 separate KYC implementations
  2. Separation of concerns: useMultiPhaseKycFlow + SumsubKycModals
  3. Type safety: Proper TypeScript, interfaces well-defined
  4. Consistent patterns: regionIntent 'STANDARD'/'LATAM' used everywhere

Weaknesses ⚠️

  1. Hook too complex: 291 lines, should split into:

    • useKycModalState (modal phase logic)
    • useKycTosFlow (ToS logic)
    • useKycRailTracking (rail status)
  2. Ref overuse: 7 refs across two hooks makes state flow hard to trace

  3. Silent failures: Many try-catch with just console.warn

  4. Disabled linting: Multiple exhaustive-deps disabled


🧪 Concise QA Testing Guide

1. Standard Bridge Flow (US/EU)

  • Navigate to Add Money → Bank (any supported country)
  • Verify KYC modal appears if not verified
  • Complete Sumsub SDK flow → Verify "Identity verified!" appears
  • If Bridge rail enabled: Check ToS modal appears → Accept
  • Verify redirect to input amount step
  • Edge case: Close modal mid-flow → Reopen → Verify state reset

2. LATAM Manteca Flow (Argentina, Mexico)

  • Navigate to Withdraw → Manteca
  • Submit bank details without KYC → Verify KYC modal appears
  • Complete Sumsub flow with regionIntent: LATAM
  • Verify no Bridge ToS (should skip to complete)
  • Verify withdrawal proceeds after KYC

3. Claim Link Flows

  • Receive link → Claim → Select bank → Verify KYC triggered
  • Verify correct region intent based on country

4. Retry & Error Scenarios

  • Simulate network failure during SDK load → Verify error message
  • Submit documents → Get "action required" → Verify status updates in history
  • Get rejected → Verify rejection reasons shown
  • Race condition test: Rapidly switch between users (if multi-account) during KYC

5. Polling Fallback (Critical)

  • Disable WebSocket (browser dev tools) → Complete KYC → Verify flow still completes via polling
  • Check Network tab: Should see initiateSumsubKyc calls every 5s during modal

6. Preparing Phase Timeout

  • Simulate slow rail provisioning → Verify 30s timeout triggers "taking longer" message
  • Verify flow eventually completes or allows skip

7. History & Status

  • Complete KYC → Check History page → Verify KYC entry appears
  • Check isKycStatusItem type guard works (no console errors)

8. Regression: Removed Components

  • Search codebase for InitiateBridgeKYCModal imports → Should be ZERO
  • Search for useBridgeKycFlow → Should be ZERO (except old learning references)

9. Memory Leaks

  • Open KYC modal → Immediately navigate away → Check console for unmount warnings
  • Repeat 10 times → Check browser memory (Performance tab)

🎯 Priority Fixes Before Merge

MUST FIX:

  1. Add cancellation guard to handleTosIframeClose setTimeout
  2. Fix exhaustive-deps in bank/page.tsx (add proper deps or extract stable function)
  3. Add error boundary around <SumsubKycModals /> to catch render errors

SHOULD FIX:
4. Add exponential backoff to polling (5s → 10s → 20s)
5. Debounce multiple fetchUser() calls with cache TTL
6. Split useMultiPhaseKycFlow into smaller hooks

NICE TO HAVE:
7. Add Sentry/logging for silent catch blocks
8. Document breaking changes in PR description (type changes, import paths)


Overall Assessment: The refactoring achieves its goal of unifying KYC flows and reduces code duplication significantly. However, the complex state machine and async operations introduce race condition risks that need cleanup guards. Performance impact is acceptable for short KYC flows but polling overhead could be optimized. I recommend fixing the priority issues above before merging.


🧠 Learnings used
Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 852
File: src/hooks/useTransactionHistory.ts:177-239
Timestamp: 2025-05-14T21:16:06.369Z
Learning: When reviewing code, pay attention to and resolve CodeRabbit comments as they can contain useful refactoring suggestions and insights.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 594
File: src/components/Request/Pay/Views/Initial.view.tsx:188-213
Timestamp: 2024-12-31T15:08:15.640Z
Learning: Always resolve coderabbit comments when reviewing code, since they can contain useful suggestions.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 1654
File: src/components/Card/CardGeoScreen.tsx:41-45
Timestamp: 2026-02-02T06:03:48.192Z
Learning: In peanutprotocol/peanut-ui, KYC 2.0 uses the unified entry route '/profile/identity-verification'. Components (e.g., src/components/Card/CardGeoScreen.tsx) should avoid region-specific paths like '/profile/identity-verification/europe/bridge' and navigate to the unified route instead.

Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1683
File: src/components/Kyc/SumsubKycFlow.tsx:218-230
Timestamp: 2026-02-23T16:00:24.665Z
Learning: In src/components/Kyc/SumsubKycFlow.tsx, the ToS confirmation flow in handleTosIframeClose intentionally continues to fetchUser() even when confirmBridgeTos() fails after retry. The phase-transition effect (which monitors rail status via useRailStatusTracking) serves as the actual validation mechanism, making the flow resilient to transient confirmation API failures while still ensuring correct state progression.

Learnt from: Hugo0
Repo: peanutprotocol/peanut-ui PR: 0
File: :0-0
Timestamp: 2026-01-02T13:14:32.635Z
Learning: In the peanut-ui codebase, the add-money flows (bank and Manteca) use nuqs library for URL-based state management (step, amount, currency via useQueryStates), while RequestFulfillmentFlowContext still uses enum-based context state. This creates intentional partial migration where URL state is used for user-facing flows that benefit from deep linking and bookmarking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant