Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
1166c0c
feat: init KYC controller and add logic from mobile repo
Akaryatrh Jul 22, 2026
18c67ef
feat: update readme with architecture doc
Akaryatrh Jul 23, 2026
400b874
feat: add product support + automatic calls to kyc required and sumsu…
Akaryatrh Jul 23, 2026
9b3de4c
fix: geoCountry not updated issue
Akaryatrh Jul 23, 2026
133fa9b
fix: previously selected product is not reset
Akaryatrh Jul 23, 2026
89ac0b4
fix: issue with sumsub complete status
Akaryatrh Jul 23, 2026
3392d00
feat: update lock file
Akaryatrh Jul 23, 2026
3de6713
fix: issue with old accessToken and state update while controller has…
Akaryatrh Jul 23, 2026
49ec4f1
fix: another race condition
Akaryatrh Jul 23, 2026
20cff98
fix: add links in changelog
Akaryatrh Jul 23, 2026
abc47a6
fix: lint issue
Akaryatrh Jul 24, 2026
461473d
fix: dedupe dependencies
Akaryatrh Jul 24, 2026
41275fc
fix: cursor comments + state management
Akaryatrh Jul 24, 2026
cb246ed
fix: session state issue
Akaryatrh Jul 24, 2026
194d216
fix: lockfile after rebase
Akaryatrh Jul 24, 2026
85a764f
fix: review again state management
Akaryatrh Jul 24, 2026
ed8497c
feat: update architecture section of readme
Akaryatrh Jul 24, 2026
3d2cf99
feat: update codeowner files
Akaryatrh Jul 24, 2026
2c4805d
fix: lint issues
Akaryatrh Jul 24, 2026
1983026
replace idosSessionId with sessionId
jiexi Jul 27, 2026
a1fcb5b
Initial key derivation, wrapping, and submission
jiexi Jul 27, 2026
b9bc735
add ukycCapabilityToken submission
jiexi Jul 28, 2026
02afb3a
WIP
jiexi Jul 28, 2026
cf28727
Merge branch 'main' into feat/kyc-controller
jiexi Jul 28, 2026
966ca62
Fix key wrapping. Fix idosSessionId param
jiexi Jul 29, 2026
9c0dee1
Merge branch 'jl/kyc-controller-fix-nonce' into feat/kyc-controller
jiexi Jul 29, 2026
8eee01d
rename fetchApplicantAccessToken to createJourney
jiexi Jul 30, 2026
40bb435
polling status
jiexi Jul 30, 2026
9b99126
Merge remote-tracking branch 'origin/main' into feat/kyc-controller
Akaryatrh Jul 31, 2026
54b16fd
feat: update lockfile after merge with main
Akaryatrh Jul 31, 2026
f69ee7e
fix: lint issues
Akaryatrh Jul 31, 2026
3c32929
fix: codeowner issue
Akaryatrh Jul 31, 2026
e35605e
fix: deps constraints
Akaryatrh Jul 31, 2026
dda438f
fix: eslint file-extension-in-import
Akaryatrh Jul 31, 2026
166c8b1
fix: import lint issue
Akaryatrh Jul 31, 2026
f4b81bc
fix: deps version
Akaryatrh Jul 31, 2026
7cfb7a3
fix: messenger action type check
Akaryatrh Jul 31, 2026
1d983a3
feat: expose as a temporary solution kycstatus and finalstatus on rel…
Akaryatrh Aug 5, 2026
e31c5ed
Merge remote-tracking branch 'origin/main' into feat/kyc-controller
Akaryatrh Aug 6, 2026
2664c92
fix: remove barrel files and useless exports
Akaryatrh Aug 6, 2026
26b1ded
feat: refactor kyc service to base service pattern
Akaryatrh Aug 6, 2026
7e5464c
feat: add comments about vendorProcessing
Akaryatrh Aug 6, 2026
f17b9ea
fix: remove type cast
Akaryatrh Aug 6, 2026
11969e2
feat: move architecture info to a dedicated .md file
Akaryatrh Aug 6, 2026
52875be
feat: remove trailing logs
Akaryatrh Aug 6, 2026
ade32c8
feat: baseUrl is now mandatory for KYC service initialization
Akaryatrh Aug 6, 2026
2be2775
feat: remove base64ToBytes from crypto ts and move encoding file one …
Akaryatrh Aug 6, 2026
6b7da84
fix: yarn constraints
Akaryatrh Aug 7, 2026
801e098
fix: bump dependency for constraint
Akaryatrh Aug 7, 2026
ece1fef
fix: missing entry in readme
Akaryatrh Aug 7, 2026
81246f6
Adjusting the storage token to the spec
BjornGunnarsson Aug 12, 2026
e57e5c3
Creating a test token script
BjornGunnarsson Aug 12, 2026
ed74f87
feat(kyc-controller): add MoonPay Iron self-hosted wallet registration
saustrie-consensys Aug 12, 2026
bafa88c
chore(kyc-controller): changelog for self-hosted wallet registration
saustrie-consensys Aug 12, 2026
6cf7da2
style(kyc-controller): apply prettier formatting
saustrie-consensys Aug 12, 2026
823788c
refactor(kyc-controller): narrow wallet registration to Monad POC
saustrie-consensys Aug 12, 2026
47ff54c
Merge KYC controller implementation for wallet registration
saustrie-consensys Aug 12, 2026
8d1ed23
feat(kyc-controller): register Money Account wallets
saustrie-consensys Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
## Product Safety Team
/packages/phishing-controller @MetaMask/product-safety

## Universal KYC Team
/packages/kyc-controller @MetaMask/universal-kyc

## Swaps-Bridge Team
/packages/bridge-controller @MetaMask/swaps-engineers
/packages/bridge-status-controller @MetaMask/swaps-engineers
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,12 @@ linkStyle default opacity:0.5
keyring_controller --> base_controller;
keyring_controller --> controller_utils;
keyring_controller --> messenger;
kyc_controller --> base_controller;
kyc_controller --> base_data_service;
kyc_controller --> controller_utils;
kyc_controller --> geolocation_controller;
kyc_controller --> messenger;
kyc_controller --> profile_sync_controller;
logging_controller --> base_controller;
logging_controller --> messenger;
message_manager --> base_controller;
Expand Down
4 changes: 4 additions & 0 deletions codeowners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,10 @@ function buildTeamSections(): CodeownersSection[] {
title: 'Product Safety Team',
rules: [buildRuleForPackage('phishing-controller')],
},
{
title: 'Universal KYC Team',
rules: [buildRuleForPackage('kyc-controller')],
},
{
title: 'Swaps-Bridge Team',
rules: [
Expand Down
9 changes: 9 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,15 @@ const config = createConfig([
'no-restricted-globals': 'off',
},
},
{
// The UKYC test-token minter is a dev-only Node CLI, so it may use Node
// builtins and globals unlike the platform-agnostic package source.
files: ['packages/kyc-controller/scripts/**/*.ts'],
rules: {
'import-x/no-nodejs-modules': 'off',
'no-restricted-globals': 'off',
},
},
{
files: [
'packages/wallet-cli/src/**/*.test.{js,ts}',
Expand Down
663 changes: 663 additions & 0 deletions packages/kyc-controller/ARCHITECTURE.md

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions packages/kyc-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Initial release of the `@metamask/kyc-controller` package for managing KYC / identity verification state across MetaMask clients ([#9781](https://github.com/MetaMask/core/pull/9781))
- `KycController:registerMoneyAccountWallet`, an address-only action that resolves the MoonPay customer, signs a Monad Money Account ownership message, and registers the wallet through the MetaMask proxy ([#9847](https://github.com/MetaMask/core/pull/9847))
- Internal wallet registration service and state machine support for `409` disambiguation, transient-failure reconciliation, UTC date rollover, and typed failures ([#9847](https://github.com/MetaMask/core/pull/9847))
- Add `KycController` and `KycService` for managing KYC / identity verification state across MetaMask clients ([#9615](https://github.com/MetaMask/core/pull/9615))
- `KycController` (`BaseController`) owns the flow state machine, the Check/Auth frame message protocol, X25519 credential decryption, and SumSub orchestration via an injected `KycSumSubLauncher` adapter.
- `KycService` extends `BaseDataService` and performs the Universal KYC (UKYC) HTTP calls via an injected `fetch`, sourcing the auth bearer token and geolocation through the messenger.
- Exposes a vendor-neutral, per-product surface (`ramps`, `card`) plus reselect selectors.
- Add automatic post-authentication continuation to `KycController`
- Add optional `baseUrl` option to `KycService` constructor that overrides the base URL derived from `env`, enabling clients to target a custom (e.g. local or staging) KYC API
- Add UKYC session-status polling to `KycController`
- Add handling in `KycController.startSumSub` for applicants already being processed by the vendor

[Unreleased]: https://github.com/MetaMask/core/
12 changes: 10 additions & 2 deletions packages/kyc-controller/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# `@metamask/kyc-controller`
# KYC Controller `@metamask/kyc-controller`

Shared KYC / identity verification controller used across MetaMask clients

Expand All @@ -10,6 +10,14 @@ or

`npm install @metamask/kyc-controller`

## Development

To rebuild the package automatically whenever you change a source file, run the `build:watch` script from core repository root folder:

`yarn workspace @metamask/kyc-controller run build:watch`

This watches `src/**/*.ts` and re-runs the build on each change (it also performs an initial build on start), which is useful when developing against a client that consumes this package locally.

## Contributing

This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme).
This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme).
26 changes: 24 additions & 2 deletions packages/kyc-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,46 @@
"build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references",
"build:all": "ts-bridge --project tsconfig.build.json --verbose --clean",
"build:docs": "typedoc",
"build:watch": "yarn build && chokidar 'src/**/*.ts' -c 'ts-bridge --project tsconfig.build.json --verbose --no-references' --initial",
"changelog:update": "../../scripts/update-changelog.sh @metamask/kyc-controller",
"changelog:validate": "../../scripts/validate-changelog.sh @metamask/kyc-controller",
"lint:tsconfigs": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts",
"lint:tsconfigs:fix": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts --fix",
"messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --check",
"messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --generate",
"messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --esm --check",
"messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --esm --generate",
"mint:ukyc-token": "tsx scripts/mint-ukyc-test-token.ts",
"since-latest-release": "../../scripts/since-latest-release.sh",
"test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter",
"test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache",
"test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose",
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch"
},
"dependencies": {
"@metamask/base-controller": "^9.1.0",
"@metamask/base-data-service": "^0.1.3",
"@metamask/controller-utils": "^12.3.0",
"@metamask/geolocation-controller": "^1.0.0",
"@metamask/keyring-controller": "^27.1.1",
"@metamask/messenger": "^2.0.0",
"@metamask/profile-sync-controller": "^29.0.0",
"@metamask/superstruct": "^3.4.1",
"@metamask/utils": "^11.11.0",
"@noble/ciphers": "^1.3.0",
"@noble/curves": "^1.9.2",
"@noble/hashes": "^1.8.0",
"@scure/base": "^1.2.6",
"@tanstack/query-core": "^4.43.0",
"reselect": "^5.1.1",
"tweetnacl": "^1.0.3"
},
"devDependencies": {
"@metamask/auto-changelog": "^6.1.0",
"@ts-bridge/cli": "^0.6.4",
"@types/jest": "^30.0.0",
"chokidar-cli": "^3.0.0",
"deepmerge": "^4.2.2",
"jest": "^30.4.2",
"nock": "^13.3.1",
"ts-jest": "^29.4.11",
"tsx": "^4.20.5",
"typedoc": "^0.25.13",
Expand Down
106 changes: 106 additions & 0 deletions packages/kyc-controller/scripts/mint-ukyc-test-token.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* CLI to mint a UKYC `storage_access_token` for testing UKYC Storage.
*
* All real logic lives in the tested `mintUkycTestToken`; this is a thin
* argument-parsing wrapper that prints the result as JSON.
*
* Usage (from the package root, via the `mint:ukyc-token` script):
* yarn workspace @metamask/kyc-controller run mint:ukyc-token -- \
* --operations read,write --expires-in 4h [--secret <hex>] \
* [--presenter client|idos-relay] [--session-id <id>]
*
* Reuse the printed `localUserSecret` (pass it back via --secret) to keep the
* same `storageId` and controlling key across runs.
*/
import process from 'node:process';

import type {
UkycStorageOperation,
UkycTokenPresenter,
} from '../src/ukyc/storageAccessToken.js';
import { mintUkycTestToken } from '../src/ukyc/testToken.js';
import type { MintUkycTestTokenParams } from '../src/ukyc/testToken.js';

/**
* Parses `--flag value` and `--flag=value` pairs into a map. Flags without a
* following value are treated as booleans (`"true"`).
*
* @param argv - Raw CLI arguments (typically `process.argv.slice(2)`).
* @returns The parsed flags keyed by name (without the leading `--`).
*/
function parseFlags(argv: string[]): Record<string, string> {
const flags: Record<string, string> = {};
let i = 0;
while (i < argv.length) {
const arg = argv[i];
if (!arg.startsWith('--')) {
i += 1;
continue;
}
const body = arg.slice(2);
const eq = body.indexOf('=');
if (eq !== -1) {
flags[body.slice(0, eq)] = body.slice(eq + 1);
i += 1;
continue;
}
const next = argv[i + 1];
if (next !== undefined && !next.startsWith('--')) {
flags[body] = next;
i += 2;
} else {
flags[body] = 'true';
i += 1;
}
}
return flags;
}

/**
* Parses a duration like `4h`, `30m`, `90s`, or a bare number of seconds.
*
* @param value - The duration string.
* @returns The duration in milliseconds.
*/
function parseDurationMs(value: string): number {
const match = /^(\d+)(s|m|h|d)?$/u.exec(value);
if (!match) {
throw new Error(`invalid --expires-in duration: ${value}`);
}
const amount = Number(match[1]);
const unitMs = { s: 1000, m: 60_000, h: 3_600_000, d: 86_400_000 };
return amount * unitMs[(match[2] ?? 's') as keyof typeof unitMs];
}

const flags = parseFlags(process.argv.slice(2));

const params: MintUkycTestTokenParams = {};

if (flags.secret) {
params.localUserSecret = flags.secret;
}
if (flags.operations) {
params.operations = flags.operations
.split(',')
.map((op) => op.trim()) as UkycStorageOperation[];
}
if (flags.presenter) {
params.presenter = flags.presenter as UkycTokenPresenter;
}
if (flags['session-id']) {
params.sessionId = flags['session-id'];
}
if (flags['issued-at']) {
params.issuedAt = new Date(flags['issued-at']);
}
if (flags['expires-at']) {
params.expiresAt = new Date(flags['expires-at']);
} else if (flags['expires-in']) {
const issuedAt = params.issuedAt ?? new Date();
params.issuedAt = issuedAt;
params.expiresAt = new Date(issuedAt.getTime() + parseDurationMs(flags['expires-in']));
}

const result = mintUkycTestToken(params);

console.log(JSON.stringify(result, null, 2));
Loading
Loading