fix: cross-region KYC locks previously unlocked regions#1872
Conversation
when sumsubVerificationRegionIntent was LATAM, isRegionUnlocked returned
false for bridge regions without checking hasProviderAccess('BRIDGE').
this locked users out of europe/NA after completing cross-region kyc
even when their bridge rails were ENABLED.
now checks each provider's rails independently: bridge regions unlock
based on bridge rail status, latam unlocks based on sumsub intent or
manteca approval. fixes the region-lock reported for fabio, federico,
and matias after PR #641 (moveToLevel) was merged.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
WalkthroughThe change modifies the Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review in depth. Evaluate:
Also, write a very concise testing guide for QA |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chainLet me gather the PR diff and relevant file contents first. 🏁 Script executed: Length of output: 4222 🏁 Script executed: Length of output: 11815
🔍 In-Depth ReviewBug Fix Correctness ✅The restructuring of
The old code used Risk Assessment — Low 🟢1. Regression: non-sumsub users (legacy Bridge-only) return (
(isBridgeApproved && BRIDGE_SUPPORTED_REGIONS.includes(regionName)) ||
(isMantecaApproved && MANTECA_SUPPORTED_REGIONS.includes(regionName))
)Legacy bridge-KYC users who never went through the sumsub flow are unaffected. 2. Regression: QR-only LATAM access if (isBridgeApproved && !isMantecaApproved && !isSumsubApproved) {
unlocked.push(...MANTECA_QR_ONLY_REGIONS, ...BRIDGE_SUPPORTED_LATAM_COUNTRIES)
}This block is unchanged — Bridge users without Manteca still get Argentina/Brazil QR access. 3. One nuance worth noting: 4. Performance Impact — None 🟢
Code Quality / DRY ✅The new structure is strictly better:
Minor nit:
|
| # | Account State | Expected Result |
|---|---|---|
| 1 | Sumsub APPROVED (LATAM intent) + Bridge rails ENABLED | Europe & North America regions show as unlocked |
| 2 | Sumsub APPROVED (STANDARD intent) + Manteca ACTIVE | LATAM region shows as unlocked |
| 3 | Sumsub APPROVED (STANDARD intent) + no Manteca | LATAM region shows as locked |
| 4 | Sumsub APPROVED (LATAM intent) + no Bridge rails | Europe & North America show as locked |
| 5 | Bridge KYC only (no Sumsub) + no Manteca | Argentina & Brazil QR-only access still shows; LATAM full access locked |
| 6 | Regression: Previously affected user (LATAM intent + Bridge rails ENABLED) | On page load/refresh, Europe/North America auto-unlock without any action |
Check the region grid on the identity verification page (/profile/identity-verification) for each account state. No API calls needed — the fix is purely client-side state evaluation.
🧠 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.
There was a problem hiding this comment.
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/hooks/useIdentityVerification.tsx`:
- Around line 182-185: The hook currently restores top-level Bridge regions via
BRIDGE_SUPPORTED_REGIONS and hasProviderAccess('BRIDGE') but still suppresses
BRIDGE_SUPPORTED_LATAM_COUNTRIES and MANTECA_QR_ONLY_REGIONS when
isSumsubApproved is true, causing Sumsub-approved Bridge users to miss Mexico
and QR-only countries in unlockedRegions; update the country-level conditional
that currently checks isSumsubApproved to instead (or additionally) consult
hasProviderAccess('BRIDGE') so that BRIDGE_SUPPORTED_LATAM_COUNTRIES and
MANTECA_QR_ONLY_REGIONS are included in unlockedRegions when
hasProviderAccess('BRIDGE') returns true (use the same predicate used for
BRIDGE_SUPPORTED_REGIONS and reference variables
BRIDGE_SUPPORTED_LATAM_COUNTRIES, MANTECA_QR_ONLY_REGIONS, isSumsubApproved,
hasProviderAccess('BRIDGE'), and unlockedRegions to locate and change the
logic).
🪄 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: e278a8a4-f092-4fa1-bc52-3455041f4030
📒 Files selected for processing (1)
src/hooks/useIdentityVerification.tsx
| // bridge regions: check provider rails regardless of sumsub regionIntent | ||
| if (BRIDGE_SUPPORTED_REGIONS.includes(regionName)) { | ||
| return hasProviderAccess('BRIDGE') | ||
| } |
There was a problem hiding this comment.
Reuse the new Bridge-access predicate for the country-level Bridge unlocks.
Lines 182-185 now restore the top-level Bridge regions from hasProviderAccess('BRIDGE'), but Lines 206-208 still suppress BRIDGE_SUPPORTED_LATAM_COUNTRIES and MANTECA_QR_ONLY_REGIONS whenever isSumsubApproved is true. Sumsub-approved Bridge users will therefore still miss Mexico and the Bridge QR-only countries in unlockedRegions, even though this hook still advertises them as Bridge unlocks at Lines 250-283.
Suggested alignment
const hasProviderAccess = (providerCode: string) => {
const providerRails = user?.rails?.filter((r) => r.rail.provider.code === providerCode) ?? []
if (providerRails.length === 0) return false
return providerRails.some(
(r) =>
r.status === 'ENABLED' ||
r.status === 'REQUIRES_INFORMATION' ||
r.status === 'REQUIRES_EXTRA_INFORMATION'
)
}
+ const hasBridgeRailAccess = hasProviderAccess('BRIDGE')
// helper to check if a region should be unlocked
const isRegionUnlocked = (regionName: string) => {
if (isSumsubApproved) {
if (regionName === 'Rest of the world') return true
// bridge regions: check provider rails regardless of sumsub regionIntent
if (BRIDGE_SUPPORTED_REGIONS.includes(regionName)) {
- return hasProviderAccess('BRIDGE')
+ return hasBridgeRailAccess
}
@@
- if (isBridgeApproved && !isMantecaApproved && !isSumsubApproved) {
+ if (((!isSumsubApproved && isBridgeApproved) || (isSumsubApproved && hasBridgeRailAccess)) && !isMantecaApproved) {
unlocked.push(...MANTECA_QR_ONLY_REGIONS, ...BRIDGE_SUPPORTED_LATAM_COUNTRIES)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // bridge regions: check provider rails regardless of sumsub regionIntent | |
| if (BRIDGE_SUPPORTED_REGIONS.includes(regionName)) { | |
| return hasProviderAccess('BRIDGE') | |
| } | |
| const hasProviderAccess = (providerCode: string) => { | |
| const providerRails = user?.rails?.filter((r) => r.rail.provider.code === providerCode) ?? [] | |
| if (providerRails.length === 0) return false | |
| return providerRails.some( | |
| (r) => | |
| r.status === 'ENABLED' || | |
| r.status === 'REQUIRES_INFORMATION' || | |
| r.status === 'REQUIRES_EXTRA_INFORMATION' | |
| ) | |
| } | |
| const hasBridgeRailAccess = hasProviderAccess('BRIDGE') | |
| // helper to check if a region should be unlocked | |
| const isRegionUnlocked = (regionName: string) => { | |
| if (isSumsubApproved) { | |
| if (regionName === 'Rest of the world') return true | |
| // bridge regions: check provider rails regardless of sumsub regionIntent | |
| if (BRIDGE_SUPPORTED_REGIONS.includes(regionName)) { | |
| return hasBridgeRailAccess | |
| } | |
| } | |
| // ... rest of isRegionUnlocked logic | |
| } | |
| // ... middle code ... | |
| if (((!isSumsubApproved && isBridgeApproved) || (isSumsubApproved && hasBridgeRailAccess)) && !isMantecaApproved) { | |
| unlocked.push(...MANTECA_QR_ONLY_REGIONS, ...BRIDGE_SUPPORTED_LATAM_COUNTRIES) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/hooks/useIdentityVerification.tsx` around lines 182 - 185, The hook
currently restores top-level Bridge regions via BRIDGE_SUPPORTED_REGIONS and
hasProviderAccess('BRIDGE') but still suppresses
BRIDGE_SUPPORTED_LATAM_COUNTRIES and MANTECA_QR_ONLY_REGIONS when
isSumsubApproved is true, causing Sumsub-approved Bridge users to miss Mexico
and QR-only countries in unlockedRegions; update the country-level conditional
that currently checks isSumsubApproved to instead (or additionally) consult
hasProviderAccess('BRIDGE') so that BRIDGE_SUPPORTED_LATAM_COUNTRIES and
MANTECA_QR_ONLY_REGIONS are included in unlockedRegions when
hasProviderAccess('BRIDGE') returns true (use the same predicate used for
BRIDGE_SUPPORTED_REGIONS and reference variables
BRIDGE_SUPPORTED_LATAM_COUNTRIES, MANTECA_QR_ONLY_REGIONS, isSumsubApproved,
hasProviderAccess('BRIDGE'), and unlockedRegions to locate and change the
logic).
problem
after PR #641 (moveToLevel for cross-region KYC), users who complete verification for a second region lose access to their first region. this affected users
root cause:
isRegionUnlockedinuseIdentityVerification.tsxscoped region access bysumsubVerificationRegionIntent. when the intent wasLATAM, it returnedfalsefor bridge regions without checkinghasProviderAccess('BRIDGE')— even when bridge rails wereENABLED. same issue in reverse:STANDARDintent never checked manteca for LATAM regions.fix
check provider access independently of sumsub regionIntent:
hasProviderAccess('BRIDGE')regardless of intentsumsubVerificationRegionIntent === 'LATAM'(immediate, while manteca rails still pending) ORisMantecaApproved(after manteca submission completes)existing affected users will auto-fix on deploy — their bridge/manteca rails are already ENABLED.
test scenarios