Commit bfe02ee
feat: KYC controller (#9615)
# `@metamask/kyc-controller`
## Overview
`@metamask/kyc-controller` is a shared, **platform-agnostic KYC /
identity-verification** package for MetaMask clients (mobile, extension,
web). It centralizes all KYC orchestration logic in the `core` monorepo
so every client consumes one implementation instead of maintaining its
own.
## What's included
### Core modules
- **`KycController`** (`src/KycController.ts`) — A `BaseController` that
owns the entire identity flow:
- The **flow state machine**: `idle → terms → session → check → auth →
form → submit → done / error`.
- The **Check/Auth frame message protocol** for communicating with
MoonPay's hosted frames (`blocks.moonpay.com`) over dedicated channels
(`ch_1`, `ch_2`, `ch_reset`).
- **X25519 credential decryption** and key-pair generation.
- **SumSub orchestration** through an injected `KycSumSubLauncher`
adapter, keeping the controller SDK-free while each client injects its
own launcher.
- **Product-scoped auto-continuation**: an optional `product` (`ramps` |
`card`) passed to `initialize` / `acceptTermsAndStartSession` is stored
as `activeProduct`. Once authentication reaches the `form` phase, the
controller **automatically** runs the KYC-required check and, when KYC
is required, chains into the SumSub sub-flow — with no extra consumer
calls. Without a `product`, the flow stops at `form` for manual control
(`checkKycRequired` / `startSumSub`).
- A per-product **"is KYC required" cache** and security-aware state
metadata (session/access tokens are marked non-persisted and
non-logged).
- **`KycService`** (`src/KycService.ts`) — A data service that performs
the **Universal KYC (UKYC) HTTP calls** via an injected `fetch`. It
wraps requests with `createServicePolicy` for resilience, validates
responses with `superstruct`, and sources the auth bearer token (from
`profile-sync-controller`) and geolocation (from
`geolocation-controller`) through the messenger. It also accepts an
optional `baseUrl` to override the `env`-derived URL for local/staging
APIs.
### Supporting modules
- **`crypto.ts`** — X25519 key-pair generation and encrypted-credential
decryption.
- **`countryCodes.ts`** — ISO 3166-1 alpha-2 → alpha-3 country-code
mapping.
- **`selectors.ts`** — Memoized state selectors (`selectKycPhase`,
`selectKycSumSub`, `selectIsKycRequiredForProduct`).
- **`types.ts`** — Shared vendor-neutral types (`KycPhase`,
`KycProduct`, `KycVendor`, `KycSumSubLauncher`, `KycDisclaimer`, etc.).
- **`KycController-method-action-types.ts` /
`KycService-method-action-types.ts`** — Messenger action type
definitions for the controller and service.
- **`index.ts`** — Explicit (non-barrel) exports defining the package's
public API.
### Tests
Comprehensive Jest test suites covering the controller, service, crypto,
country codes, selectors, and the index/public API (`KycController.ts`
at 100% coverage, including the new auto-continuation paths).
### Package scaffolding & monorepo wiring
- Standard package files: `package.json`, `tsconfig.json`,
`tsconfig.build.json`, `jest.config.js`, `typedoc.json`, `CHANGELOG.md`,
`README.md`, and the `LICENSE`, `LICENSE.APACHE2`, and `LICENSE.MIT`
files.
- Registered in the root `tsconfig.json` / `tsconfig.build.json` project
references, added to the root `README.md` package list, and reflected in
`yarn.lock`.
## Public API
The package explicitly exports:
- **Controller:** `KycController`, `getDefaultKycControllerState`,
`controllerName`, plus its messenger, options, state, action, and event
types. `initialize` / `acceptTermsAndStartSession` accept an optional
`product` to enable the automatic post-authentication continuation.
- **Service:** `KycService`, `serviceName`, plus its messenger, options,
params, response, action, and event types.
- **Selectors:** `selectIsKycRequiredForProduct`, `selectKycPhase`,
`selectKycSumSub`.
- **Utilities:** `alpha2ToAlpha3`, `ALPHA2_TO_ALPHA3`,
`decryptCredentials`, `generateKeyPair`, and related crypto/domain
types.
## Note
The SumSub sub-flow currently uses placeholders (`MOCK_JWT_TOKEN` and a
hardcoded `vendorUserId`), which must be replaced with real UKYC-issued
credentials before production use.
## Checklist
- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [ ] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **High Risk**
> New identity/KYC surface handling access tokens, encryption keys, JWT
attestation, and user storage; incorrect flow guards or crypto could
block verification or mishandle secrets, and mock JWT remains for
production.
>
> **Overview**
> Adds **`@metamask/kyc-controller`**, a new shared package that
orchestrates vendor-neutral KYC for **`ramps`** and **`card`**:
**`KycController`** runs the phase machine (terms → MoonPay Check/Auth
frames → KYC-required → SumSub), decrypts frame credentials
(X25519/AES-GCM), and **`KycService`** calls the Universal KYC API with
bearer auth, geolocation, retries, and **superstruct** validation.
>
> **Product-scoped flows** store `activeProduct` and, after
authentication, automatically run the KYC-required check and chain into
document verification when needed; **`reset()`** and generation/phase
guards prevent stale async work and late frame messages from corrupting
state. The SumSub path adds UKYC wrapping-key verification (JWKS
**`jwtChain`**), **`local_user_secret`** via **User Storage**, read-only
**`ukyc_capability_token`**, SDK launch through an injected
**`KycSumSubLauncher`**, and post-SDK session-status polling.
>
> Monorepo updates assign **Universal KYC** ownership, wire the package
into the dependency graph, relax ESLint for the dev
**`mint:ukyc-token`** script, and document architecture in
**`ARCHITECTURE.md`**. **Note:** **`startSumSub`** still uses a
**`MOCK_JWT_TOKEN`** placeholder until real UKYC credentials are wired.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
5a29cbb. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Signed-off-by: Sébastien Van Eyck <sebastien.vaneyck@consensys.net>
Co-authored-by: Jiexi Luan <jiexiluan@gmail.com>
Co-authored-by: Bjorn <bjorn.gunnarsson@consensys.net>
Co-authored-by: Cursor <cursoragent@cursor.com>1 parent ecf4e9d commit bfe02ee
46 files changed
Lines changed: 9370 additions & 27 deletions
File tree
- .github
- packages/kyc-controller
- scripts
- src
- ukyc
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
79 | 82 | | |
80 | 83 | | |
81 | 84 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
426 | 426 | | |
427 | 427 | | |
428 | 428 | | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
429 | 435 | | |
430 | 436 | | |
431 | 437 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
514 | 514 | | |
515 | 515 | | |
516 | 516 | | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
517 | 521 | | |
518 | 522 | | |
519 | 523 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
314 | 314 | | |
315 | 315 | | |
316 | 316 | | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
317 | 326 | | |
318 | 327 | | |
319 | 328 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
12 | | - | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
13 | 15 | | |
14 | 16 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
13 | 21 | | |
14 | 22 | | |
15 | 23 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| 45 | + | |
45 | 46 | | |
46 | 47 | | |
47 | 48 | | |
48 | 49 | | |
49 | | - | |
50 | | - | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
51 | 53 | | |
52 | 54 | | |
53 | 55 | | |
54 | 56 | | |
55 | 57 | | |
56 | 58 | | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
57 | 76 | | |
58 | 77 | | |
59 | 78 | | |
60 | 79 | | |
| 80 | + | |
61 | 81 | | |
62 | 82 | | |
| 83 | + | |
63 | 84 | | |
64 | 85 | | |
65 | 86 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
0 commit comments