Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 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
176da6a
feat(ramps): add autoramp accounts, NeoBankService, and User Storage …
georgeweiler Aug 12, 2026
6964382
feat(transaction-pay-controller): add Money Account vault deposit and…
saustrie-consensys Aug 12, 2026
4a9cb43
fix(transaction-pay-controller): harden Money Account vault deposit/w…
saustrie-consensys Aug 12, 2026
8e7e0ac
feat(kyc-controller): register Money Account wallets
saustrie-consensys Aug 12, 2026
e3c13f9
feat(ramps): add NeoBankService Pix and quote client methods
saustrie-consensys Aug 12, 2026
ecb94e8
feat(kyc-controller): sync wallet registration with neobank-proxy
saustrie-consensys Aug 12, 2026
bd674c3
fix(transaction-pay-controller): do not cache skipped vault deposits
saustrie-consensys Aug 12, 2026
a40fa2f
feat(kyc-controller): add Iron Money KYC path and status polling
amitabh94 Aug 12, 2026
10b3588
Merge branch 'cursor/ramps-autoramp-accounts' into neobank-demo
georgeweiler Aug 12, 2026
189aa9e
Merge branch 'feat/iron-kyc-controller-m2' into neobank-demo
georgeweiler Aug 12, 2026
04d40bc
Merge branch 'saustrie/neobank-pix-service-methods' into neobank-demo
georgeweiler Aug 12, 2026
508cd90
Merge branch 'saustrie/vba-money-account-vault-functions' into neoban…
georgeweiler Aug 12, 2026
13585d8
feat(ramps): resolve autoramp customer id from KYC controller
georgeweiler Aug 12, 2026
7e209f7
fix(kyc-controller): sync Iron CI lint fixes onto neobank-demo (#9855)
amitabh94 Aug 12, 2026
594bb21
fix(ramps): narrow required service action list to its literal members
georgeweiler Aug 12, 2026
ae31c6e
Merge branch 'feat/moonpay-wallet-registration-stacked' into neobank-…
georgeweiler Aug 12, 2026
5311fe8
feat(kyc-controller): register Money Account wallets
saustrie-consensys Aug 12, 2026
a1b133d
feat(kyc-controller): sync wallet registration with neobank-proxy
saustrie-consensys Aug 12, 2026
fee45dd
feat(ramps): fall back to Profile Sync for autoramp customer id
georgeweiler Aug 13, 2026
b0d70fa
Merge remote-tracking branch 'origin/neobank-demo' into neobank-demo
georgeweiler Aug 13, 2026
0a3674b
refactor(ramps): move Money Account wallet signing from kyc-controlle…
amitabh94 Aug 13, 2026
8667a18
refactor(ramps): stop resolving autoramp customer id via KycControlle…
amitabh94 Aug 13, 2026
c6b9443
fix: unblock neobank-demo lint for #9853 (#9860)
amitabh94 Aug 13, 2026
c34a16a
fix: unblock neobank-demo CI and resolve Cursor findings for #9853 (#…
amitabh94 Aug 13, 2026
8bc742f
fix(ramps): stop retrying permanent neobank 4xx failures
georgeweiler Aug 13, 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
7 changes: 7 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 Expand Up @@ -643,6 +649,7 @@ linkStyle default opacity:0.5
transaction_pay_controller --> gas_fee_controller;
transaction_pay_controller --> keyring_controller;
transaction_pay_controller --> messenger;
transaction_pay_controller --> money_account_utils;
transaction_pay_controller --> network_controller;
transaction_pay_controller --> ramps_controller;
transaction_pay_controller --> remote_feature_flag_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.

22 changes: 21 additions & 1 deletion packages/kyc-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,26 @@ 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))
- Add `KycController.getCustomerIdentity()` method and the `KycController:getCustomerIdentity` messenger action (plus the exported `KycControllerGetCustomerIdentityAction` and `KycCustomerIdentity` types). Returns the vendor-scoped `{ vendor, id }` for the currently authenticated customer, or `null` before authentication and after `reset()`. Lets consumers (e.g. ramps autoramp creation) attach the vendor customer id to downstream calls without reading the full KYC state, which also holds session/access tokens. The id is session-scoped and never persisted. ([#9853](https://github.com/MetaMask/core/pull/9853))
- Add Iron (Money/VBA) KYC path to `@metamask/kyc-controller`: `vendor: 'iron'` skips MoonPay Check/Auth frames; `KycService` clients for `/vendors/iron/*`, `POST /consents`, and `GET /kyc/status`; `refreshKycStatus` + `statusChanged` for Money toast state ([#9852](https://github.com/MetaMask/core/pull/9852), [#9853](https://github.com/MetaMask/core/pull/9853))
- 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), [#9853](https://github.com/MetaMask/core/pull/9853))
- Add `KycController` and `KycService` for managing KYC / identity verification state across MetaMask clients ([#9615](https://github.com/MetaMask/core/pull/9615), [#9853](https://github.com/MetaMask/core/pull/9853))
- `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

### Removed

- Move Money Account wallet registration to `@metamask/ramps-controller`: removes `KycController.registerMoneyAccountWallet`, the `KycService` wallet-registration methods (`getMoonpayCustomerId`, `getWalletRegistrationStatus`, `registerSelfHostedWallet`), the `neobankBaseUrl` service option, and the wallet registration exports (`WalletRegistrationError`, `SelfHostedRegistration`, `MoneyAccountWalletRegistrationResult`, and related types). Wallet ownership signing is a Money Movement (neobank-proxy) concern, so it now lives on `RampsController` / `NeoBankService`. ([#9853](https://github.com/MetaMask/core/pull/9853))

### Fixed

- Clear `moonpayCustomerId` when the active vendor changes, so `getCustomerIdentity()` can no longer report a MoonPay customer id under another vendor. The id is dropped when `initialize` starts a non-MoonPay flow and when `createIronCustomer` switches to Iron. ([#9861](https://github.com/MetaMask/core/pull/9861), [#9853](https://github.com/MetaMask/core/pull/9853))
- Call `unref()` on the user-status poll timer only when it exists. React Native and browser timers are numbers, so the unconditional call threw when Money status polling started outside Node. ([#9861](https://github.com/MetaMask/core/pull/9861), [#9853](https://github.com/MetaMask/core/pull/9853))
- Skip the `session_not_in_valid_state` completion write when a `reset()` superseded the SumSub flow, so a late vendor response can no longer force `userStatus` to `completed` (and publish `statusChanged`) on an idle controller. ([#9861](https://github.com/MetaMask/core/pull/9861), [#9853](https://github.com/MetaMask/core/pull/9853))

[Unreleased]: https://github.com/MetaMask/core/
10 changes: 9 additions & 1 deletion 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).
25 changes: 23 additions & 2 deletions packages/kyc-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,45 @@
"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/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
108 changes: 108 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,108 @@
/**
* 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