From bae5fdd5dc7db717d7bf765cdc3d1756be40fd3a Mon Sep 17 00:00:00 2001 From: Jiexi Luan-Huang Date: Wed, 9 Sep 2026 14:05:14 -0700 Subject: [PATCH 1/7] Rename fetchDisclaimersCatalog to fetchSessionDisclaimersByCountry and fetchSessionDisclaimers to fetchSessionDisclaimersBySessionId --- packages/kyc-controller/ARCHITECTURE.md | 6 ++-- packages/kyc-controller/src/KycController.ts | 4 +-- .../src/KycService-method-action-types.ts | 24 +++++++-------- .../kyc-controller/src/KycService.test.ts | 24 +++++++-------- packages/kyc-controller/src/KycService.ts | 30 +++++++++---------- packages/kyc-controller/src/index.ts | 9 +++--- 6 files changed, 48 insertions(+), 49 deletions(-) diff --git a/packages/kyc-controller/ARCHITECTURE.md b/packages/kyc-controller/ARCHITECTURE.md index d3601027467..4eeeca0460c 100644 --- a/packages/kyc-controller/ARCHITECTURE.md +++ b/packages/kyc-controller/ARCHITECTURE.md @@ -122,7 +122,7 @@ Exposed messenger actions (`MESSENGER_EXPOSED_METHODS`): Exposed messenger actions (`MESSENGER_EXPOSED_METHODS`): `getGeoCountry`, `fetchVendorDisclaimers`, `createSession`, `checkKycRequired`, -`createVendorCustomer`, `submitVendorDisclaimers`, `fetchDisclaimersCatalog`, `fetchSessionDisclaimers`, `submitSessionDisclaimers`, +`createVendorCustomer`, `submitVendorDisclaimers`, `fetchSessionDisclaimersByCountry`, `fetchSessionDisclaimersBySessionId`, `submitSessionDisclaimers`, `fetchKycStatus`, `fetchIdosEnclaveJwks`, `fetchIdosRelayJwks`, `createUkycSession`, `setAuthorizations`, `createJourney`, `getSessionStatus`. @@ -136,8 +136,8 @@ Endpoints: | `checkKycRequired` | `POST` | `/vendors/{vendor}/kyc-required` | Is KYC required? (normalizes `required` → `kycRequired`) | | `createVendorCustomer` | `POST` | `/vendors/{vendor}/customers` | Create or resume an empty-shell vendor customer | | `submitVendorDisclaimers` | `POST` | `/vendors/{vendor}/disclaimers` | Record vendor T&C signings (`disclaimerIds`) | -| `fetchDisclaimersCatalog` | `GET` | `/disclaimers?country=` | Global idOS + KYC-provider catalog (no consent state) | -| `fetchSessionDisclaimers` | `GET` | `/sessions/{id}/disclaimers` | Session-scoped catalog, with `consented` flags + credential-reuse flag | +| `fetchSessionDisclaimersByCountry` | `GET` | `/disclaimers?country=` | Global idOS + KYC-provider catalog (no consent state) | +| `fetchSessionDisclaimersBySessionId` | `GET` | `/sessions/{id}/disclaimers` | Session-scoped catalog, with `consented` flags + credential-reuse flag | | `submitSessionDisclaimers` | `POST` | `/sessions/{id}/disclaimers` | Record `{ idOS, kycProvider, credentialReusabilityConsentGiven }` consents | | `fetchKycStatus` | `GET` | `/kyc/status` | User-keyed simplified KYC status | | `fetchIdosEnclaveJwks` | `GET` | `{idosEnclaveBaseUrl}/.well-known/jwks.json` | idOS enclave JWKS for `encryptionDataKey` attestation | diff --git a/packages/kyc-controller/src/KycController.ts b/packages/kyc-controller/src/KycController.ts index 5f9ed9897f4..4c18684516a 100644 --- a/packages/kyc-controller/src/KycController.ts +++ b/packages/kyc-controller/src/KycController.ts @@ -1369,7 +1369,7 @@ export class KycController extends BaseController< generation: number, ): Promise { const catalog = await this.messenger.call( - 'KycService:fetchSessionDisclaimers', + 'KycService:fetchSessionDisclaimersBySessionId', { sessionId }, ); if (this.#generation !== generation) { @@ -1429,7 +1429,7 @@ export class KycController extends BaseController< // continue only when every document the user accepted is now consented; // otherwise fail closed so a version bump cannot skip new docs. const latest = await this.messenger.call( - 'KycService:fetchSessionDisclaimers', + 'KycService:fetchSessionDisclaimersBySessionId', { sessionId }, ); if (this.#generation !== generation) { diff --git a/packages/kyc-controller/src/KycService-method-action-types.ts b/packages/kyc-controller/src/KycService-method-action-types.ts index 791e1b4b107..975254525ec 100644 --- a/packages/kyc-controller/src/KycService-method-action-types.ts +++ b/packages/kyc-controller/src/KycService-method-action-types.ts @@ -91,38 +91,38 @@ export type KycServiceSubmitVendorDisclaimersAction = { * Fetches the global idOS + KYC-provider disclaimer catalog * (`GET /disclaimers?country=`). Carries no consent state — per-document * `consented` flags and `credentialReusabilityConsentGiven` are - * session-scoped via {@link fetchSessionDisclaimers}. Vendor T&Cs continue to + * session-scoped via {@link fetchSessionDisclaimersBySessionId}. Vendor T&Cs continue to * come from {@link fetchVendorDisclaimers}. * * @param params - The parameters. * @param params.country - ISO 3166-1 alpha-3 country code. * @returns The catalog documents. */ -export type KycServiceFetchDisclaimersCatalogAction = { - type: `KycService:fetchDisclaimersCatalog`; - handler: KycService['fetchDisclaimersCatalog']; +export type KycServiceFetchSessionDisclaimersByCountryAction = { + type: `KycService:fetchSessionDisclaimersByCountry`; + handler: KycService['fetchSessionDisclaimersByCountry']; }; /** * Fetches the session-scoped idOS + KYC-provider disclaimer catalog * (`GET /sessions/{sessionId}/disclaimers`), including per-session * `consented` flags and `credentialReusabilityConsentGiven`. For the - * pre-session global catalog use {@link fetchDisclaimersCatalog}. Vendor - * T&Cs continue to come from {@link fetchVendorDisclaimers}. + * pre-session global catalog use {@link fetchSessionDisclaimersByCountry}. + * Vendor T&Cs continue to come from {@link fetchVendorDisclaimers}. * * @param params - The parameters. * @param params.sessionId - The UKYC session id. * @returns The catalog, including which documents are already consented. */ -export type KycServiceFetchSessionDisclaimersAction = { - type: `KycService:fetchSessionDisclaimers`; - handler: KycService['fetchSessionDisclaimers']; +export type KycServiceFetchSessionDisclaimersBySessionIdAction = { + type: `KycService:fetchSessionDisclaimersBySessionId`; + handler: KycService['fetchSessionDisclaimersBySessionId']; }; /** * Records idOS + KYC-provider consents for a UKYC session * (`POST /sessions/{sessionId}/disclaimers`). `key`/`version` pairs must - * match the current catalog from {@link fetchSessionDisclaimers}. A 409 + * match the current catalog from {@link fetchSessionDisclaimersBySessionId}. A 409 * means those document versions were already recorded for the session. * * @param params - The consent parameters. @@ -241,8 +241,8 @@ export type KycServiceMethodActions = | KycServiceCheckKycRequiredAction | KycServiceCreateVendorCustomerAction | KycServiceSubmitVendorDisclaimersAction - | KycServiceFetchDisclaimersCatalogAction - | KycServiceFetchSessionDisclaimersAction + | KycServiceFetchSessionDisclaimersByCountryAction + | KycServiceFetchSessionDisclaimersBySessionIdAction | KycServiceSubmitSessionDisclaimersAction | KycServiceFetchKycStatusAction | KycServiceFetchIdosEnclaveJwksAction diff --git a/packages/kyc-controller/src/KycService.test.ts b/packages/kyc-controller/src/KycService.test.ts index b6882ffdd48..bf8ad25f8ea 100644 --- a/packages/kyc-controller/src/KycService.test.ts +++ b/packages/kyc-controller/src/KycService.test.ts @@ -760,7 +760,7 @@ describe('KycService', () => { }); }); - describe('fetchDisclaimersCatalog', () => { + describe('fetchSessionDisclaimersByCountry', () => { const documents = { idOS: [ { @@ -788,7 +788,7 @@ describe('KycService', () => { const { service } = getService(); expect( - await service.fetchDisclaimersCatalog({ country: 'USA' }), + await service.fetchSessionDisclaimersByCountry({ country: 'USA' }), ).toStrictEqual(documents); }); @@ -796,7 +796,7 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchDisclaimersCatalog({ country: 'US' }), + service.fetchSessionDisclaimersByCountry({ country: 'US' }), ).rejects.toThrow(/ISO 3166-1 alpha-3/u); }); @@ -808,7 +808,7 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchDisclaimersCatalog({ country: 'USA' }), + service.fetchSessionDisclaimersByCountry({ country: 'USA' }), ).rejects.toThrow(/Malformed response received from disclaimers API/u); }); @@ -820,7 +820,7 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchDisclaimersCatalog({ country: 'USA' }), + service.fetchSessionDisclaimersByCountry({ country: 'USA' }), ).rejects.toThrow(/Malformed response received from disclaimers API/u); }); @@ -832,12 +832,12 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchDisclaimersCatalog({ country: 'USA' }), + service.fetchSessionDisclaimersByCountry({ country: 'USA' }), ).rejects.toThrow(/failed with status '500'/u); }); }); - describe('fetchSessionDisclaimers', () => { + describe('fetchSessionDisclaimersBySessionId', () => { const documents = { idOS: [ { @@ -868,7 +868,7 @@ describe('KycService', () => { const { service } = getService(); expect( - await service.fetchSessionDisclaimers({ sessionId: 'sid-1' }), + await service.fetchSessionDisclaimersBySessionId({ sessionId: 'sid-1' }), ).toStrictEqual(catalog); }); @@ -877,7 +877,7 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchSessionDisclaimers({ sessionId: 'sid-1' }), + service.fetchSessionDisclaimersBySessionId({ sessionId: 'sid-1' }), ).rejects.toThrow( /Malformed response received from session disclaimers API/u, ); @@ -890,7 +890,7 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchSessionDisclaimers({ sessionId: 'sid-1' }), + service.fetchSessionDisclaimersBySessionId({ sessionId: 'sid-1' }), ).rejects.toThrow( /Malformed response received from session disclaimers API/u, ); @@ -914,7 +914,7 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchSessionDisclaimers({ sessionId: 'sid-1' }), + service.fetchSessionDisclaimersBySessionId({ sessionId: 'sid-1' }), ).rejects.toThrow( /Malformed response received from session disclaimers API/u, ); @@ -925,7 +925,7 @@ describe('KycService', () => { const { service } = getService(); await expect( - service.fetchSessionDisclaimers({ sessionId: 'sid-1' }), + service.fetchSessionDisclaimersBySessionId({ sessionId: 'sid-1' }), ).rejects.toThrow(/failed with status '500'/u); }); }); diff --git a/packages/kyc-controller/src/KycService.ts b/packages/kyc-controller/src/KycService.ts index bdb4f8717e8..ea2afb80ccc 100644 --- a/packages/kyc-controller/src/KycService.ts +++ b/packages/kyc-controller/src/KycService.ts @@ -54,8 +54,8 @@ const MESSENGER_EXPOSED_METHODS = [ 'checkKycRequired', 'createVendorCustomer', 'submitVendorDisclaimers', - 'fetchDisclaimersCatalog', - 'fetchSessionDisclaimers', + 'fetchSessionDisclaimersByCountry', + 'fetchSessionDisclaimersBySessionId', 'submitSessionDisclaimers', 'fetchKycStatus', 'fetchIdosEnclaveJwks', @@ -323,12 +323,12 @@ export type SubmitVendorDisclaimersParams = { disclaimerIds: string[]; }; -export type FetchDisclaimersCatalogParams = { +export type FetchSessionDisclaimersByCountryParams = { /** ISO 3166-1 alpha-3 country code for `GET /disclaimers?country=`. */ country: string; }; -export type FetchSessionDisclaimersParams = { +export type FetchSessionDisclaimersBySessionIdParams = { /** UKYC session id from {@link KycService.createUkycSession}. */ sessionId: string; }; @@ -679,26 +679,26 @@ export class KycService extends BaseDataService< * Fetches the global idOS + KYC-provider disclaimer catalog * (`GET /disclaimers?country=`). Carries no consent state — per-document * `consented` flags and `credentialReusabilityConsentGiven` are - * session-scoped via {@link fetchSessionDisclaimers}. Vendor T&Cs continue to + * session-scoped via {@link fetchSessionDisclaimersBySessionId}. Vendor T&Cs continue to * come from {@link fetchVendorDisclaimers}. * * @param params - The parameters. * @param params.country - ISO 3166-1 alpha-3 country code. * @returns The catalog documents. */ - async fetchDisclaimersCatalog({ + async fetchSessionDisclaimersByCountry({ country, - }: FetchDisclaimersCatalogParams): Promise { + }: FetchSessionDisclaimersByCountryParams): Promise { if (country.length !== 3) { throw new Error( - `KycService.fetchDisclaimersCatalog: country must be an ISO 3166-1 alpha-3 code (received "${country}").`, + `KycService.fetchSessionDisclaimersByCountry: country must be an ISO 3166-1 alpha-3 code (received "${country}").`, ); } const url = new URL('/disclaimers', this.#baseUrl); url.searchParams.set('country', country); const data = await this.fetchQuery({ - queryKey: [`${this.name}:fetchDisclaimersCatalog`, country], + queryKey: [`${this.name}:fetchSessionDisclaimersByCountry`, country], queryFn: async () => this.#requestJson(url, { method: 'GET' }), staleTime: 0, gcTime: 0, @@ -714,22 +714,22 @@ export class KycService extends BaseDataService< * Fetches the session-scoped idOS + KYC-provider disclaimer catalog * (`GET /sessions/{sessionId}/disclaimers`), including per-session * `consented` flags and `credentialReusabilityConsentGiven`. For the - * pre-session global catalog use {@link fetchDisclaimersCatalog}. Vendor - * T&Cs continue to come from {@link fetchVendorDisclaimers}. + * pre-session global catalog use {@link fetchSessionDisclaimersByCountry}. + * Vendor T&Cs continue to come from {@link fetchVendorDisclaimers}. * * @param params - The parameters. * @param params.sessionId - The UKYC session id. * @returns The catalog, including which documents are already consented. */ - async fetchSessionDisclaimers({ + async fetchSessionDisclaimersBySessionId({ sessionId, - }: FetchSessionDisclaimersParams): Promise { + }: FetchSessionDisclaimersBySessionIdParams): Promise { const url = new URL( `/sessions/${encodeURIComponent(sessionId)}/disclaimers`, this.#baseUrl, ); const data = await this.fetchQuery({ - queryKey: [`${this.name}:fetchSessionDisclaimers`, sessionId], + queryKey: [`${this.name}:fetchSessionDisclaimersBySessionId`, sessionId], queryFn: async () => this.#requestJson(url, { method: 'GET' }), // Consent state can change after a POST, so always re-fetch. staleTime: 0, @@ -745,7 +745,7 @@ export class KycService extends BaseDataService< /** * Records idOS + KYC-provider consents for a UKYC session * (`POST /sessions/{sessionId}/disclaimers`). `key`/`version` pairs must - * match the current catalog from {@link fetchSessionDisclaimers}. A 409 + * match the current catalog from {@link fetchSessionDisclaimersBySessionId}. A 409 * means those document versions were already recorded for the session. * * @param params - The consent parameters. diff --git a/packages/kyc-controller/src/index.ts b/packages/kyc-controller/src/index.ts index f0bc44276d7..34ec730acb7 100644 --- a/packages/kyc-controller/src/index.ts +++ b/packages/kyc-controller/src/index.ts @@ -44,8 +44,8 @@ export type { CreateSessionParams, CreateUkycSessionParams, EncryptionSchema, - FetchDisclaimersCatalogParams, - FetchSessionDisclaimersParams, + FetchSessionDisclaimersByCountryParams, + FetchSessionDisclaimersBySessionIdParams, GetSessionStatusParams, VendorCustomerResponse, JwksResponse, @@ -67,12 +67,11 @@ export type { KycServiceCreateJourneyAction, KycServiceCreateSessionAction, KycServiceCreateUkycSessionAction, - KycServiceFetchVendorDisclaimersAction, - KycServiceFetchDisclaimersCatalogAction, KycServiceFetchIdosEnclaveJwksAction, KycServiceFetchIdosRelayJwksAction, KycServiceFetchKycStatusAction, - KycServiceFetchSessionDisclaimersAction, + KycServiceFetchSessionDisclaimersByCountryAction, + KycServiceFetchSessionDisclaimersBySessionIdAction, KycServiceGetGeoCountryAction, KycServiceGetSessionStatusAction, KycServiceSetAuthorizationsAction, From 88c58fc46efaf1864779d60339fb8c59960d434b Mon Sep 17 00:00:00 2001 From: Jiexi Luan-Huang Date: Wed, 9 Sep 2026 14:10:55 -0700 Subject: [PATCH 2/7] add fetchSessionDisclaimers to KycController --- packages/kyc-controller/ARCHITECTURE.md | 2 +- packages/kyc-controller/CHANGELOG.md | 13 ++ .../src/KycController-method-action-types.ts | 24 +++ .../kyc-controller/src/KycController.test.ts | 192 +++++++++++++++--- packages/kyc-controller/src/KycController.ts | 72 +++++++ packages/kyc-controller/src/index.ts | 3 + 6 files changed, 280 insertions(+), 26 deletions(-) diff --git a/packages/kyc-controller/ARCHITECTURE.md b/packages/kyc-controller/ARCHITECTURE.md index 4eeeca0460c..be6532b14ca 100644 --- a/packages/kyc-controller/ARCHITECTURE.md +++ b/packages/kyc-controller/ARCHITECTURE.md @@ -99,7 +99,7 @@ graph TB Exposed messenger actions (`MESSENGER_EXPOSED_METHODS`): -`initialize`, `loadDisclaimers`, `acceptTermsAndStartSession`, +`initialize`, `loadDisclaimers`, `fetchSessionDisclaimers`, `acceptTermsAndStartSession`, `createVendorCustomer`, `clearSavedTerms`, `handleFrameMessage`, `buildCheckFrameUrl`, `buildAuthFrameUrl`, `buildResetFrameUrl`, `checkKycRequired`, `getKycStatus`, `getCustomerIdentity`, `refreshKycStatus`, diff --git a/packages/kyc-controller/CHANGELOG.md b/packages/kyc-controller/CHANGELOG.md index 928123db219..352aa60da90 100644 --- a/packages/kyc-controller/CHANGELOG.md +++ b/packages/kyc-controller/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `KycController.fetchSessionDisclaimers`, which fetches the idOS + KYC-provider catalog by `{ sessionId }` or `{ country }` via `KycService.fetchSessionDisclaimersBySessionId` or `KycService.fetchSessionDisclaimersByCountry`. + +### Changed + +- **BREAKING:** Rename `KycService.fetchDisclaimersCatalog` to `fetchSessionDisclaimersByCountry`. + - Rename `FetchDisclaimersCatalogParams` to `FetchSessionDisclaimersByCountryParams`. + - Rename the messenger action `KycService:fetchDisclaimersCatalog` to `KycService:fetchSessionDisclaimersByCountry`. +- **BREAKING:** Rename `KycService.fetchSessionDisclaimers` to `fetchSessionDisclaimersBySessionId`. + - Rename `FetchSessionDisclaimersParams` to `FetchSessionDisclaimersBySessionIdParams`. + - Rename the messenger action `KycService:fetchSessionDisclaimers` to `KycService:fetchSessionDisclaimersBySessionId`. + ## [0.2.0] ### Changed diff --git a/packages/kyc-controller/src/KycController-method-action-types.ts b/packages/kyc-controller/src/KycController-method-action-types.ts index cbb56684f85..c602a10bb89 100644 --- a/packages/kyc-controller/src/KycController-method-action-types.ts +++ b/packages/kyc-controller/src/KycController-method-action-types.ts @@ -53,6 +53,29 @@ export type KycControllerLoadDisclaimersAction = { handler: KycController['loadDisclaimers']; }; +/** + * Fetches the idOS + KYC-provider disclaimer catalog. Pass exactly one of + * `sessionId` or `country`: + * + * - `{ sessionId }` → {@link KycService.fetchSessionDisclaimersBySessionId} + * (`GET /sessions/{sessionId}/disclaimers`) + * - `{ country }` → {@link KycService.fetchSessionDisclaimersByCountry} + * (`GET /disclaimers?country=`) + * + * A session-id fetch also writes the catalog to `sessionDisclaimers`. + * + * @param params - The parameters. Provide exactly one of `sessionId` or + * `country`. + * @param params.sessionId - The UKYC session id. + * @param params.country - ISO 3166-1 alpha-3 country code. + * @returns The catalog. Session fetches include consent state; country + * fetches do not. + */ +export type KycControllerFetchSessionDisclaimersAction = { + type: `KycController:fetchSessionDisclaimers`; + handler: KycController['fetchSessionDisclaimers']; +}; + /** * Captures terms acceptance for the currently loaded disclaimers and creates * a session. @@ -264,6 +287,7 @@ export type KycControllerMethodActions = | KycControllerInitializeAction | KycControllerCreateVendorCustomerAction | KycControllerLoadDisclaimersAction + | KycControllerFetchSessionDisclaimersAction | KycControllerAcceptTermsAndStartSessionAction | KycControllerClearSavedTermsAction | KycControllerHandleFrameMessageAction diff --git a/packages/kyc-controller/src/KycController.test.ts b/packages/kyc-controller/src/KycController.test.ts index e487d652527..e2ff67fb691 100644 --- a/packages/kyc-controller/src/KycController.test.ts +++ b/packages/kyc-controller/src/KycController.test.ts @@ -13,7 +13,10 @@ import { getDefaultKycControllerState, KycController, } from './KycController.js'; -import type { KycControllerMessenger } from './KycController.js'; +import type { + FetchSessionDisclaimersParams, + KycControllerMessenger, +} from './KycController.js'; import type { KycConsentRecord, KycDisclaimer, @@ -498,6 +501,121 @@ describe('KycController', () => { }); }); + describe('fetchSessionDisclaimers', () => { + const globalCatalog = { + idOS: [ + { + key: 'idos-tos', + version: '1', + title: 'idOS ToS', + url: 'https://idos.example/tos', + }, + ], + kycProvider: [ + { + key: 'sumsub-tos', + version: '1', + title: 'SumSub ToS', + url: 'https://sumsub.example/tos', + }, + ], + }; + + it('fetches the session-scoped catalog by sessionId', async () => { + await withController(async ({ controller, handlers }) => { + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue( + MOCK_SESSION_DISCLAIMERS, + ); + + await expect( + controller.fetchSessionDisclaimers({ sessionId: 'sid' }), + ).resolves.toStrictEqual(MOCK_SESSION_DISCLAIMERS); + + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).toHaveBeenCalledWith({ sessionId: 'sid' }); + expect( + handlers.fetchSessionDisclaimersByCountry, + ).not.toHaveBeenCalled(); + expect(controller.state.sessionDisclaimers).toStrictEqual( + MOCK_SESSION_DISCLAIMERS, + ); + }); + }); + + it('fetches the global catalog by country', async () => { + await withController(async ({ controller, handlers }) => { + handlers.fetchSessionDisclaimersByCountry.mockResolvedValue( + globalCatalog, + ); + + await expect( + controller.fetchSessionDisclaimers({ country: 'USA' }), + ).resolves.toStrictEqual(globalCatalog); + + expect(handlers.fetchSessionDisclaimersByCountry).toHaveBeenCalledWith({ + country: 'USA', + }); + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).not.toHaveBeenCalled(); + expect(controller.state.sessionDisclaimers).toBeNull(); + }); + }); + + it('throws when neither sessionId nor country is provided', async () => { + await withController(async ({ controller, handlers }) => { + await expect( + controller.fetchSessionDisclaimers( + {} as FetchSessionDisclaimersParams, + ), + ).rejects.toThrow(/exactly one of sessionId or country/u); + + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).not.toHaveBeenCalled(); + expect( + handlers.fetchSessionDisclaimersByCountry, + ).not.toHaveBeenCalled(); + }); + }); + + it('throws when both sessionId and country are provided', async () => { + await withController(async ({ controller, handlers }) => { + await expect( + controller.fetchSessionDisclaimers({ + sessionId: 'sid', + country: 'USA', + } as unknown as FetchSessionDisclaimersParams), + ).rejects.toThrow(/exactly one of sessionId or country/u); + + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).not.toHaveBeenCalled(); + expect( + handlers.fetchSessionDisclaimersByCountry, + ).not.toHaveBeenCalled(); + }); + }); + + it('does not write sessionDisclaimers when reset lands during a sessionId fetch', async () => { + await withController(async ({ controller, handlers }) => { + handlers.fetchSessionDisclaimersBySessionId.mockImplementation( + async () => { + controller.reset(); + return MOCK_SESSION_DISCLAIMERS; + }, + ); + + await expect( + controller.fetchSessionDisclaimers({ sessionId: 'sid' }), + ).resolves.toStrictEqual(MOCK_SESSION_DISCLAIMERS); + + expect(controller.state.sessionDisclaimers).toBeNull(); + }); + }); + }); + describe('acceptTermsAndStartSession', () => { it('captures terms and creates a session', async () => { await withController( @@ -2704,7 +2822,7 @@ describe('KycController', () => { product: 'money', }); - expect(handlers.fetchSessionDisclaimers).toHaveBeenCalled(); + expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalled(); expect(handlers.submitVendorDisclaimers).toHaveBeenCalledWith({ vendor: 'iron', disclaimerIds: ['d1'], @@ -3038,7 +3156,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers, launcher }) => { - handlers.fetchSessionDisclaimers.mockResolvedValue( + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue( MOCK_SESSION_DISCLAIMERS, ); handlers.submitSessionDisclaimers.mockResolvedValue({ @@ -3071,7 +3189,7 @@ describe('KycController', () => { vendor: 'iron', disclaimerIds: ['d1'], }); - expect(handlers.fetchSessionDisclaimers).toHaveBeenCalledWith({ + expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalledWith({ sessionId: 'sid', }); expect(handlers.submitSessionDisclaimers).toHaveBeenCalledWith({ @@ -3089,7 +3207,7 @@ describe('KycController', () => { expect( handlers.createUkycSession.mock.invocationCallOrder[0], ).toBeLessThan( - handlers.fetchSessionDisclaimers.mock.invocationCallOrder[0], + handlers.fetchSessionDisclaimersBySessionId.mock.invocationCallOrder[0], ); expect(handlers.createUkycSession).toHaveBeenCalledWith( expect.objectContaining({ @@ -3172,7 +3290,7 @@ describe('KycController', () => { consented: true, })), }; - handlers.fetchSessionDisclaimers + handlers.fetchSessionDisclaimersBySessionId .mockResolvedValueOnce(MOCK_SESSION_DISCLAIMERS) .mockResolvedValueOnce(consentedCatalog); handlers.submitSessionDisclaimers.mockRejectedValue( @@ -3193,7 +3311,7 @@ describe('KycController', () => { idosDisclaimersAccepted: MOCK_IDOS_DISCLAIMERS_ACCEPTED, }); - expect(handlers.fetchSessionDisclaimers).toHaveBeenCalledTimes(2); + expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalledTimes(2); expect(controller.state.phase).toBe('done'); expect(launcher.launch).toHaveBeenCalled(); controller.reset(); @@ -3220,7 +3338,7 @@ describe('KycController', () => { consented: true, })), }; - handlers.fetchSessionDisclaimers + handlers.fetchSessionDisclaimersBySessionId .mockResolvedValueOnce(MOCK_SESSION_DISCLAIMERS) .mockResolvedValueOnce(afterConflict); handlers.submitSessionDisclaimers.mockRejectedValue( @@ -3270,7 +3388,7 @@ describe('KycController', () => { })), credentialReusabilityConsentGiven: false, }; - handlers.fetchSessionDisclaimers.mockResolvedValue(consentedDocs); + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue(consentedDocs); handlers.submitSessionDisclaimers.mockRejectedValue( new HttpError( 409, @@ -3305,7 +3423,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers, launcher }) => { - handlers.fetchSessionDisclaimers.mockResolvedValue( + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue( MOCK_SESSION_DISCLAIMERS, ); handlers.submitSessionDisclaimers.mockRejectedValue( @@ -3342,7 +3460,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers, launcher }) => { - handlers.fetchSessionDisclaimers.mockResolvedValue({ + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue({ idOS: [ { key: 'idos-tos', @@ -3400,7 +3518,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers, launcher }) => { - handlers.fetchSessionDisclaimers.mockResolvedValue({ + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue({ ...MOCK_SESSION_DISCLAIMERS, idOS: MOCK_SESSION_DISCLAIMERS.idOS.map((doc) => ({ ...doc, @@ -3863,7 +3981,7 @@ describe('KycController', () => { let release: () => void = () => { // placeholder }; - handlers.fetchSessionDisclaimers.mockReturnValue( + handlers.fetchSessionDisclaimersBySessionId.mockReturnValue( new Promise((resolve) => { release = (): void => { resolve(MOCK_SESSION_DISCLAIMERS); @@ -3991,7 +4109,7 @@ describe('KycController', () => { idosDisclaimersAccepted: MOCK_IDOS_DISCLAIMERS_ACCEPTED, }); - expect(handlers.fetchSessionDisclaimers).toHaveBeenCalledWith({ + expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalledWith({ sessionId: 'sid', }); expect(launcher.launch).not.toHaveBeenCalled(); @@ -4013,7 +4131,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers }) => { - handlers.fetchSessionDisclaimers + handlers.fetchSessionDisclaimersBySessionId .mockResolvedValueOnce(MOCK_SESSION_DISCLAIMERS) .mockImplementationOnce(async () => { controller.reset(); @@ -4049,7 +4167,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers }) => { - handlers.fetchSessionDisclaimers.mockImplementation(async () => { + handlers.fetchSessionDisclaimersBySessionId.mockImplementation(async () => { controller.reset(); return MOCK_SESSION_DISCLAIMERS; }); @@ -4170,7 +4288,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers, launcher }) => { - handlers.fetchSessionDisclaimers.mockResolvedValue({ + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue({ idOS: [], kycProvider: MOCK_SESSION_DISCLAIMERS.kycProvider, credentialReusabilityConsentGiven: false, @@ -4205,7 +4323,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers, launcher }) => { - handlers.fetchSessionDisclaimers.mockResolvedValue({ + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue({ idOS: MOCK_SESSION_DISCLAIMERS.idOS, kycProvider: [], credentialReusabilityConsentGiven: false, @@ -4239,7 +4357,7 @@ describe('KycController', () => { }, }, async ({ controller, handlers, launcher }) => { - handlers.fetchSessionDisclaimers + handlers.fetchSessionDisclaimersBySessionId .mockResolvedValueOnce(MOCK_SESSION_DISCLAIMERS) .mockResolvedValueOnce({ idOS: [], @@ -4322,7 +4440,7 @@ describe('KycController', () => { }); expect(controller.state.phase).toBe('idle'); - expect(handlers.fetchSessionDisclaimers).not.toHaveBeenCalled(); + expect(handlers.fetchSessionDisclaimersBySessionId).not.toHaveBeenCalled(); expect(handlers.submitSessionDisclaimers).not.toHaveBeenCalled(); }, ); @@ -4807,6 +4925,20 @@ describe('KycController', () => { ).toContain('ch_reset'); }); }); + + it('exposes fetchSessionDisclaimers as a messenger action', async () => { + await withController(async ({ rootMessenger, handlers }) => { + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue( + MOCK_SESSION_DISCLAIMERS, + ); + + await expect( + rootMessenger.call('KycController:fetchSessionDisclaimers', { + sessionId: 'sid', + }), + ).resolves.toStrictEqual(MOCK_SESSION_DISCLAIMERS); + }); + }); }); }); @@ -4823,7 +4955,8 @@ type ServiceHandlers = { checkKycRequired: jest.Mock; createVendorCustomer: jest.Mock; submitVendorDisclaimers: jest.Mock; - fetchSessionDisclaimers: jest.Mock; + fetchSessionDisclaimersByCountry: jest.Mock; + fetchSessionDisclaimersBySessionId: jest.Mock; submitSessionDisclaimers: jest.Mock; fetchKycStatus: jest.Mock; fetchIdosEnclaveJwks: jest.Mock; @@ -4860,7 +4993,8 @@ const SERVICE_ACTIONS = [ 'KycService:checkKycRequired', 'KycService:createVendorCustomer', 'KycService:submitVendorDisclaimers', - 'KycService:fetchSessionDisclaimers', + 'KycService:fetchSessionDisclaimersByCountry', + 'KycService:fetchSessionDisclaimersBySessionId', 'KycService:submitSessionDisclaimers', 'KycService:fetchKycStatus', 'KycService:fetchIdosEnclaveJwks', @@ -4969,7 +5103,11 @@ function withController( .mockResolvedValue([ { id: 'sign-1', customer_id: 'cust-1', content_id: 'd1' }, ]), - fetchSessionDisclaimers: jest + fetchSessionDisclaimersByCountry: jest.fn().mockResolvedValue({ + idOS: [], + kycProvider: [], + }), + fetchSessionDisclaimersBySessionId: jest .fn() .mockResolvedValue(MOCK_SESSION_DISCLAIMERS), submitSessionDisclaimers: jest.fn().mockResolvedValue({ @@ -5021,8 +5159,12 @@ function withController( handlers.submitVendorDisclaimers, ); rootMessenger.registerActionHandler( - 'KycService:fetchSessionDisclaimers', - handlers.fetchSessionDisclaimers, + 'KycService:fetchSessionDisclaimersByCountry', + handlers.fetchSessionDisclaimersByCountry, + ); + rootMessenger.registerActionHandler( + 'KycService:fetchSessionDisclaimersBySessionId', + handlers.fetchSessionDisclaimersBySessionId, ); rootMessenger.registerActionHandler( 'KycService:submitSessionDisclaimers', diff --git a/packages/kyc-controller/src/KycController.ts b/packages/kyc-controller/src/KycController.ts index 4c18684516a..39085630f3e 100644 --- a/packages/kyc-controller/src/KycController.ts +++ b/packages/kyc-controller/src/KycController.ts @@ -26,6 +26,7 @@ import type { KycConsentRecord, KycCustomerIdentity, KycDisclaimer, + KycDisclaimersCatalog, KycPhase, KycProduct, KycProviderDisclaimersAccepted, @@ -614,11 +615,28 @@ function usesConsentsFlow(vendor: KycVendor): boolean { return vendor !== 'moonpay'; } +/** + * Parameters for {@link KycController.fetchSessionDisclaimers}. Provide + * exactly one of `sessionId` or `country`. + */ +export type FetchSessionDisclaimersParams = + | { + /** UKYC session id from `KycService.createUkycSession`. */ + sessionId: string; + country?: never; + } + | { + /** ISO 3166-1 alpha-3 country code for `GET /disclaimers?country=`. */ + country: string; + sessionId?: never; + }; + // === MESSENGER === const MESSENGER_EXPOSED_METHODS = [ 'initialize', 'loadDisclaimers', + 'fetchSessionDisclaimers', 'acceptTermsAndStartSession', 'createVendorCustomer', 'clearSavedTerms', @@ -1075,6 +1093,60 @@ export class KycController extends BaseController< } } + /** + * Fetches the idOS + KYC-provider disclaimer catalog. Pass exactly one of + * `sessionId` or `country`: + * + * - `{ sessionId }` → {@link KycService.fetchSessionDisclaimersBySessionId} + * (`GET /sessions/{sessionId}/disclaimers`) + * - `{ country }` → {@link KycService.fetchSessionDisclaimersByCountry} + * (`GET /disclaimers?country=`) + * + * A session-id fetch also writes the catalog to `sessionDisclaimers`. + * + * @param params - The parameters. Provide exactly one of `sessionId` or + * `country`. + * @param params.sessionId - The UKYC session id. + * @param params.country - ISO 3166-1 alpha-3 country code. + * @returns The catalog. Session fetches include consent state; country + * fetches do not. + */ + async fetchSessionDisclaimers( + params: FetchSessionDisclaimersParams, + ): Promise { + const sessionId = 'sessionId' in params ? params.sessionId : undefined; + const country = 'country' in params ? params.country : undefined; + if (sessionId && country) { + throw new Error( + 'KycController.fetchSessionDisclaimers: provide exactly one of sessionId or country.', + ); + } + + const generation = this.#generation; + + if (country) { + return this.messenger.call( + 'KycService:fetchSessionDisclaimersByCountry', + { country }, + ); + } + + if (!sessionId) { + throw new Error( + 'KycController.fetchSessionDisclaimers: provide exactly one of sessionId or country.', + ); + } + + const catalog = await this.messenger.call( + 'KycService:fetchSessionDisclaimersBySessionId', + { sessionId }, + ); + this.#updateIfCurrent(generation, (state) => { + state.sessionDisclaimers = catalog; + }); + return catalog; + } + /** * Captures terms acceptance for the currently loaded disclaimers and creates * a session. diff --git a/packages/kyc-controller/src/index.ts b/packages/kyc-controller/src/index.ts index 34ec730acb7..911f13e5957 100644 --- a/packages/kyc-controller/src/index.ts +++ b/packages/kyc-controller/src/index.ts @@ -14,6 +14,7 @@ export type { KycControllerState, KycControllerStateChangeEvent, KycControllerStatusChangedEvent, + FetchSessionDisclaimersParams, } from './KycController.js'; export type { KycControllerAcceptTermsAndStartSessionAction, @@ -24,6 +25,7 @@ export type { KycControllerClearSavedTermsAction, KycControllerClearStateAction, KycControllerCreateVendorCustomerAction, + KycControllerFetchSessionDisclaimersAction, KycControllerGetCustomerIdentityAction, KycControllerGetKycStatusAction, KycControllerGetSessionStatusAction, @@ -72,6 +74,7 @@ export type { KycServiceFetchKycStatusAction, KycServiceFetchSessionDisclaimersByCountryAction, KycServiceFetchSessionDisclaimersBySessionIdAction, + KycServiceFetchVendorDisclaimersAction, KycServiceGetGeoCountryAction, KycServiceGetSessionStatusAction, KycServiceSetAuthorizationsAction, From 0a30e47b3c8a247d21c8b4c9a5708d345ec21d99 Mon Sep 17 00:00:00 2001 From: Jiexi Luan-Huang Date: Wed, 9 Sep 2026 14:16:55 -0700 Subject: [PATCH 3/7] changelog --- packages/kyc-controller/CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/kyc-controller/CHANGELOG.md b/packages/kyc-controller/CHANGELOG.md index 352aa60da90..2eb44d8cd68 100644 --- a/packages/kyc-controller/CHANGELOG.md +++ b/packages/kyc-controller/CHANGELOG.md @@ -9,14 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `KycController.fetchSessionDisclaimers`, which fetches the idOS + KYC-provider catalog by `{ sessionId }` or `{ country }` via `KycService.fetchSessionDisclaimersBySessionId` or `KycService.fetchSessionDisclaimersByCountry`. +- Add `KycController.fetchSessionDisclaimers`, which fetches the idOS + KYC-provider disclaimers by `{ sessionId }` or `{ country }` via `KycService.fetchSessionDisclaimersBySessionId` or `KycService.fetchSessionDisclaimersByCountry`. ([#10162](https://github.com/MetaMask/core/pull/10162)) ### Changed -- **BREAKING:** Rename `KycService.fetchDisclaimersCatalog` to `fetchSessionDisclaimersByCountry`. +- **BREAKING:** Rename `KycService.fetchDisclaimersCatalog` to `fetchSessionDisclaimersByCountry`. ([#10162](https://github.com/MetaMask/core/pull/10162)) - Rename `FetchDisclaimersCatalogParams` to `FetchSessionDisclaimersByCountryParams`. - Rename the messenger action `KycService:fetchDisclaimersCatalog` to `KycService:fetchSessionDisclaimersByCountry`. -- **BREAKING:** Rename `KycService.fetchSessionDisclaimers` to `fetchSessionDisclaimersBySessionId`. +- **BREAKING:** Rename `KycService.fetchSessionDisclaimers` to `fetchSessionDisclaimersBySessionId` ([#10162](https://github.com/MetaMask/core/pull/10162)) - Rename `FetchSessionDisclaimersParams` to `FetchSessionDisclaimersBySessionIdParams`. - Rename the messenger action `KycService:fetchSessionDisclaimers` to `KycService:fetchSessionDisclaimersBySessionId`. From 4555ed44fc73eec574ad441900a77f211bfafb6c Mon Sep 17 00:00:00 2001 From: Jiexi Luan-Huang Date: Wed, 9 Sep 2026 14:30:20 -0700 Subject: [PATCH 4/7] fix: satisfy eslint for fetchSessionDisclaimers Co-authored-by: Cursor --- .../kyc-controller/src/KycController.test.ts | 24 +++++++++---------- packages/kyc-controller/src/KycController.ts | 6 +++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/kyc-controller/src/KycController.test.ts b/packages/kyc-controller/src/KycController.test.ts index e2ff67fb691..79be4ace7d7 100644 --- a/packages/kyc-controller/src/KycController.test.ts +++ b/packages/kyc-controller/src/KycController.test.ts @@ -527,9 +527,9 @@ describe('KycController', () => { MOCK_SESSION_DISCLAIMERS, ); - await expect( - controller.fetchSessionDisclaimers({ sessionId: 'sid' }), - ).resolves.toStrictEqual(MOCK_SESSION_DISCLAIMERS); + expect( + await controller.fetchSessionDisclaimers({ sessionId: 'sid' }), + ).toStrictEqual(MOCK_SESSION_DISCLAIMERS); expect( handlers.fetchSessionDisclaimersBySessionId, @@ -549,9 +549,9 @@ describe('KycController', () => { globalCatalog, ); - await expect( - controller.fetchSessionDisclaimers({ country: 'USA' }), - ).resolves.toStrictEqual(globalCatalog); + expect( + await controller.fetchSessionDisclaimers({ country: 'USA' }), + ).toStrictEqual(globalCatalog); expect(handlers.fetchSessionDisclaimersByCountry).toHaveBeenCalledWith({ country: 'USA', @@ -607,9 +607,9 @@ describe('KycController', () => { }, ); - await expect( - controller.fetchSessionDisclaimers({ sessionId: 'sid' }), - ).resolves.toStrictEqual(MOCK_SESSION_DISCLAIMERS); + expect( + await controller.fetchSessionDisclaimers({ sessionId: 'sid' }), + ).toStrictEqual(MOCK_SESSION_DISCLAIMERS); expect(controller.state.sessionDisclaimers).toBeNull(); }); @@ -4932,11 +4932,11 @@ describe('KycController', () => { MOCK_SESSION_DISCLAIMERS, ); - await expect( - rootMessenger.call('KycController:fetchSessionDisclaimers', { + expect( + await rootMessenger.call('KycController:fetchSessionDisclaimers', { sessionId: 'sid', }), - ).resolves.toStrictEqual(MOCK_SESSION_DISCLAIMERS); + ).toStrictEqual(MOCK_SESSION_DISCLAIMERS); }); }); }); diff --git a/packages/kyc-controller/src/KycController.ts b/packages/kyc-controller/src/KycController.ts index 39085630f3e..1553a8e41be 100644 --- a/packages/kyc-controller/src/KycController.ts +++ b/packages/kyc-controller/src/KycController.ts @@ -1114,8 +1114,10 @@ export class KycController extends BaseController< async fetchSessionDisclaimers( params: FetchSessionDisclaimersParams, ): Promise { - const sessionId = 'sessionId' in params ? params.sessionId : undefined; - const country = 'country' in params ? params.country : undefined; + const { sessionId, country } = params as { + sessionId?: string; + country?: string; + }; if (sessionId && country) { throw new Error( 'KycController.fetchSessionDisclaimers: provide exactly one of sessionId or country.', From 8dfd4cbed9625d24ac5fbb18345fa0aed50393f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Van=20Eyck?= Date: Thu, 10 Sep 2026 00:17:19 +0200 Subject: [PATCH 5/7] fix: linting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sébastien Van Eyck --- packages/kyc-controller/ARCHITECTURE.md | 32 ++++++++-------- .../kyc-controller/src/KycController.test.ts | 37 +++++++++++++------ .../kyc-controller/src/KycService.test.ts | 4 +- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/packages/kyc-controller/ARCHITECTURE.md b/packages/kyc-controller/ARCHITECTURE.md index be6532b14ca..0dac3cc4f3e 100644 --- a/packages/kyc-controller/ARCHITECTURE.md +++ b/packages/kyc-controller/ARCHITECTURE.md @@ -128,23 +128,23 @@ Exposed messenger actions (`MESSENGER_EXPOSED_METHODS`): Endpoints: -| Method | HTTP | Endpoint | Purpose | -| -------------------------- | ------ | -------------------------------------------- | -------------------------------------------------------------------------------------- | -| `getGeoCountry` | — | (geolocation action) | Resolve alpha-3 country | -| `fetchVendorDisclaimers` | `GET` | `/vendors/{vendor}/disclaimers?country=` | Vendor T&Cs to accept (`vendor` defaults to `moonpay`) | -| `createSession` | `POST` | `/vendors/moonpay/sessions` | Create MoonPay vendor session | -| `checkKycRequired` | `POST` | `/vendors/{vendor}/kyc-required` | Is KYC required? (normalizes `required` → `kycRequired`) | -| `createVendorCustomer` | `POST` | `/vendors/{vendor}/customers` | Create or resume an empty-shell vendor customer | -| `submitVendorDisclaimers` | `POST` | `/vendors/{vendor}/disclaimers` | Record vendor T&C signings (`disclaimerIds`) | -| `fetchSessionDisclaimersByCountry` | `GET` | `/disclaimers?country=` | Global idOS + KYC-provider catalog (no consent state) | +| Method | HTTP | Endpoint | Purpose | +| ------------------------------------ | ------ | -------------------------------------------- | -------------------------------------------------------------------------------------- | +| `getGeoCountry` | — | (geolocation action) | Resolve alpha-3 country | +| `fetchVendorDisclaimers` | `GET` | `/vendors/{vendor}/disclaimers?country=` | Vendor T&Cs to accept (`vendor` defaults to `moonpay`) | +| `createSession` | `POST` | `/vendors/moonpay/sessions` | Create MoonPay vendor session | +| `checkKycRequired` | `POST` | `/vendors/{vendor}/kyc-required` | Is KYC required? (normalizes `required` → `kycRequired`) | +| `createVendorCustomer` | `POST` | `/vendors/{vendor}/customers` | Create or resume an empty-shell vendor customer | +| `submitVendorDisclaimers` | `POST` | `/vendors/{vendor}/disclaimers` | Record vendor T&C signings (`disclaimerIds`) | +| `fetchSessionDisclaimersByCountry` | `GET` | `/disclaimers?country=` | Global idOS + KYC-provider catalog (no consent state) | | `fetchSessionDisclaimersBySessionId` | `GET` | `/sessions/{id}/disclaimers` | Session-scoped catalog, with `consented` flags + credential-reuse flag | -| `submitSessionDisclaimers` | `POST` | `/sessions/{id}/disclaimers` | Record `{ idOS, kycProvider, credentialReusabilityConsentGiven }` consents | -| `fetchKycStatus` | `GET` | `/kyc/status` | User-keyed simplified KYC status | -| `fetchIdosEnclaveJwks` | `GET` | `{idosEnclaveBaseUrl}/.well-known/jwks.json` | idOS enclave JWKS for `encryptionDataKey` attestation | -| `fetchIdosRelayJwks` | `GET` | `{idosRelayBaseUrl}/.well-known/jwks.json` | idOS relay JWKS for `ukycCapabilityToken` attestation | -| `createUkycSession` | `POST` | `/sessions` | Start SumSub sub-flow; registers session client public key; returns encryption schemas | -| `setAuthorizations` | `POST` | `/sessions/{id}/authorizations` | Submit wrapped `data_encryption_key` and wrapped `ukyc_capability_token` | -| `createJourney` | `POST` | `/sessions/{id}/journey` | Create verification journey → applicant token | +| `submitSessionDisclaimers` | `POST` | `/sessions/{id}/disclaimers` | Record `{ idOS, kycProvider, credentialReusabilityConsentGiven }` consents | +| `fetchKycStatus` | `GET` | `/kyc/status` | User-keyed simplified KYC status | +| `fetchIdosEnclaveJwks` | `GET` | `{idosEnclaveBaseUrl}/.well-known/jwks.json` | idOS enclave JWKS for `encryptionDataKey` attestation | +| `fetchIdosRelayJwks` | `GET` | `{idosRelayBaseUrl}/.well-known/jwks.json` | idOS relay JWKS for `ukycCapabilityToken` attestation | +| `createUkycSession` | `POST` | `/sessions` | Start SumSub sub-flow; registers session client public key; returns encryption schemas | +| `setAuthorizations` | `POST` | `/sessions/{id}/authorizations` | Submit wrapped `data_encryption_key` and wrapped `ukyc_capability_token` | +| `createJourney` | `POST` | `/sessions/{id}/journey` | Create verification journey → applicant token | ### 2.3 `crypto.ts` diff --git a/packages/kyc-controller/src/KycController.test.ts b/packages/kyc-controller/src/KycController.test.ts index 79be4ace7d7..e67e970bcf3 100644 --- a/packages/kyc-controller/src/KycController.test.ts +++ b/packages/kyc-controller/src/KycController.test.ts @@ -2822,7 +2822,9 @@ describe('KycController', () => { product: 'money', }); - expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalled(); + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).toHaveBeenCalled(); expect(handlers.submitVendorDisclaimers).toHaveBeenCalledWith({ vendor: 'iron', disclaimerIds: ['d1'], @@ -3189,7 +3191,9 @@ describe('KycController', () => { vendor: 'iron', disclaimerIds: ['d1'], }); - expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalledWith({ + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).toHaveBeenCalledWith({ sessionId: 'sid', }); expect(handlers.submitSessionDisclaimers).toHaveBeenCalledWith({ @@ -3207,7 +3211,8 @@ describe('KycController', () => { expect( handlers.createUkycSession.mock.invocationCallOrder[0], ).toBeLessThan( - handlers.fetchSessionDisclaimersBySessionId.mock.invocationCallOrder[0], + handlers.fetchSessionDisclaimersBySessionId.mock + .invocationCallOrder[0], ); expect(handlers.createUkycSession).toHaveBeenCalledWith( expect.objectContaining({ @@ -3311,7 +3316,9 @@ describe('KycController', () => { idosDisclaimersAccepted: MOCK_IDOS_DISCLAIMERS_ACCEPTED, }); - expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalledTimes(2); + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).toHaveBeenCalledTimes(2); expect(controller.state.phase).toBe('done'); expect(launcher.launch).toHaveBeenCalled(); controller.reset(); @@ -3388,7 +3395,9 @@ describe('KycController', () => { })), credentialReusabilityConsentGiven: false, }; - handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue(consentedDocs); + handlers.fetchSessionDisclaimersBySessionId.mockResolvedValue( + consentedDocs, + ); handlers.submitSessionDisclaimers.mockRejectedValue( new HttpError( 409, @@ -4109,7 +4118,9 @@ describe('KycController', () => { idosDisclaimersAccepted: MOCK_IDOS_DISCLAIMERS_ACCEPTED, }); - expect(handlers.fetchSessionDisclaimersBySessionId).toHaveBeenCalledWith({ + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).toHaveBeenCalledWith({ sessionId: 'sid', }); expect(launcher.launch).not.toHaveBeenCalled(); @@ -4167,10 +4178,12 @@ describe('KycController', () => { }, }, async ({ controller, handlers }) => { - handlers.fetchSessionDisclaimersBySessionId.mockImplementation(async () => { - controller.reset(); - return MOCK_SESSION_DISCLAIMERS; - }); + handlers.fetchSessionDisclaimersBySessionId.mockImplementation( + async () => { + controller.reset(); + return MOCK_SESSION_DISCLAIMERS; + }, + ); await controller.acceptTermsAndStartSession({ email: 'a@b.co', @@ -4440,7 +4453,9 @@ describe('KycController', () => { }); expect(controller.state.phase).toBe('idle'); - expect(handlers.fetchSessionDisclaimersBySessionId).not.toHaveBeenCalled(); + expect( + handlers.fetchSessionDisclaimersBySessionId, + ).not.toHaveBeenCalled(); expect(handlers.submitSessionDisclaimers).not.toHaveBeenCalled(); }, ); diff --git a/packages/kyc-controller/src/KycService.test.ts b/packages/kyc-controller/src/KycService.test.ts index bf8ad25f8ea..a6fc864b7bb 100644 --- a/packages/kyc-controller/src/KycService.test.ts +++ b/packages/kyc-controller/src/KycService.test.ts @@ -868,7 +868,9 @@ describe('KycService', () => { const { service } = getService(); expect( - await service.fetchSessionDisclaimersBySessionId({ sessionId: 'sid-1' }), + await service.fetchSessionDisclaimersBySessionId({ + sessionId: 'sid-1', + }), ).toStrictEqual(catalog); }); From 1da654b4ffb4b913a4efd9a198a3107a22e5d78d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Van=20Eyck?= Date: Thu, 10 Sep 2026 00:20:41 +0200 Subject: [PATCH 6/7] fix: messenger-action-types issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sébastien Van Eyck --- .../kyc-controller/src/KycController-method-action-types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/kyc-controller/src/KycController-method-action-types.ts b/packages/kyc-controller/src/KycController-method-action-types.ts index c602a10bb89..c03a617de6b 100644 --- a/packages/kyc-controller/src/KycController-method-action-types.ts +++ b/packages/kyc-controller/src/KycController-method-action-types.ts @@ -58,9 +58,9 @@ export type KycControllerLoadDisclaimersAction = { * `sessionId` or `country`: * * - `{ sessionId }` → {@link KycService.fetchSessionDisclaimersBySessionId} - * (`GET /sessions/{sessionId}/disclaimers`) + * (`GET /sessions/{sessionId}/disclaimers`) * - `{ country }` → {@link KycService.fetchSessionDisclaimersByCountry} - * (`GET /disclaimers?country=`) + * (`GET /disclaimers?country=`) * * A session-id fetch also writes the catalog to `sessionDisclaimers`. * From 97d6b86c0e2244cdebbff4ff224c55a37c5d047d Mon Sep 17 00:00:00 2001 From: Jiexi Luan-Huang Date: Wed, 9 Sep 2026 15:22:27 -0700 Subject: [PATCH 7/7] changelog --- packages/kyc-controller/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kyc-controller/CHANGELOG.md b/packages/kyc-controller/CHANGELOG.md index 2eb44d8cd68..7f9786fa97c 100644 --- a/packages/kyc-controller/CHANGELOG.md +++ b/packages/kyc-controller/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `KycController.fetchSessionDisclaimers`, which fetches the idOS + KYC-provider disclaimers by `{ sessionId }` or `{ country }` via `KycService.fetchSessionDisclaimersBySessionId` or `KycService.fetchSessionDisclaimersByCountry`. ([#10162](https://github.com/MetaMask/core/pull/10162)) +- Add `KycController.fetchSessionDisclaimers`, which fetches the idOS + KYC-provider disclaimers by `{ sessionId }` or `{ country }`. ([#10162](https://github.com/MetaMask/core/pull/10162)) ### Changed