fix: bridge KYC state machine — unified gate, inline ToS, rejection handling#1943
Conversation
…andling
the bridge KYC state handling in transfer flows (withdraw, deposit, claim, qr-pay)
had multiple issues: wrong priority order, missing ToS checks, redirects instead of
inline flows, and broken rejection handling.
changes:
- new `useBridgeTransferReadiness` hook: unified pre-transfer gate with correct
priority (hard rejection → tos → fixable rejection → enrollment → ready)
- `useBridgeTosStatus`: expanded to check `tos_acceptance`/`tos_v2_acceptance` in
rail `additionalRequirements` metadata, not just `REQUIRES_INFORMATION` status
- `InitiateKycModal`: added `blocked` variant (contact support CTA), `error` prop
for failed self-heal attempts
- withdraw/bank, add-money/bank, AddWithdrawCountriesList: replaced fragmented
`needsBridgeEnrollment` + `guardWithTos` + `bridgeRejection` checks with unified gate
- BankFlowManager (claim flow): added ToS guard + rejection handling for non-guest paths
- qr-pay: replaced redirect-based KYC (`router.push('/profile/identity-verification')`)
with inline sumsub SDK initiation (`handleInitiateKyc('LATAM')`)
- BridgeTosStep: show confirmation modal before iframe, prevent modal flash during
confirmation with `isConfirming` state
- KycProviderRejection, ActivationCTAs, RegionsVerification: replaced broken
`$crisp.push` calls with `setIsSupportModalOpen`
- useSumsubKycFlow: fixed race condition where `fetchCurrentStatus` closes SDK wrapper
on first attempt by guarding with `showWrapperRef`
- useProviderRejectionStatus: permanent rejections now show generic support message
instead of misleading "upload a clearer photo"
- DynamicBankAccountForm: `__silent__` sentinel to prevent form navigation when gate blocks
- close kyc modal via `sumsubFlow.showWrapper` effect instead of in `onVerify` callback
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
WalkthroughThis PR consolidates KYC and bridge transfer gating into a single prioritized ChangesBridge gate + Sumsub KYC integration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
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 read all the changed files to do a thorough review. 🏁 Script executed: Length of output: 23384 🏁 Script executed: Length of output: 28640 🏁 Script executed: Length of output: 492 🏁 Script executed: Length of output: 76931
🐛 Bug Risks & Breakage1.
|
| # | Scenario | Steps | Expected |
|---|---|---|---|
| 1 | Hard rejection | Login as Bridge-hard-rejected user → attempt withdraw | "Verification issue" modal → "Contact support" opens support drawer |
| 2 | Fixable rejection | Login as Bridge-fixable-rejected user → attempt withdraw | "We need extra documents" modal → "Upload document" → Sumsub SDK opens inline |
| 3 | Self-heal error state | Fixable-rejected user with unrecognized reject label → attempt upload | Modal shows "Something went wrong" + "Contact support" |
| 4 | ToS acceptance (withdraw) | User with REQUIRES_INFORMATION rails → attempt withdraw |
ToS modal → iframe opens → accept → no modal flash → form re-submits |
| 5 | ToS acceptance (deposit) | Same user → attempt deposit | Same ToS gate triggers before proceeding |
| 6 | ToS acceptance (claim) | Claim link as unaccepted-ToS user | ToS gate triggers at confirm step, not at bank form |
| 7 | QR pay — unverified | Scan QR as unverified user | Inline KYC modal (not redirect to /profile/identity-verification) → "Verify now" → SDK opens |
| 8 | QR pay — verified | Scan QR as fully approved user | Proceeds directly to payment, no KYC prompt |
| 9 | SDK race condition | Navigate to identity verification → click "Start Verification" immediately | SDK stays open, does not auto-close on first attempt |
| 10 | Contact support CTAs | Click "Contact support" in KycProviderRejection, ActivationCTAs, RegionsVerification | Support drawer opens in all locations, no JS error |
| 11 | BridgeTosStep network error | Accept ToS with BE unavailable | Error displayed, component not stuck (not blank screen) |
| 12 | Happy path regression | Fully approved user: withdraw, deposit, claim | No KYC gates shown, full flows complete successfully |
| 13 | Form submit latency | Submit bank form as KYC-approved user (devtools → network tab) | Only one fetchUser call, not on gate-blocked attempts |
| 14 | needs_enrollment |
Sumsub-approved but Bridge not yet started → attempt bank transfer | "cross_region" variant modal appears, enrollment initiates |
…ata cast, gate fallthrough - BridgeTosStep: wrap confirmBridgeTosAndAwaitRails in try/catch to prevent isConfirming getting stuck on error - useBridgeTosStatus: use Array.isArray guard for additionalRequirements metadata - all gate checks: always return when gate is not ready, even if guardWithTos returns false (defensive)
|
@coderabbitai re-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 chain🏁 Script executed: Length of output: 52205 🏁 Script executed: Length of output: 77005
✅ Fixed Since Last Review
🐛 Remaining / New Bug Risks1.
|
| # | Scenario | Steps | Expected |
|---|---|---|---|
| 1 | Hard rejection | Log in as Bridge-hard-rejected user → attempt withdraw | "Verification issue" modal → "Contact support" → support drawer opens |
| 2 | Fixable rejection | Log in as Bridge-fixable-rejected user → attempt withdraw | "We need extra documents" → "Upload document" → Sumsub SDK inline |
| 3 | Self-heal error | Fixable user with unrecognized label → attempt upload | "Something went wrong" modal + "Contact support" |
| 4 | ToS (withdraw/deposit/claim) | User with REQUIRES_INFORMATION rails → trigger transfer |
ToS modal appears → iframe → accept → no modal flash → flow proceeds |
| 5 | QR pay — unverified | Scan QR as unverified user | Inline KYC modal (no redirect) → "Verify now" → SDK opens |
| 6 | QR pay — verified | Scan QR as fully-approved user | Proceeds to payment directly, no KYC prompt |
| 7 | SDK race condition | Navigate to identity verification → click "Start Verification" immediately | SDK stays open, does not auto-close |
| 8 | ToS network error | Accept ToS with BE unavailable | Error message shown, component not stuck/blank |
| 9 | Cross-region KYC failure | Trigger needs_enrollment → API error on initiate |
Error shown, future APPROVED transitions still work (verify prevStatusRef restored) |
| 10 | Happy path regression | Fully approved user: withdraw, deposit, claim | No KYC/ToS gates shown, all flows complete |
|
@coderabbitai re-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 chain🏁 Script executed: Length of output: 132633
✅ Fixed Since Last Review
🐛 Remaining / New Bug Risks1.
|
| # | Scenario | Steps | Expected |
|---|---|---|---|
| 1 | Hard rejection | Login as Bridge-hard-rejected user → attempt withdraw | "Verification issue" modal → "Contact support" → support drawer opens |
| 2 | Fixable rejection | Login as Bridge-fixable-rejected user → attempt withdraw | "We need extra documents" modal → "Upload document" → Sumsub SDK opens inline |
| 3 | Self-heal error (claim flow) | Fixable user → attempt claim → "Upload document" → API fails | Error should remain visible in modal (not silently closed) |
| 4 | ToS (withdraw/deposit/claim) | User with REQUIRES_INFORMATION rails → trigger transfer |
ToS modal → iframe → accept → no modal flash → flow proceeds |
| 5 | ToS network error | Accept ToS with BE unavailable | Error message shown, component not stuck |
| 6 | QR pay — unverified | Scan QR as unverified user | Inline KYC modal (no redirect) → "Verify now" → SDK opens |
| 7 | QR pay — verified | Scan QR as approved user | Proceeds to payment directly |
| 8 | SDK race condition | Navigate to identity verification → click "Start Verification" immediately | SDK stays open, not auto-closed |
| 9 | Cross-region KYC API error | Trigger needs_enrollment → API error on initiate |
Error shown in modal; future APPROVED events still fire correctly |
| 10 | Happy path regression | Fully approved user: withdraw, deposit, claim | No KYC/ToS gates, all flows complete successfully |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/hooks/useSumsubKycFlow.ts (1)
141-143:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRestore
prevStatusRefwhen cross-region initiation fails.Line 141 pre-sets
prevStatusRef.current = 'APPROVED'before initiation succeeds. If the request fails (Line 152 or Line 198), that value persists and can suppress later realAPPROVEDtransition handling (onKycSuccesspath).💡 Proposed fix
const handleInitiateKyc = useCallback( async (overrideIntent?: KYCRegionIntent, levelName?: string, crossRegion?: boolean) => { + const previousStatus = prevStatusRef.current userInitiatedRef.current = true initiatingRef.current = true selfHealProviderRef.current = null setIsLoading(true) setError(null) // for cross-region: pre-set prevStatusRef to APPROVED so the fetchCurrentStatus // effect (which also fires when regionIntent changes) doesn't trigger onKycSuccess // when it sees the existing APPROVED status. if (crossRegion) { prevStatusRef.current = 'APPROVED' } try { const response = await initiateSumsubKyc({ regionIntent: overrideIntent ?? regionIntent, levelName, crossRegion, }) if (response.error) { + if (crossRegion) prevStatusRef.current = previousStatus setError(response.error) return } ... } catch (e: unknown) { + if (crossRegion) prevStatusRef.current = previousStatus const message = e instanceof Error ? e.message : 'An unexpected error occurred' setError(message) } finally {Also applies to: 152-155, 198-200
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/hooks/useSumsubKycFlow.ts` around lines 141 - 143, When cross-region is set the code unconditionally overwrites prevStatusRef.current to 'APPROVED' before the initiation promise resolves; change this to save the original value (e.g., const prev = prevStatusRef.current), set prevStatusRef.current = 'APPROVED' only for the pending initiation, and on every failure/early-return path (the initiation catch/response-failure branches around the current initiation and the later failure branches tied to crossRegion) restore prevStatusRef.current = prev so the original status is preserved and onKycSuccess can still run correctly.
🧹 Nitpick comments (2)
src/components/AddWithdraw/DynamicBankAccountForm.tsx (1)
252-254: 💤 Low valueConsider replacing magic string with typed result.
The
'__silent__'sentinel works but is fragile. A typed return like{ error?: string; silent?: boolean }would be safer and self-documenting.Current approach functions correctly for this PR; flagging as optional improvement.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/AddWithdraw/DynamicBankAccountForm.tsx` around lines 252 - 254, Replace the fragile magic string check in DynamicBankAccountForm: instead of comparing result.error to '__silent__', change the API/handler that produces result (the value used by the submission logic around setSubmissionError and setIsSubmitting) to return a typed shape like { error?: string; silent?: boolean } and update the submission code to check result.silent (or !result.error) before calling setSubmissionError(result.error); also update any functions that construct this result to set silent: true when you intend to suppress UI errors so the logic in the submission handler (the block that currently checks result.error and calls setSubmissionError/setIsSubmitting) becomes explicit and type-safe.src/app/(mobile-ui)/add-money/[country]/bank/page.tsx (1)
400-435: ⚡ Quick winGate-to-variant mapping is duplicated across files.
This mapping logic (gate.type → InitiateKycModal variant) is repeated in BankFlowManager.view.tsx and AddWithdrawCountriesList.tsx. Consider extracting to a shared utility.
♻️ Suggested extraction
// utils/gate.utils.ts export function getKycModalVariant(gateType: BridgeGateAction['type']): 'blocked' | 'provider_rejection' | 'cross_region' | 'default' { switch (gateType) { case 'blocked_rejection': return 'blocked' case 'fixable_rejection': return 'provider_rejection' case 'needs_enrollment': return 'cross_region' default: return 'default' } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx around lines 400 - 435, The gate.type → InitiateKycModal variant mapping is duplicated; extract it into a shared function (e.g., getKycModalVariant) that accepts BridgeGateAction['type'] and returns 'blocked' | 'provider_rejection' | 'cross_region' | 'default', then replace the inline nested ternary in InitiateKycModal's variant prop in Add-money page with a call to getKycModalVariant(gate.type) and similarly import/use that function in BankFlowManager.view.tsx and AddWithdrawCountriesList.tsx so all files share the single mapping implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(mobile-ui)/withdraw/[country]/bank/page.tsx:
- Around line 172-179: The guard re-uses a stale render-time gate value and
re-triggers the ToS flow; modify handleCreateAndInitiateOfframp (and its
callers) to accept a bypass flag (e.g., bypassGate:boolean) or await gate
refresh: update BridgeTosStep.onComplete/confirmBridgeTosAndAwaitRails() to call
handleCreateAndInitiateOfframp(true) after confirming ToS, and inside
handleCreateAndInitiateOfframp check bypassGate before performing the gate.type
!== 'ready' early-return (instead of using the closed-over gate), or
alternatively re-read the current gate from Redux/store and wait until it
becomes 'ready' before proceeding; ensure guardWithTos and setShowKycModal are
only used when not bypassing.
In `@src/components/Claim/Link/views/BankFlowManager.view.tsx`:
- Around line 516-526: The modal is being closed unconditionally in the onVerify
handler which can hide errors from sumsubFlow (e.g., sumsubFlow.error) when
sumsubFlow.handleSelfHealResubmit('BRIDGE') or sumsubFlow.handleInitiateKyc(...)
fails; update the onVerify logic so setShowKycModal(false) is called only after
the async call succeeds (e.g., await the call inside try and call
setShowKycModal(false) in the try path), and in the catch path keep the modal
open and surface sumsubFlow.error (or display an inline error) so users see
failures instead of the modal disappearing. Ensure you update the branch
handling for gate.type === 'fixable_rejection' (handleSelfHealResubmit) and the
else branch (handleInitiateKyc) accordingly.
---
Outside diff comments:
In `@src/hooks/useSumsubKycFlow.ts`:
- Around line 141-143: When cross-region is set the code unconditionally
overwrites prevStatusRef.current to 'APPROVED' before the initiation promise
resolves; change this to save the original value (e.g., const prev =
prevStatusRef.current), set prevStatusRef.current = 'APPROVED' only for the
pending initiation, and on every failure/early-return path (the initiation
catch/response-failure branches around the current initiation and the later
failure branches tied to crossRegion) restore prevStatusRef.current = prev so
the original status is preserved and onKycSuccess can still run correctly.
---
Nitpick comments:
In `@src/app/`(mobile-ui)/add-money/[country]/bank/page.tsx:
- Around line 400-435: The gate.type → InitiateKycModal variant mapping is
duplicated; extract it into a shared function (e.g., getKycModalVariant) that
accepts BridgeGateAction['type'] and returns 'blocked' | 'provider_rejection' |
'cross_region' | 'default', then replace the inline nested ternary in
InitiateKycModal's variant prop in Add-money page with a call to
getKycModalVariant(gate.type) and similarly import/use that function in
BankFlowManager.view.tsx and AddWithdrawCountriesList.tsx so all files share the
single mapping implementation.
In `@src/components/AddWithdraw/DynamicBankAccountForm.tsx`:
- Around line 252-254: Replace the fragile magic string check in
DynamicBankAccountForm: instead of comparing result.error to '__silent__',
change the API/handler that produces result (the value used by the submission
logic around setSubmissionError and setIsSubmitting) to return a typed shape
like { error?: string; silent?: boolean } and update the submission code to
check result.silent (or !result.error) before calling
setSubmissionError(result.error); also update any functions that construct this
result to set silent: true when you intend to suppress UI errors so the logic in
the submission handler (the block that currently checks result.error and calls
setSubmissionError/setIsSubmitting) becomes explicit and type-safe.
🪄 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: 732fdfd6-8386-4830-816e-80a449d4f094
📒 Files selected for processing (17)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsxsrc/app/(mobile-ui)/qr-pay/page.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/app/(mobile-ui)/withdraw/manteca/page.tsxsrc/app/actions/sumsub.tssrc/components/AddWithdraw/AddWithdrawCountriesList.tsxsrc/components/AddWithdraw/DynamicBankAccountForm.tsxsrc/components/Claim/Link/views/BankFlowManager.view.tsxsrc/components/Home/ActivationCTAs.tsxsrc/components/Kyc/BridgeTosStep.tsxsrc/components/Kyc/InitiateKycModal.tsxsrc/components/Kyc/states/KycProviderRejection.tsxsrc/components/Profile/views/RegionsVerification.view.tsxsrc/hooks/useBridgeTosStatus.tssrc/hooks/useBridgeTransferReadiness.tssrc/hooks/useProviderRejectionStatus.tssrc/hooks/useSumsubKycFlow.ts
…lt, dedup variant mapping
- useSumsubKycFlow: save/restore prevStatusRef on crossRegion failure to prevent
suppressing subsequent legitimate APPROVED transitions
- DynamicBankAccountForm: replace __silent__ magic string with typed { silent: true }
- useBridgeTransferReadiness: extract getKycModalVariant() and getGateProviderMessage()
helpers to deduplicate gate→modal mapping across 4 files
- BankFlowManager: only close kyc modal if sdk opened (check showWrapper), preserving
error visibility on failure
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/app/(mobile-ui)/withdraw/[country]/bank/page.tsx (1)
448-454:⚠️ Potential issue | 🟠 Major | ⚡ Quick winStale
gateclosure causes re-entry into TOS guard after completion.After
BridgeTosStep.onCompletecallsconfirmBridgeTosAndAwaitRails(), thegatevalue captured in the closure still holds'accept_tos'. WhenhandleCreateAndInitiateOfframp()is immediately called, the gate check at line 176 will re-triggerguardWithTos()instead of proceeding.🐛 Suggested fix — add bypass flag
- const handleCreateAndInitiateOfframp = async () => { - if (gate.type !== 'ready') { + const handleCreateAndInitiateOfframp = async (skipGateCheck = false) => { + if (!skipGateCheck && gate.type !== 'ready') { if (gate.type === 'accept_tos') { guardWithTos() } else { setShowKycModal(true) } return }Then update the BridgeTosStep callback:
<BridgeTosStep visible={showBridgeTos} onComplete={() => { hideTos() - handleCreateAndInitiateOfframp() + handleCreateAndInitiateOfframp(true) }} onSkip={hideTos} />🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/app/`(mobile-ui)/withdraw/[country]/bank/page.tsx around lines 448 - 454, The gate closure still reads 'accept_tos' causing guardWithTos to re-run right after BridgeTosStep.onComplete; fix by adding a bypass flag (e.g., bypassTos: boolean) to handleCreateAndInitiateOfframp and make the early gate check skip guardWithTos when bypassTos is true, then update the BridgeTosStep onComplete callback to call hideTos(), await confirmBridgeTosAndAwaitRails() if needed, and invoke handleCreateAndInitiateOfframp(true); ensure guardWithTos remains unchanged except for honoring the bypassTos parameter.
🧹 Nitpick comments (1)
src/components/AddWithdraw/AddWithdrawCountriesList.tsx (1)
99-112: ⚡ Quick win
fetchUser()on every submission adds unnecessary latency when gate blocks.The call to
fetchUser()at line 101 runs unconditionally before checking the gate. When the gate is not ready (TOS/KYC/rejection), this network call adds latency without benefit since the action will be blocked anyway. Consider moving the fetch after the gate check or making it conditional.♻️ Suggested optimization
const handleFormSubmit = async ( payload: AddBankAccountPayload, rawData: IBankAccountDetails ): Promise<{ error?: string; silent?: boolean }> => { - // re-fetch user to ensure we have the latest KYC status - // (the multi-phase flow may have completed but websocket/state not yet propagated) - await fetchUser() - // unified bridge gate: tos → fixable rejection → blocked → enrollment // return a non-visible error to prevent the form from treating this as success if (gate.type !== 'ready') { if (gate.type === 'accept_tos') { guardWithTos() } else { setIsKycModalOpen(true) } return { error: 'gate_blocked', silent: true } } + // re-fetch user to ensure we have the latest KYC status + // (the multi-phase flow may have completed but websocket/state not yet propagated) + await fetchUser() + // scenario (1): happy path...🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx` around lines 99 - 112, The fetchUser() call is invoked unconditionally before the gate check, causing unnecessary latency when the bridge gate blocks; update the flow in the submission handler so you first inspect gate.type and handle non-'ready' cases (calling guardWithTos() or setIsKycModalOpen(true) and returning the { error: 'gate_blocked', silent: true } early), and only call fetchUser() afterwards (or call it conditionally when gate.type === 'ready') to avoid the redundant network request.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/Claim/Link/views/BankFlowManager.view.tsx`:
- Around line 509-516: The BridgeTosStep.onComplete closure captures a stale
gate value ('accept_tos') causing guardWithTos() to re-trigger when
handleCreateOfframpAndClaim(localBankDetails) is called; update
handleCreateOfframpAndClaim (or add an overload) to accept a bypassTos boolean
and, when true, skip the gate/guardWithTos() check, then change the onComplete
callback to call hideTos() followed by
handleCreateOfframpAndClaim(localBankDetails, true) (or the new bypass param) so
the Tos acceptance gate is not re-evaluated from the stale closure; ensure
BridgeTosStep, onComplete, hideTos, handleCreateOfframpAndClaim and the
gate/guardWithTos logic are the referenced symbols updated.
In `@src/hooks/useBridgeTransferReadiness.ts`:
- Around line 44-50: The ready branch currently returns { type: 'ready' }
without ensuring any KYC is approved, allowing unapproved users through; in
useBridgeTransferReadiness.ts update the final readiness check so that the
'ready' state is only returned when KYC is satisfied (e.g., require
isUserSumsubKycApproved OR isUserBridgeKycApproved, or use a consolidated
isKycApproved boolean) and otherwise return a proper blocking state (e.g.,
'needs_enrollment' or 'kyc_required'); adjust the logic around
isUserSumsubKycApproved, isUserBridgeKycApproved and isUserBridgeKycUnderReview
to enforce the documented priority order and ensure the function
(useBridgeTransferReadiness / its return paths) never falls through to 'ready'
for users without KYC approval.
---
Duplicate comments:
In `@src/app/`(mobile-ui)/withdraw/[country]/bank/page.tsx:
- Around line 448-454: The gate closure still reads 'accept_tos' causing
guardWithTos to re-run right after BridgeTosStep.onComplete; fix by adding a
bypass flag (e.g., bypassTos: boolean) to handleCreateAndInitiateOfframp and
make the early gate check skip guardWithTos when bypassTos is true, then update
the BridgeTosStep onComplete callback to call hideTos(), await
confirmBridgeTosAndAwaitRails() if needed, and invoke
handleCreateAndInitiateOfframp(true); ensure guardWithTos remains unchanged
except for honoring the bypassTos parameter.
---
Nitpick comments:
In `@src/components/AddWithdraw/AddWithdrawCountriesList.tsx`:
- Around line 99-112: The fetchUser() call is invoked unconditionally before the
gate check, causing unnecessary latency when the bridge gate blocks; update the
flow in the submission handler so you first inspect gate.type and handle
non-'ready' cases (calling guardWithTos() or setIsKycModalOpen(true) and
returning the { error: 'gate_blocked', silent: true } early), and only call
fetchUser() afterwards (or call it conditionally when gate.type === 'ready') to
avoid the redundant network request.
🪄 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: 9c15ab31-8a2e-4886-8b80-f9aefcaa0b3a
📒 Files selected for processing (7)
src/app/(mobile-ui)/add-money/[country]/bank/page.tsxsrc/app/(mobile-ui)/withdraw/[country]/bank/page.tsxsrc/components/AddWithdraw/AddWithdrawCountriesList.tsxsrc/components/AddWithdraw/DynamicBankAccountForm.tsxsrc/components/Claim/Link/views/BankFlowManager.view.tsxsrc/hooks/useBridgeTransferReadiness.tssrc/hooks/useSumsubKycFlow.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/hooks/useSumsubKycFlow.ts
Follow-up items (not blocking merge)1. Unit tests for
|
…minate SDK auto-close race the fetchCurrentStatus effect could still race with handleInitiateKyc in rare cases — initiatingRef resets in the finally block before React batches the showWrapper state update. adding userInitiatedRef as a guard permanently disables the background fetch after any user-initiated flow, since the SDK and websocket handle status updates from that point.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/app/`(mobile-ui)/withdraw/manteca/page.tsx:
- Around line 411-414: The current useEffect in the component unconditionally
clears balanceErrorMessage on usdAmount or balance changes (useEffect(() =>
setBalanceErrorMessage(null), [usdAmount, balance])), which bypasses
insufficient-balance validation; replace that effect with logic that computes
whether usdAmount > balance and sets balanceErrorMessage to a descriptive error
when true and to null when false (i.e., validate inside the useEffect and call
setBalanceErrorMessage accordingly), ensuring the amount step blocks when the
requested amount exceeds the wallet balance.
In `@src/app/`(mobile-ui)/withdraw/page.tsx:
- Around line 168-175: The validateAmount function and the isContinueDisabled
gating were temporarily stubbed and must be restored: implement validateAmount
to parse the input string to a decimal/number, enforce minUsdAmount and max USD
thresholds (use the existing minUsdAmount calculation), enforce wallet balance
constraint using maxDecimalAmount, setError appropriately with descriptive
messages when invalid, and return true only when the amount passes all checks;
update isContinueDisabled to also check that parsed amount is finite and
positive AND that it respects minUsdAmount, maxDecimalAmount (wallet balance),
the component's limits validation blocking state, and the current error state so
users cannot continue when any of those validations fail. Ensure you reference
and use minUsdAmount, maxDecimalAmount, setError, and the limits validation
gating variables present in this file.
🪄 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: 2cfb6f5e-31fa-4a56-ab54-1b6614db126a
📒 Files selected for processing (3)
src/app/(mobile-ui)/withdraw/manteca/page.tsxsrc/app/(mobile-ui)/withdraw/page.tsxsrc/hooks/useSumsubKycFlow.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- src/hooks/useSumsubKycFlow.ts
Manual Testing Results — 20/20 PassedAll tests performed locally with FE worktree pointed to local BE + local DB (staging Bridge/Sumsub API keys). DB state was modified between tests to simulate different user states. Bridge Bank Flows (Withdraw / Deposit)
QR Pay Flow
Identity Verification Page
Cross-Region Enrollment
Claim Flow
Priority Order Verification
Summary26/26 tests passed (23 manual + 3 covered by unit tests in Accounts used:
|
Summary
useBridgeTransferReadiness): single hook enforcing correct priority across all Bridge bank flows — hard rejection → ToS acceptance → fixable rejection → enrollment → readyuseBridgeTosStatus): now checkstos_acceptance/tos_v2_acceptancein railadditionalRequirementsmetadata, not justREQUIRES_INFORMATIONstatus/profile/identity-verificationwith inline Sumsub SDK (handleInitiateKyc('LATAM'))$crisp.pushwithsetIsSupportModalOpenin KycProviderRejection, ActivationCTAs, RegionsVerificationuseSumsubKycFlowwherefetchCurrentStatusclosed the SDK wrapper on first attemptInitiateKycModalnow shows error state with "Contact support" when self-heal/KYC initiation failsFiles changed (15)
hooks/useBridgeTransferReadiness.tshooks/useBridgeTosStatus.tsadditionalRequirementsfor ToShooks/useSumsubKycFlow.tsfetchCurrentStatuswithshowWrapperRef)hooks/useProviderRejectionStatus.tscomponents/Kyc/InitiateKycModal.tsxblockedvariant,errorpropcomponents/Kyc/BridgeTosStep.tsxisConfirmingstatecomponents/Kyc/states/KycProviderRejection.tsx$crisp→setIsSupportModalOpencomponents/Home/ActivationCTAs.tsx$crisp→setIsSupportModalOpencomponents/Profile/views/RegionsVerification.view.tsx$crisp→setIsSupportModalOpenapp/(mobile-ui)/withdraw/[country]/bank/page.tsxapp/(mobile-ui)/add-money/[country]/bank/page.tsxapp/(mobile-ui)/qr-pay/page.tsxcomponents/AddWithdraw/AddWithdrawCountriesList.tsx__silent__sentinelcomponents/AddWithdraw/DynamicBankAccountForm.tsx__silent__error displaycomponents/Claim/Link/views/BankFlowManager.view.tsxBackend dependency
Requires peanutprotocol/peanut-api-ts#fix/bridge-tos-transfer-guard for:
fetchCustomerByIdcheck)confirm-bridge-tosfull status processor (correct rail state after ToS)get-bridge-tos-linkfallback for non-REQUIRES_INFORMATION railsinitiate-kycregionIntent overwrite for non-approved usersTesting done
Tested locally with FE pointed to local BE + staging DB:
Test plan