Skip to content

ENG-3063 / ENG-3064 / ENG-3065: KYC verification gate on the stake enter flow#521

Open
auralshin wants to merge 3 commits into
mainfrom
feat/kyc
Open

ENG-3063 / ENG-3064 / ENG-3065: KYC verification gate on the stake enter flow#521
auralshin wants to merge 3 commits into
mainfrom
feat/kyc

Conversation

@auralshin
Copy link
Copy Markdown

@auralshin auralshin commented Jun 2, 2026

Added

  • Inline KYC gate in the staking enter flow. When a user enters a yield that requires KYC, the widget checks their verification status for the connected address and, if they're not approved, shows an "Identity verification required" screen instead of proceeding to review.
  • /kyc gate route that resolves the status and either continues to /review (approved / not_required) or renders the verification screen (not_started / pending / rejected) and fails closed if the status can't be read.
  • Iframe modal that embeds the issuer KYC page.
  • KYC status call: getKycStatus(yieldId, address) on the API client and a useKycStatus hook, with a non-blocking background prefetch when a KYC-requiring yield is selected.
  • KYC domain types + helpers status/metadata types and getYieldKycRequirement / kycNeedsVerification / resolveKycUrl.

Changed

  • Enter-flow CTA (earn page) now branches to /kyc for KYC-required yields instead of going straight to /review, and prefetches KYC status on yield selection so the gate decision is instant.
  • API client gains a getKycStatus method that runs through the configured yields client (manually added currently).
  • Router (Widget.tsx) registers the new /kyc route inside the connected stake flow.
file-5290262455935d4d7f67c25f14adeba2 file-0ccb43c3a5f1bb1f9e51eeb222c9ebbc

Summary by CodeRabbit

  • New Features

    • Identity verification gating for select yields — users may be routed to a verification screen during staking; new in-app verification modal (iframe) and identity verification page.
    • Added shield/check icon used in verification UI.
  • Localization

    • KYC screens added in English and French.
  • Tests

    • End-to-end and unit tests for the KYC gate flow and helper logic.

@auralshin auralshin self-assigned this Jun 2, 2026
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 2, 2026

⚠️ No Changeset found

Latest commit: 0586284

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

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: c846a5f7-e2dd-4b88-997d-b345c1578c9e

📥 Commits

Reviewing files that changed from the base of the PR and between f344670 and 0586284.

📒 Files selected for processing (3)
  • packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx
  • packages/widget/src/translation/English/translations.json
  • packages/widget/src/translation/French/translations.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/widget/src/translation/English/translations.json
  • packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx
  • packages/widget/src/translation/French/translations.json

📝 Walkthrough

Walkthrough

Adds a KYC identity verification flow: domain types/helpers, API client endpoint, React Query hooks and prefetching, UI atom/icon, modal + styles, gate and identity pages, router integration, translations, and tests.

Changes

KYC Identity Verification Feature

Layer / File(s) Summary
KYC domain types and helpers
packages/widget/src/domain/types/kyc.ts
Defines KYC enums, eligibility and metadata shapes, KycStatusResult, and helpers: getYieldKycRequirement, kycNeedsVerification, resolveKycUrl.
API client KYC status endpoint
packages/widget/src/providers/api/api-client.ts
Adds getKycStatus(yieldId, address) to the API client calling /v1/yields/{yieldId}/kyc/status and returning KycStatusResult.
React Query hooks and prefetching
packages/widget/src/hooks/api/use-kyc-status.ts
Adds useKycStatus (useQuery wrapper, 30s staleTime, conditional enable) and useKycStatusPrefetch (prefetch when wallet connected and yield requires KYC).
KYC UI: icon, modal, and styles
packages/widget/src/components/atoms/icons/shield-check.tsx, packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx, packages/widget/src/components/molecules/kyc-iframe-modal/styles.css.ts
Adds ShieldCheckIcon; KycIframeModal (Radix Dialog with iframe and data-testid attributes); and Vanilla Extract styles with slideUp/fadeIn and responsive content/overlay/iframe rules.
KYC gate and identity pages
packages/widget/src/pages/kyc/kyc-gate.page.tsx, packages/widget/src/pages/kyc/identity-verification.page.tsx
Adds KycGatePage (reads selected stake, fetches KYC status, handles loading/redirects, renders identity screen and modal) and IdentityVerificationScreen (status-based copy, icon color, footer verify button wiring).
Router and earn page navigation changes
packages/widget/src/Widget.tsx, packages/widget/src/pages/details/earn-page/state/earn-page-context.tsx
Adds /kyc route. Earn page now prefetches KYC status and navigates to /kyc if the selected yield requires verification, otherwise to /review.
Translations and tests
packages/widget/src/translation/English/translations.json, packages/widget/src/translation/French/translations.json, packages/widget/tests/use-cases/kyc-flow/*
Adds kyc translation blocks for EN/FR and tests: helper unit tests, KYC test setup (MSW handlers), and an end-to-end KYC gate flow test asserting identity screen and iframe modal.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • dnehl
  • jdomingos
  • Philippoes

Poem

A rabbit hops through verification gates,
With shields and modals shining bright,
The KYC path now softly waits,
Stakers stride in morning light,
🐰✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The PR title references specific issue numbers (ENG-3063/3064/3065) and accurately summarizes the main change: adding a KYC verification gate to the stake entry flow.
Description check ✅ Passed The PR description follows the template with clear 'Added' and 'Changed' sections detailing all new functionality, modifications, and includes relevant visual diagrams illustrating the verification flow states.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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 feat/kyc

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

@aws-amplify-eu-central-1
Copy link
Copy Markdown

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-521.df4xyoi0xyeak.amplifyapp.com

@aws-amplify-eu-central-1
Copy link
Copy Markdown

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-521.d2ribjy8evqo6h.amplifyapp.com

Copy link
Copy Markdown

@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

🧹 Nitpick comments (2)
packages/widget/src/providers/api/api-client.ts (1)

252-270: ⚖️ Poor tradeoff

Unvalidated response cast.

data as KycStatusResult trusts the payload shape without runtime validation, unlike the schema-backed generated operations. A malformed kycStatus would flow straight into kycNeedsVerification, which only matches known enum values — an unexpected string would be treated as "no verification needed" downstream. The current gate happens to fail closed on missing data, so impact is limited; consider validating kycStatus against KycStatus once the contract lands in the generated client.

🤖 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 `@packages/widget/src/providers/api/api-client.ts` around lines 252 - 270, The
getKycStatus function currently casts the HTTP response to KycStatusResult
without runtime validation; change getKycStatus to validate the parsed response
(specifically the kycStatus field) against the KycStatus enum/allowed values
before returning—use KycStatus (or a small validator) to accept only known enum
values, map valid values into the KycStatusResult, and throw or return a
controlled error/fallback for unknown/malformed values so downstream logic like
kycNeedsVerification cannot silently treat invalid strings as "no verification
needed."
packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx (1)

52-58: ⚡ Quick win

Consider adding error handling for iframe load failures.

If the iframe fails to load (network error, blocked content, etc.), users will see a blank modal with no feedback. Consider adding an error boundary or onError handler to provide user feedback.

🛡️ Example implementation
+            const [iframeError, setIframeError] = useState(false);
+
             <iframe
               data-testid="kyc-iframe-modal__iframe"
               title={title}
               src={url}
               allow="camera; microphone; clipboard-write"
               className={iframe}
+              onError={() => setIframeError(true)}
             />
+            {iframeError && (
+              <Box p="4">
+                <Text>Failed to load verification page. Please try again.</Text>
+              </Box>
+            )}
🤖 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 `@packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx` around
lines 52 - 58, The iframe currently rendered in KycIframeModal (the <iframe
data-testid="kyc-iframe-modal__iframe" title={title} src={url}
className={iframe} />) has no failure handling; add an onError handler and local
error state in the KycIframeModal component to detect load failures and render a
fallback UI (error message + retry or close) instead of a blank modal; also
consider adding an onLoad/onLoadStart and a loading state to show a spinner
while the iframe loads and include the error state in any telemetry/logging.
🤖 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 `@packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx`:
- Around line 47-49: The close button rendered as <Box as="button" onClick={()
=> onOpenChange(false)}> with <XIcon /> has no accessible label; add an
accessible name by adding an aria-label (e.g., aria-label="Close" or
aria-label={t('close')} if using i18n) or include visually-hidden text inside
the button so screen readers announce its purpose; ensure the attribute is
applied to the same element using Box (the button element) that calls
onOpenChange and that any existing XIcon remains decorative (aria-hidden="true")
if applicable.

---

Nitpick comments:
In `@packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx`:
- Around line 52-58: The iframe currently rendered in KycIframeModal (the
<iframe data-testid="kyc-iframe-modal__iframe" title={title} src={url}
className={iframe} />) has no failure handling; add an onError handler and local
error state in the KycIframeModal component to detect load failures and render a
fallback UI (error message + retry or close) instead of a blank modal; also
consider adding an onLoad/onLoadStart and a loading state to show a spinner
while the iframe loads and include the error state in any telemetry/logging.

In `@packages/widget/src/providers/api/api-client.ts`:
- Around line 252-270: The getKycStatus function currently casts the HTTP
response to KycStatusResult without runtime validation; change getKycStatus to
validate the parsed response (specifically the kycStatus field) against the
KycStatus enum/allowed values before returning—use KycStatus (or a small
validator) to accept only known enum values, map valid values into the
KycStatusResult, and throw or return a controlled error/fallback for
unknown/malformed values so downstream logic like kycNeedsVerification cannot
silently treat invalid strings as "no verification needed."
🪄 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: 754fdc11-f0ea-415b-a05b-741de7d27146

📥 Commits

Reviewing files that changed from the base of the PR and between e7229e5 and 3d00697.

📒 Files selected for processing (15)
  • packages/widget/src/Widget.tsx
  • packages/widget/src/components/atoms/icons/shield-check.tsx
  • packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx
  • packages/widget/src/components/molecules/kyc-iframe-modal/styles.css.ts
  • packages/widget/src/domain/types/kyc.ts
  • packages/widget/src/hooks/api/use-kyc-status.ts
  • packages/widget/src/pages/details/earn-page/state/earn-page-context.tsx
  • packages/widget/src/pages/kyc/identity-verification.page.tsx
  • packages/widget/src/pages/kyc/kyc-gate.page.tsx
  • packages/widget/src/providers/api/api-client.ts
  • packages/widget/src/translation/English/translations.json
  • packages/widget/src/translation/French/translations.json
  • packages/widget/tests/use-cases/kyc-flow/kyc-flow.test.tsx
  • packages/widget/tests/use-cases/kyc-flow/kyc-helpers.test.ts
  • packages/widget/tests/use-cases/kyc-flow/setup.ts

Comment thread packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx Outdated
Copy link
Copy Markdown

@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)
packages/widget/src/domain/types/kyc.ts (1)

17-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore the exported KYC types.

Line 17 and Line 54 remove export from types that were previously part of this module's public surface. That is a source-compatible break for any caller importing KycAccreditation, KycSubjectType, YieldKycEligibility, or YieldKycRequirement, and it also makes the return type of getYieldKycRequirement harder to consume by name. Keep these exported unless this PR is intentionally shipping a breaking API change.

Suggested fix
-enum KycAccreditation {
+export enum KycAccreditation {
   Retail = "retail",
   QualifiedPurchaser = "qualified_purchaser",
   Accredited = "accredited",
 }

-enum KycSubjectType {
+export enum KycSubjectType {
   Kyc = "KYC",
   Kyb = "KYB",
 }

-interface YieldKycEligibility {
+export interface YieldKycEligibility {
   countries?: string[];
   usPersonAllowed?: boolean;
   accreditation?: KycAccreditation;
   subjectType?: KycSubjectType;
 }

-type YieldKycRequirement = {
+export type YieldKycRequirement = {
   required: boolean;
   kyc?: YieldKycMetadata;
   legacyKycUrl?: string;
 };

Also applies to: 54-58

🤖 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 `@packages/widget/src/domain/types/kyc.ts` around lines 17 - 33, The PR removed
exports for public KYC types causing a breaking change; restore the exports for
KycAccreditation, KycSubjectType, YieldKycEligibility and YieldKycRequirement so
consumers can import them by name and so getYieldKycRequirement's return type
remains referencable; locate the enum/type declarations (KycAccreditation,
KycSubjectType, YieldKycEligibility, YieldKycRequirement) and add the export
keyword back to each declaration and ensure any corresponding exports in index
barrels are kept in sync.
packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx (1)

10-15: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep KycIframeModalProps exported to avoid a public TS API break
KycIframeModalProps in packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx is now a non-exported type, so any consumer doing import type { KycIframeModalProps } ... will fail to compile. It’s also not imported anywhere in this repo, so re-exporting it (or explicitly documenting this as an intentional breaking change) is the safer 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 `@packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx` around
lines 10 - 15, KycIframeModalProps was made non-exported and will break
consumers importing the type; make it part of the public API by exporting it
(e.g., change the declaration to "export type KycIframeModalProps = { ... }") in
the KycIframeModal component file and ensure any package-level re-exports
(barrel/index export) also include KycIframeModalProps so downstream "import
type { KycIframeModalProps }" continues to work.
🤖 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.

Outside diff comments:
In `@packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx`:
- Around line 10-15: KycIframeModalProps was made non-exported and will break
consumers importing the type; make it part of the public API by exporting it
(e.g., change the declaration to "export type KycIframeModalProps = { ... }") in
the KycIframeModal component file and ensure any package-level re-exports
(barrel/index export) also include KycIframeModalProps so downstream "import
type { KycIframeModalProps }" continues to work.

In `@packages/widget/src/domain/types/kyc.ts`:
- Around line 17-33: The PR removed exports for public KYC types causing a
breaking change; restore the exports for KycAccreditation, KycSubjectType,
YieldKycEligibility and YieldKycRequirement so consumers can import them by name
and so getYieldKycRequirement's return type remains referencable; locate the
enum/type declarations (KycAccreditation, KycSubjectType, YieldKycEligibility,
YieldKycRequirement) and add the export keyword back to each declaration and
ensure any corresponding exports in index barrels are kept in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a3f132fe-ee4f-430b-8f12-7e2887dfc8ed

📥 Commits

Reviewing files that changed from the base of the PR and between 3d00697 and f344670.

📒 Files selected for processing (2)
  • packages/widget/src/components/molecules/kyc-iframe-modal/index.tsx
  • packages/widget/src/domain/types/kyc.ts

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant