Skip to content

fix: thread crossRegion flag for cross-region KYC#1874

Open
kushagrasarathe wants to merge 4 commits intomainfrom
fix/rail-enrollment-timing
Open

fix: thread crossRegion flag for cross-region KYC#1874
kushagrasarathe wants to merge 4 commits intomainfrom
fix/rail-enrollment-timing

Conversation

@kushagrasarathe
Copy link
Copy Markdown
Contributor

@kushagrasarathe kushagrasarathe commented Apr 6, 2026

context

pairs with backend PR peanutprotocol/peanut-api-ts#643. must be deployed together.

changes

1. crossRegion flag threading

threads crossRegion boolean through the KYC initiation chain:

  • initiateSumsubKyc server action → backend POST /users/identity
  • useSumsubKycFlow.handleInitiateKyc → accepts crossRegion param
  • useMultiPhaseKycFlow.handleInitiateKyc → passes through
  • RegionsVerification.handleStartKyc → detects cross-region (clicked intent differs from existing) and passes crossRegion: true (omitted when same-region)

automatic calls (fetchCurrentStatus, polling, component mounts) never set this flag, so they can't trigger moveToLevel or stale rail enrollment on the backend.

2. REVERIFYING status support

  • added REVERIFYING to SumsubKycStatus type union
  • added REVERIFYING to APPROVED_STATUSES set in kyc.consts.ts (single source of truth)
  • all downstream readers automatically treat REVERIFYING as approved: isKycStatusApproved, getKycStatusCategory, useKycStatus, useQrKycGate, useIdentityVerification, KycStatusItem, etc.
  • user retains existing provider access (QR pay, deposits, withdrawals) while re-verifying for a new region

test plan

test expected
first-time KYC no crossRegion flag sent, works as before
cross-region click on regions page crossRegion=true sent, moveToLevel triggered
navigate to deposit/withdraw after approval no crossRegion, no side effects
during REVERIFYING: QR pay gate PROCEED_TO_PAY (treated as approved)
during REVERIFYING: regions page all previously unlocked regions stay unlocked
during REVERIFYING: "Rest of World" stays unlocked

pairs with backend fix that moves rail enrollment to webhook time.
cross-region moveToLevel now requires explicit crossRegion=true flag.
only set when user clicks "Verify now" on a different region —
automatic calls (fetchCurrentStatus, polling) never set it.
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 6, 2026

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

Project Deployment Actions Updated (UTC)
peanut-wallet Ready Ready Preview, Comment Apr 7, 2026 10:18am

Request Review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d1fb8c22-5823-46a8-b65f-14566bccf49e

📥 Commits

Reviewing files that changed from the base of the PR and between 3a38e9f and 2834cc6.

📒 Files selected for processing (1)
  • src/hooks/useSumsubKycFlow.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/useSumsubKycFlow.ts

Walkthrough

Threads a new optional crossRegion?: boolean through Sumsub KYC initiation (payload and hook signatures), updates component call sites to pass it, adds REVERIFYING KYC status, and treats REVERIFYING as an approved/completed state in constants and flow logic.

Changes

Cohort / File(s) Summary
Server-side KYC Action
src/app/actions/sumsub.ts
Added crossRegion?: boolean to initiateSumsubKyc params; request payload type widened to allow boolean; include crossRegion in JSON body posted to /users/identity.
KYC Flow Hooks
src/hooks/useMultiPhaseKycFlow.ts, src/hooks/useSumsubKycFlow.ts
Extended handleInitiateKyc signatures to accept optional crossRegion?: boolean and forward it to the original initiate handler; useSumsubKycFlow also treats REVERIFYING as a terminal/approved status in modal/finish logic and triggers onKycSuccess when appropriate.
Component Integration
src/components/Profile/views/RegionsVerification.view.tsx
Derive crossRegion by comparing selected region intent vs sumsubVerificationRegionIntent; clear selectedRegion and call flow.handleInitiateKyc(intent, undefined, crossRegion); added sumsubVerificationRegionIntent to hook deps.
Statuses & Constants
src/app/actions/types/sumsub.types.ts, src/constants/kyc.consts.ts
Added new SumsubKycStatus member 'REVERIFYING'; added REVERIFYING to APPROVED_STATUSES, so helpers and status-category logic treat it as approved/completed.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: thread crossRegion flag for cross-region KYC' clearly and concisely summarizes the main change—threading a crossRegion flag through the KYC initiation chain.
Description check ✅ Passed The description comprehensively covers the changes, including the crossRegion flag threading and REVERIFYING status support, and provides a detailed test plan aligned with the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rail-enrollment-timing

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

Copy link
Copy Markdown
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/Profile/views/RegionsVerification.view.tsx`:
- Around line 103-107: The code computes isCrossRegion and always passes it into
flow.handleInitiateKyc, which sends crossRegion: false when not cross-region;
change the call site so crossRegion is only included when true: compute
isCrossRegion (using selectedRegion and sumsubVerificationRegionIntent) then
call flow.handleInitiateKyc(intent, undefined) for non-cross-region flows and
call flow.handleInitiateKyc(intent, undefined, true) only when isCrossRegion is
true; update the block around isCrossRegion, setSelectedRegion, and the
flow.handleInitiateKyc call accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d4ff7b36-fb9d-4b2a-8286-d11776d6bfd5

📥 Commits

Reviewing files that changed from the base of the PR and between d77f5cf and 9bc513b.

📒 Files selected for processing (4)
  • src/app/actions/sumsub.ts
  • src/components/Profile/views/RegionsVerification.view.tsx
  • src/hooks/useMultiPhaseKycFlow.ts
  • src/hooks/useSumsubKycFlow.ts

@kushagrasarathe
Copy link
Copy Markdown
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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== src/app/actions/sumsub.ts ==="
cat src/app/actions/sumsub.ts

echo ""
echo "=== src/hooks/useSumsubKycFlow.ts ==="
cat src/hooks/useSumsubKycFlow.ts

echo ""
echo "=== src/hooks/useMultiPhaseKycFlow.ts ==="
cat src/hooks/useMultiPhaseKycFlow.ts

echo ""
echo "=== src/components/Profile/views/RegionsVerification.view.tsx ==="
cat src/components/Profile/views/RegionsVerification.view.tsx

Length of output: 33455


🏁 Script executed:

#!/bin/bash
echo "=== All callers of handleInitiateKyc ==="
rg -n "handleInitiateKyc" --type=ts --type=tsx -C2

echo ""
echo "=== All callers of initiateSumsubKyc ==="
rg -n "initiateSumsubKyc" --type=ts --type=tsx -C2

echo ""
echo "=== sumsubVerificationRegionIntent usages ==="
rg -n "sumsubVerificationRegionIntent" -C2

echo ""
echo "=== All files importing useMultiPhaseKycFlow ==="
rg -n "useMultiPhaseKycFlow" -l

Length of output: 6423


🏁 Script executed:

#!/bin/bash
echo "=== All callers of handleInitiateKyc ==="
rg -n "handleInitiateKyc" -C2

echo ""
echo "=== All callers of initiateSumsubKyc (in all files) ==="
rg -n "initiateSumsubKyc" -C2

echo ""
echo "=== KycStatusDrawer handleInitiateKyc usage ==="
cat src/components/Kyc/KycStatusDrawer.tsx

echo ""
echo "=== SumsubKycFlow handleInitiateKyc usage ==="
cat src/components/Kyc/SumsubKycFlow.tsx

Length of output: 22586


@kushagrasarathe — Here is the in-depth review.


Overall Assessment

The change is minimal, well-scoped, and low-risk. The flag is correctly threaded through the entire chain (RegionsVerification → useMultiPhaseKycFlow → useSumsubKycFlow → initiateSumsubKyc → backend), and all automatic call paths (mount-time status fetch, polling, token refresh) are correctly untouched. One subtle issue is worth addressing before merge.


🐛 Risk of Bugs / Breaking Existing Functionality

⚠️ crossRegion: false is serialized and sent for same-region clicks

File: src/components/Profile/views/RegionsVerification.view.tsx Line 104–106

const isCrossRegion = !!sumsubVerificationRegionIntent && !!intent && intent !== sumsubVerificationRegionIntent
setSelectedRegion(null)
await flow.handleInitiateKyc(intent, undefined, isCrossRegion)

When isCrossRegion evaluates to false (first-time KYC or same-region resubmit), the call is handleInitiateKyc(intent, undefined, false). This chains all the way down to:

// sumsub.ts
const body = {
    ...
    crossRegion: params?.crossRegion,  // → false
}
JSON.stringify(body)  // → "crossRegion": false included in body

The three resulting states the backend receives are:

Caller crossRegion in JSON
Automatic (fetchCurrentStatus, polling, refreshToken) absent
Same-region explicit click from RegionsVerification false
Cross-region explicit click from RegionsVerification true
All other consumers (SumsubKycFlow, KycStatusDrawer, MantecaFlowManager, etc.) absent

If the backend guards with if (crossRegion) (truthy check), this is a non-issue. But if it ever checks crossRegion === false for any separate branch, it could behave differently for same-region clicks vs all other callers. The safest fix is to pass undefined instead of false at the call site:

// RegionsVerification.view.tsx
await flow.handleInitiateKyc(intent, undefined, isCrossRegion || undefined)

This ensures the JSON field is only ever present when true, making the contract consistent with every other caller.

✅ All other callers are safe — no updates needed

Confirmed: SumsubKycFlow, KycStatusDrawer, MantecaFlowManager, BankFlowManager, AddWithdrawCountriesList, MantecaAddMoney, withdraw/manteca/page.tsx, and add-money/[country]/bank/page.tsx all call handleInitiateKyc without the third argument. Since crossRegion is optional and defaults to undefined, these are completely unaffected.

✅ No risk from dependency arrays

crossRegion is always a function parameter, never a closed-over variable. All useCallback dep arrays in useSumsubKycFlow, useMultiPhaseKycFlow, and RegionsVerification are correct. The newly added sumsubVerificationRegionIntent dep to handleStartKyc's useCallback was correctly identified and added.

✅ No risk from automatic calls

fetchCurrentStatus, the 5-second polling interval, and refreshToken in useSumsubKycFlow all call initiateSumsubKyc without crossRegion, so they will never trigger moveToLevel on the backend. This is the core invariant of the PR and it holds.


⚡ Performance Impact & Latency

Negligible. The changes introduce:

  • One extra optional boolean field in the POST body
  • No new useState, useEffect, useRef, or extra renders
  • No change to request frequency (polling, WebSocket, mount-time fetch all unchanged)

🧹 Code Quality & DRY

  • Clean pass-through: the flag propagates through 4 layers without any logic duplication — detection is correctly placed only at the view layer (RegionsVerification), which is the sole place that has context about user intent vs existing state.
  • Body type widening (Record<string, string | boolean | undefined>) in sumsub.ts is correct and minimal.
  • isCrossRegion logic (!!sumsubVerificationRegionIntent && !!intent && intent !== sumsubVerificationRegionIntent) is readable, handles all edge cases (first-time KYC, same-region, cross-region), and is correctly guarded by double negation.
  • Minor: the body object in sumsub.ts could optionally filter out undefined entries before stringifying to be explicit, but JSON.stringify already handles this — not a real issue.

🧪 QA Testing Guide

Scenario Expected behaviour
First-time KYC — no existing verification, click "Verify now" for any region crossRegion absent from request; normal KYC flow, no side effects
Cross-region — already APPROVED for EUROPE, click "Verify now" for US crossRegion: true sent; backend triggers moveToLevel; new region unlocked on approval
Same-region resubmit — ACTION_REQUIRED for EUROPE, click "Retry" crossRegion absent (after fix) or false (current); no moveToLevel triggered
Page load / navigation — navigate to /profile/regions while approved Mount-time fetchCurrentStatus fires without crossRegion; status read-only, no rail enrollment side effects
Token refresh — leave Sumsub SDK idle until token expires Refresh call has no crossRegion; no moveToLevel triggered
Polling — verification progress modal open, websocket delayed Polling calls have no crossRegion; status updates correctly without backend side effects
Other entry points (add-money bank flow, withdraw Manteca, claim link flow) None of these pass crossRegion; existing behaviour fully preserved

TL;DR: One actionable fix — pass isCrossRegion || undefined instead of isCrossRegion at the call site in RegionsVerification.handleStartKyc to avoid sending "crossRegion": false" to the backend for same-region clicks. Everything else is solid.


🧠 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: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 0
File: :0-0
Timestamp: 2026-03-04T18:32:51.654Z
Learning: In peanutprotocol/peanut-ui, Bridge-specific add-money and withdraw flows (`src/app/(mobile-ui)/add-money/[country]/bank/page.tsx`, `src/components/AddWithdraw/AddWithdrawCountriesList.tsx`, `src/components/Claim/Link/views/BankFlowManager.view.tsx`) have been migrated (PR `#1713`) to use the unified `isUserKycApproved` from `useKycStatus()` hook instead of per-component `liveKycStatus` + WebSocket state. The migration is complete; no more direct `bridgeKycStatus === 'approved'` checks in those files.

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: 1693
File: src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:206-207
Timestamp: 2026-03-04T18:35:17.673Z
Learning: In Bridge-specific add-money and withdraw flows (e.g., src/app/(mobile-ui)/add-money/[country]/bank/page.tsx), liveKycStatus from WebSocket tracks real-time bridge KYC status changes, and bridgeKycStatus is the persisted status. These flows use direct bridgeKycStatus === 'approved' checks instead of the unified isUserKycVerified helper. Migrating to the unified verifier requires careful testing of WebSocket-based real-time status tracking and is deferred to follow-up work.

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: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1690
File: src/hooks/useMultiPhaseKycFlow.ts:84-89
Timestamp: 2026-02-25T11:02:11.410Z
Learning: In src/hooks/useMultiPhaseKycFlow.ts, the bridge_tos phase does not require rail status tracking because handleTosIframeClose optimistically calls completeFlow() immediately after confirmBridgeTos() succeeds. The flow bypasses the phase-transition effect entirely for the happy path, completing the flow after fetchUser() without waiting for WebSocket rail status updates.

REVERIFYING is treated as approved for all access checks — user
retains existing provider access while re-verifying for a new region.
added to APPROVED_STATUSES set (single source of truth) so all
downstream readers (useKycStatus, useQrKycGate, useIdentityVerification,
KycStatusItem, etc.) automatically handle it correctly.
Copy link
Copy Markdown
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/constants/kyc.consts.ts`:
- Around line 13-15: APPROVED_STATUSES currently includes 'REVERIFYING', which
causes getKycStatusCategory() to classify re-verifying users as 'completed' and
breaks the cross-region flow; remove 'REVERIFYING' from the APPROVED_STATUSES
set in src/constants/kyc.consts.ts and instead update getKycStatusCategory() to
treat 'REVERIFYING' as 'processing' so UI components (KycStatusDrawer,
KycCompleted) render the processing state while keeping access-check helpers
(isKycStatusApproved, isUserKycVerified) unchanged if they must still treat
REVERIFYING as approved for access; ensure regionIntent is not cleared for
REVERIFYING users so initiateSumsubKyc() can run.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a4b08a0a-33a1-45f4-bad2-8a9a4b7662f4

📥 Commits

Reviewing files that changed from the base of the PR and between f9f056b and 3a38e9f.

📒 Files selected for processing (2)
  • src/app/actions/types/sumsub.types.ts
  • src/constants/kyc.consts.ts
✅ Files skipped from review due to trivial changes (1)
  • src/app/actions/types/sumsub.types.ts

Comment on lines +13 to +15
// REVERIFYING = user is approved but re-verifying for a new region (cross-region KYC).
// treated as approved for access checks — user retains existing provider access.
const APPROVED_STATUSES: ReadonlySet<string> = new Set(['approved', 'ACTIVE', 'APPROVED', 'REVERIFYING'])
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check all usages of getKycStatusCategory to assess impact of REVERIFYING returning 'completed'
echo "=== getKycStatusCategory usages ==="
rg -n -C3 'getKycStatusCategory'

echo ""
echo "=== isKycStatusApproved usages ==="
rg -n -C3 'isKycStatusApproved'

echo ""
echo "=== isUserKycVerified usages ==="
rg -n -C3 'isUserKycVerified'

Repository: peanutprotocol/peanut-ui

Length of output: 17219


🏁 Script executed:

# Check the current implementation of getKycStatusCategory and surrounding constants
cat -n src/constants/kyc.consts.ts | head -90

Repository: peanutprotocol/peanut-ui

Length of output: 4364


🏁 Script executed:

# Check KycStatusDrawer.tsx around the lines mentioned in the review (39-60, 131-137)
cat -n src/components/Kyc/KycStatusDrawer.tsx | head -150

Repository: peanutprotocol/peanut-ui

Length of output: 7521


🏁 Script executed:

# Check if REVERIFYING is already handled specially anywhere
rg -n 'REVERIFYING' --type ts --type tsx

Repository: peanutprotocol/peanut-ui

Length of output: 95


🏁 Script executed:

# Search for REVERIFYING references without type filter
rg -n 'REVERIFYING'

Repository: peanutprotocol/peanut-ui

Length of output: 384


🏁 Script executed:

# Check if there's any conditional handling for REVERIFYING in the codebase
rg -n -B2 -A2 "statusCategory.*completed"

Repository: peanutprotocol/peanut-ui

Length of output: 526


REVERIFYING in APPROVED_STATUSES breaks cross-region KYC flows.

Adding REVERIFYING to APPROVED_STATUSES causes getKycStatusCategory() to return 'completed' for users actively re-verifying for a new region. This creates two problems:

  1. KycStatusDrawer prevents KYC initialization: Line 60 sets regionIntent = undefined when statusCategory === 'completed', blocking initiateSumsubKyc() from being called for REVERIFYING users who need to complete cross-region verification.

  2. Wrong UI shown to users: REVERIFYING users see the KycCompleted component (lines 131–139), displaying "verification complete" while they're still actively verifying.

Consider separating access-check logic from UI categorization:

Suggested fix: Handle REVERIFYING as 'processing' in getKycStatusCategory
 export const getKycStatusCategory = (status: string): KycStatusCategory => {
+    // REVERIFYING users are approved but actively re-verifying for a new region
+    if (status === 'REVERIFYING') return 'processing'
     if (APPROVED_STATUSES.has(status)) return 'completed'
     if (FAILED_STATUSES.has(status)) return 'failed'
     if (ACTION_REQUIRED_STATUSES.has(status)) return 'action_required'
     return 'processing'
 }

This allows access checks (isKycStatusApproved, isUserKycVerified) to return true for REVERIFYING users while showing appropriate processing UI for the cross-region flow.

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

In `@src/constants/kyc.consts.ts` around lines 13 - 15, APPROVED_STATUSES
currently includes 'REVERIFYING', which causes getKycStatusCategory() to
classify re-verifying users as 'completed' and breaks the cross-region flow;
remove 'REVERIFYING' from the APPROVED_STATUSES set in
src/constants/kyc.consts.ts and instead update getKycStatusCategory() to treat
'REVERIFYING' as 'processing' so UI components (KycStatusDrawer, KycCompleted)
render the processing state while keeping access-check helpers
(isKycStatusApproved, isUserKycVerified) unchanged if they must still treat
REVERIFYING as approved for access; ensure regionIntent is not cleared for
REVERIFYING users so initiateSumsubKyc() can run.

@kushagrasarathe
Copy link
Copy Markdown
Contributor Author

@coderabbitai re-review in depth, more changes are made since last review

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 7, 2026

Tip

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== src/app/actions/sumsub.ts ==="
cat src/app/actions/sumsub.ts

echo ""
echo "=== src/app/actions/types/sumsub.types.ts ==="
cat src/app/actions/types/sumsub.types.ts

echo ""
echo "=== src/constants/kyc.consts.ts ==="
cat src/constants/kyc.consts.ts

echo ""
echo "=== src/hooks/useSumsubKycFlow.ts ==="
cat src/hooks/useSumsubKycFlow.ts

echo ""
echo "=== src/hooks/useMultiPhaseKycFlow.ts ==="
cat src/hooks/useMultiPhaseKycFlow.ts

echo ""
echo "=== src/components/Profile/views/RegionsVerification.view.tsx ==="
cat src/components/Profile/views/RegionsVerification.view.tsx

Length of output: 37662


@kushagrasarathe — Here is the updated in-depth review covering all changes since the last review pass.


Overall Assessment

The PR is in good shape. The previously flagged crossRegion: false bug is correctly fixed, and the new REVERIFYING status is integrated cleanly through a single source of truth. Two minor edge cases are worth knowing before merge.


🐛 Risk of New Bugs / Breaking Existing Functionality

crossRegion: false bug — FIXED

The previous review's only actionable finding has been addressed correctly:

// RegionsVerification.view.tsx — current
const crossRegion =
    sumsubVerificationRegionIntent && intent && intent !== sumsubVerificationRegionIntent ? true : undefined

undefined is returned for same-region/first-time KYC, and true for cross-region. JSON.stringify omits undefined, so "crossRegion": false is never serialized. ✅


⚠️ REVERIFYING arriving via WebSocket while progress modal is open

In useSumsubKycFlow, the status-transition effect has this branch:

} else if (
    liveKycStatus &&
    liveKycStatus !== prevStatus &&
    liveKycStatus !== 'APPROVED' &&
    liveKycStatus !== 'PENDING'   // ← REVERIFYING is NOT excluded here
) {
    setIsVerificationProgressModalOpen(false)  // modal would close
}

APPROVED and PENDING are explicitly excluded because they should not close the modal. REVERIFYING is not excluded. If a WebSocket event carries REVERIFYING while the verification-progress modal is open (e.g., backend emits it on cross-region KYC initiation during SDK completion), the modal would close unexpectedly mid-flow.

In practice the risk is low: REVERIFYING is assigned by the backend at the moment crossRegion: true is received during handleInitiateKyc — before the SDK opens and well before the progress modal appears. But if the backend ever emits a REVERIFYING WebSocket event during the re-verification session, this branch fires. A one-line guard would make the contract explicit:

liveKycStatus !== 'PENDING' &&
liveKycStatus !== 'REVERIFYING'   // treat like APPROVED — user retains access

⚠️ REVERIFYING + no token from backend → misleading error

In useSumsubKycFlow.handleInitiateKyc:

if (response.data?.status === 'APPROVED' && !response.data?.token) {
    prevStatusRef.current = 'APPROVED'
    onKycSuccess?.()
    return
}
if (response.data?.token) {
    setAccessToken(response.data.token)
    setShowWrapper(true)
} else {
    setError('Could not initiate verification. Please try again.')  // ← fires if REVERIFYING + no token
}

If the backend ever returns status: 'REVERIFYING' alongside no token (e.g., user is already re-verifying, nothing more to do), the user sees "Could not initiate verification" instead of a meaningful state. This can be paired with the backend contract — if REVERIFYING always comes with a token (SDK still needed), it's fine. But if REVERIFYING can arrive token-less, the fast-exit logic should also handle it:

if ((response.data?.status === 'APPROVED' || response.data?.status === 'REVERIFYING') && !response.data?.token) {
    prevStatusRef.current = response.data.status
    onKycSuccess?.()
    return
}

Worth confirming the backend contract with peanutprotocol/peanut-api-ts#643.


REVERIFYING integration in kyc.consts.ts — clean

REVERIFYING is added to APPROVED_STATUSES only, which is the single source of truth. All downstream helpers adopt it automatically:

Helper With REVERIFYING Correct?
isKycStatusApproved true ✅ user retains access
isUserKycVerified true
getKycStatusCategory 'completed' ✅ intentional (access preserved)
isSumsubStatusInProgress false ✅ not gated

FAILED_STATUSES, PENDING_STATUSES, ACTION_REQUIRED_STATUSES are all unaffected. ✅


getModalVariant handles REVERIFYING correctly

  • Same region + REVERIFYING: hits default'start' modal — lets user re-initiate. Intentional.
  • Different region + REVERIFYING: clickedRegionIntent !== existingRegionIntent guard → 'start' modal. ✅

✅ All other callers remain unaffected

Confirmed: all other call sites (SumsubKycFlow, KycStatusDrawer, MantecaFlowManager, BankFlowManager, AddWithdrawCountriesList, add-money/withdraw pages) omit the third arg. No changes needed there. ✅


useCallback dependency arrays — correct

  • handleStartKyc: now includes sumsubVerificationRegionIntent. ✅
  • handleInitiateKyc in both hooks: crossRegion is a parameter, never closed over. No dep array changes needed. ✅

⚡ Performance Impact & Latency

No change. No new state, hooks, effects, or render cycles. The only addition is an optional boolean in one POST body. ✅


🧹 Code Quality & DRY

  • Single source of truth for the new status (APPROVED_STATUSES in kyc.consts.ts).
  • Detection logic for cross-region remains correctly localized to the view layer.
  • ? true : undefined is idiomatic and correct.
  • Type widening (string | boolean | undefined) in the server action body is minimal and correct.

🧪 Concise QA Testing Guide

Scenario What to verify
First-time KYC — no existing verification crossRegion absent from POST body; normal flow, no side effects
Cross-region — APPROVED for EUROPE, click US crossRegion: true in POST body; moveToLevel fires; new region unlocked after approval
Same-region resubmit — ACTION_REQUIRED for EUROPE, click Retry crossRegion absent from POST body; no moveToLevel
REVERIFYING status — backend returns REVERIFYING on cross-region initiation SDK opens; user completes flow; eventually transitions to APPROVED; progress modal stays open
Page load/navigation — visit /profile/regions while APPROVED Mount-time fetchCurrentStatus fires without crossRegion; no backend side effects
Token refresh — leave SDK open until token expires Refresh call has no crossRegion; flow continues normally
Polling — progress modal open, WebSocket delayed Polling calls have no crossRegion; status updates correctly
Other entry points (add-money, withdraw, claim link) No crossRegion sent; existing behaviour unchanged

TL;DR: Two minor edge cases to verify against the backend contract — (1) whether REVERIFYING can arrive via WebSocket during SDK completion phase (if yes, add it to the exclusion list in the status-transition effect), and (2) whether REVERIFYING can come token-less (if yes, add a fast-exit branch alongside APPROVED). Everything else is correct and ready.


🧠 Learnings used
Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 0
File: :0-0
Timestamp: 2026-03-04T18:32:51.654Z
Learning: In peanutprotocol/peanut-ui, Bridge-specific add-money and withdraw flows (`src/app/(mobile-ui)/add-money/[country]/bank/page.tsx`, `src/components/AddWithdraw/AddWithdrawCountriesList.tsx`, `src/components/Claim/Link/views/BankFlowManager.view.tsx`) have been migrated (PR `#1713`) to use the unified `isUserKycApproved` from `useKycStatus()` hook instead of per-component `liveKycStatus` + WebSocket state. The migration is complete; no more direct `bridgeKycStatus === 'approved'` checks in those files.

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: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1693
File: src/app/(mobile-ui)/add-money/[country]/bank/page.tsx:206-207
Timestamp: 2026-03-04T18:35:17.673Z
Learning: In Bridge-specific add-money and withdraw flows (e.g., src/app/(mobile-ui)/add-money/[country]/bank/page.tsx), liveKycStatus from WebSocket tracks real-time bridge KYC status changes, and bridgeKycStatus is the persisted status. These flows use direct bridgeKycStatus === 'approved' checks instead of the unified isUserKycVerified helper. Migrating to the unified verifier requires careful testing of WebSocket-based real-time status tracking and is deferred to follow-up work.

Learnt from: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1690
File: src/hooks/useMultiPhaseKycFlow.ts:84-89
Timestamp: 2026-02-25T11:02:11.410Z
Learning: In src/hooks/useMultiPhaseKycFlow.ts, the bridge_tos phase does not require rail status tracking because handleTosIframeClose optimistically calls completeFlow() immediately after confirmBridgeTos() succeeds. The flow bypasses the phase-transition effect entirely for the happy path, completing the flow after fetchUser() without waiting for WebSocket rail status updates.

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: 1078
File: src/hooks/useKycFlow.ts:129-141
Timestamp: 2025-08-11T10:35:02.715Z
Learning: In the KYC flow implementation in `src/hooks/useKycFlow.ts`, when Terms of Service (ToS) is accepted, there will always be a KYC link available in the `apiResponse`. The system ensures this invariant, so defensive checks for missing KYC links after ToS acceptance are unnecessary.

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: kushagrasarathe
Repo: peanutprotocol/peanut-ui PR: 1693
File: src/constants/kyc.consts.ts:49-50
Timestamp: 2026-03-02T18:08:32.294Z
Learning: In src/constants/kyc.consts.ts, bridgeKycStatus is a field specific to the Bridge provider and always contains lowercase 'approved' when KYC is approved. Direct string comparison (bridgeKycStatus === 'approved') is correct and preferred over using isKycStatusApproved, since the field will never contain Manteca ('ACTIVE') or Sumsub ('APPROVED') statuses. The provider-specific field name indicates it only holds Bridge-formatted values.

…less handling

1. add REVERIFYING to exclusion list in status-transition effect so it
   doesn't close the progress modal if received via websocket during
   SDK completion

2. handle REVERIFYING + no token same as APPROVED — call onKycSuccess
   instead of showing misleading "Could not initiate verification" error
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.

1 participant