From 558776142ccda35624d98283f9396c20b683e40b Mon Sep 17 00:00:00 2001 From: Tuna Date: Wed, 22 Jul 2026 15:09:48 +0700 Subject: [PATCH 1/9] feat(wallet): wire ClaimsService and ClaimsController initialization Add default wallet initialization units for ClaimsService and ClaimsController with tight messenger delegation, required claimsService instance options, wallet-cli defaults, ownership, and dependency graph updates. Co-authored-by: Cursor --- .github/CODEOWNERS | 2 + README.md | 2 + codeowners.ts | 37 ++-- packages/wallet-cli/package.json | 1 + .../src/daemon/wallet-factory.test.ts | 5 + .../wallet-cli/src/daemon/wallet-factory.ts | 7 + packages/wallet-cli/tsconfig.build.json | 1 + packages/wallet-cli/tsconfig.json | 3 + packages/wallet/package.json | 1 + packages/wallet/src/Wallet.test.ts | 130 +++++-------- .../claims-controller.test.ts | 171 ++++++++++++++++++ .../claims-controller/claims-controller.ts | 39 ++++ .../instances/claims-controller/types.ts | 1 + .../claims-service/claims-service.test.ts | 122 +++++++++++++ .../claims-service/claims-service.ts | 33 ++++ .../instances/claims-service/types.ts | 14 ++ .../src/initialization/instances/index.ts | 2 + .../transaction-controller.test.ts | 5 + packages/wallet/src/types.ts | 4 + packages/wallet/tsconfig.build.json | 1 + packages/wallet/tsconfig.json | 3 + yarn.lock | 4 +- 22 files changed, 480 insertions(+), 108 deletions(-) create mode 100644 packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts create mode 100644 packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts create mode 100644 packages/wallet/src/initialization/instances/claims-controller/types.ts create mode 100644 packages/wallet/src/initialization/instances/claims-service/claims-service.test.ts create mode 100644 packages/wallet/src/initialization/instances/claims-service/claims-service.ts create mode 100644 packages/wallet/src/initialization/instances/claims-service/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f7d8e29ec70..02a7c744ed9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -149,6 +149,8 @@ /packages/wallet/src/initialization/instances/accounts-controller/ @MetaMask/accounts-engineers /packages/wallet/src/initialization/instances/address-book-controller/ @MetaMask/confirmations /packages/wallet/src/initialization/instances/approval-controller/ @MetaMask/confirmations +/packages/wallet/src/initialization/instances/claims-controller/ @MetaMask/web3auth +/packages/wallet/src/initialization/instances/claims-service/ @MetaMask/web3auth /packages/wallet/src/initialization/instances/connectivity-controller/ @MetaMask/core-platform /packages/wallet/src/initialization/instances/keyring-controller/ @MetaMask/accounts-engineers @MetaMask/core-platform /packages/wallet/src/initialization/instances/passkey-controller/ @MetaMask/web3auth diff --git a/README.md b/README.md index ec3d61dc7b9..c03de120027 100644 --- a/README.md +++ b/README.md @@ -647,6 +647,7 @@ linkStyle default opacity:0.5 wallet --> address_book_controller; wallet --> approval_controller; wallet --> base_controller; + wallet --> claims_controller; wallet --> connectivity_controller; wallet --> controller_utils; wallet --> keyring_controller; @@ -658,6 +659,7 @@ linkStyle default opacity:0.5 wallet --> storage_service; wallet --> transaction_controller; wallet_cli --> base_controller; + wallet_cli --> claims_controller; wallet_cli --> remote_feature_flag_controller; wallet_cli --> storage_service; wallet_cli --> wallet; diff --git a/codeowners.ts b/codeowners.ts index ce77d6b6877..16ec97facd4 100644 --- a/codeowners.ts +++ b/codeowners.ts @@ -14,12 +14,12 @@ type PackageInfo = { teams: string[]; /** - * The package's directory name under - * `/packages/wallet/src/initialization/instances`, used to generate its rule + * The package's directory name(s) under + * `/packages/wallet/src/initialization/instances`, used to generate its rule(s) * in the "Initialization" section. Omit this if the package has not been * added to the Wallet Library yet. */ - initializationPath?: string; + initializationPaths?: string[]; }; /** @@ -34,11 +34,11 @@ const PACKAGES: Record = { }, 'accounts-controller': { teams: ['@MetaMask/accounts-engineers'], - initializationPath: 'accounts-controller', + initializationPaths: ['accounts-controller'], }, 'address-book-controller': { teams: ['@MetaMask/confirmations'], - initializationPath: 'address-book-controller', + initializationPaths: ['address-book-controller'], }, 'ai-controllers': { teams: ['@MetaMask/social-ai'], @@ -57,7 +57,7 @@ const PACKAGES: Record = { }, 'approval-controller': { teams: ['@MetaMask/confirmations'], - initializationPath: 'approval-controller', + initializationPaths: ['approval-controller'], }, 'assets-controller': { teams: ['@MetaMask/metamask-assets'], @@ -98,6 +98,7 @@ const PACKAGES: Record = { }, 'claims-controller': { teams: ['@MetaMask/web3auth'], + initializationPaths: ['claims-controller', 'claims-service'], }, 'client-controller': { teams: [ @@ -120,7 +121,7 @@ const PACKAGES: Record = { }, 'connectivity-controller': { teams: ['@MetaMask/core-platform'], - initializationPath: 'connectivity-controller', + initializationPaths: ['connectivity-controller'], }, 'controller-utils': { teams: ['@MetaMask/core-platform'], @@ -182,7 +183,7 @@ const PACKAGES: Record = { }, 'keyring-controller': { teams: ['@MetaMask/accounts-engineers', '@MetaMask/core-platform'], - initializationPath: 'keyring-controller', + initializationPaths: ['keyring-controller'], }, 'local-node-utils': { teams: [ @@ -248,7 +249,7 @@ const PACKAGES: Record = { }, 'passkey-controller': { teams: ['@MetaMask/web3auth'], - initializationPath: 'passkey-controller', + initializationPaths: ['passkey-controller'], }, 'permission-controller': { teams: ['@MetaMask/core-platform'], @@ -292,14 +293,14 @@ const PACKAGES: Record = { '@MetaMask/mobile-platform', '@MetaMask/core-platform', ], - initializationPath: 'remote-feature-flag-controller', + initializationPaths: ['remote-feature-flag-controller'], }, 'sample-controllers': { teams: ['@MetaMask/core-platform'], }, 'seedless-onboarding-controller': { teams: ['@MetaMask/web3auth'], - initializationPath: 'seedless-onboarding-controller', + initializationPaths: ['seedless-onboarding-controller'], }, 'selected-network-controller': { teams: ['@MetaMask/core-platform'], @@ -342,14 +343,14 @@ const PACKAGES: Record = { '@MetaMask/mobile-platform', '@MetaMask/core-platform', ], - initializationPath: 'storage-service', + initializationPaths: ['storage-service'], }, 'subscription-controller': { teams: ['@MetaMask/web3auth'], }, 'transaction-controller': { teams: ['@MetaMask/confirmations'], - initializationPath: 'transaction-controller', + initializationPaths: ['transaction-controller'], }, 'transaction-pay-controller': { teams: ['@MetaMask/confirmations'], @@ -603,14 +604,14 @@ function buildInitializationSection(): CodeownersSection { return { title: 'Initialization', rules: Object.keys(PACKAGES) - .filter((name) => PACKAGES[name].initializationPath !== undefined) + .filter((name) => PACKAGES[name].initializationPaths !== undefined) .sort() - .map((name) => { - const { teams, initializationPath } = PACKAGES[name]; - return { + .flatMap((name) => { + const { teams, initializationPaths } = PACKAGES[name]; + return (initializationPaths ?? []).map((initializationPath) => ({ pattern: `/packages/wallet/src/initialization/instances/${initializationPath}/`, owners: teams, - }; + })); }), }; } diff --git a/packages/wallet-cli/package.json b/packages/wallet-cli/package.json index 2023edb1647..1cbadf61908 100644 --- a/packages/wallet-cli/package.json +++ b/packages/wallet-cli/package.json @@ -49,6 +49,7 @@ "@inquirer/confirm": "^6.0.11", "@inquirer/password": "^5.1.1", "@metamask/base-controller": "^9.1.0", + "@metamask/claims-controller": "workspace:^", "@metamask/remote-feature-flag-controller": "^4.2.2", "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.1", diff --git a/packages/wallet-cli/src/daemon/wallet-factory.test.ts b/packages/wallet-cli/src/daemon/wallet-factory.test.ts index de29cebdd7c..f51b0e626f0 100644 --- a/packages/wallet-cli/src/daemon/wallet-factory.test.ts +++ b/packages/wallet-cli/src/daemon/wallet-factory.test.ts @@ -1,3 +1,4 @@ +import { Env } from '@metamask/claims-controller'; import { ClientConfigApiService } from '@metamask/remote-feature-flag-controller'; import { InMemoryStorageAdapter } from '@metamask/storage-service'; import { @@ -120,6 +121,10 @@ describe('createWallet', () => { ); expect(instanceOptions.transactionController?.disableSwaps).toBe(true); expect(instanceOptions.transactionController?.hooks).toStrictEqual({}); + expect(instanceOptions.claimsService).toStrictEqual({ + env: Env.PRD, + fetchFunction: globalThis.fetch, + }); expect(ClientConfigApiService).toHaveBeenCalled(); await dispose(); diff --git a/packages/wallet-cli/src/daemon/wallet-factory.ts b/packages/wallet-cli/src/daemon/wallet-factory.ts index f281c74f230..98845c7ffb9 100644 --- a/packages/wallet-cli/src/daemon/wallet-factory.ts +++ b/packages/wallet-cli/src/daemon/wallet-factory.ts @@ -1,3 +1,4 @@ +import { Env } from '@metamask/claims-controller'; import { ClientConfigApiService, ClientType, @@ -65,6 +66,8 @@ export type CreateWalletResult = { * - `transactionController` — swaps processing disabled and no client hooks; * see the slot's inline comment for why the daemon relies on the * controller's defaults for everything else. + * - `claimsService` — production Claims API environment and the platform + * `fetch` implementation. * * The optional `keyringController` slot is intentionally omitted so the * controller's built-in defaults (e.g. the PBKDF2 encryptor) apply. @@ -116,6 +119,10 @@ function buildInstanceOptions( // the controller's default. hooks: {}, }, + claimsService: { + env: Env.PRD, + fetchFunction: globalThis.fetch, + }, }; } diff --git a/packages/wallet-cli/tsconfig.build.json b/packages/wallet-cli/tsconfig.build.json index 934a11754a4..c2d3d2accf6 100644 --- a/packages/wallet-cli/tsconfig.build.json +++ b/packages/wallet-cli/tsconfig.build.json @@ -7,6 +7,7 @@ }, "references": [ { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../claims-controller/tsconfig.build.json" }, { "path": "../remote-feature-flag-controller/tsconfig.build.json" }, { "path": "../storage-service/tsconfig.build.json" }, { "path": "../wallet/tsconfig.build.json" } diff --git a/packages/wallet-cli/tsconfig.json b/packages/wallet-cli/tsconfig.json index d0fd77d3457..a9fb1ecd172 100644 --- a/packages/wallet-cli/tsconfig.json +++ b/packages/wallet-cli/tsconfig.json @@ -7,6 +7,9 @@ { "path": "../base-controller" }, + { + "path": "../claims-controller" + }, { "path": "../remote-feature-flag-controller" }, diff --git a/packages/wallet/package.json b/packages/wallet/package.json index b56f1b253f5..039cbcf1a9e 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -60,6 +60,7 @@ "@metamask/approval-controller": "^9.0.2", "@metamask/base-controller": "^9.1.0", "@metamask/browser-passworder": "^6.0.0", + "@metamask/claims-controller": "^0.5.3", "@metamask/connectivity-controller": "^0.3.0", "@metamask/controller-utils": "^12.3.0", "@metamask/keyring-controller": "^27.1.0", diff --git a/packages/wallet/src/Wallet.test.ts b/packages/wallet/src/Wallet.test.ts index 75dac034e92..992709efe91 100644 --- a/packages/wallet/src/Wallet.test.ts +++ b/packages/wallet/src/Wallet.test.ts @@ -1,4 +1,6 @@ import { getDefaultAddressBookControllerState } from '@metamask/address-book-controller'; +import { Env } from '@metamask/claims-controller'; +import { getDefaultClaimsControllerState } from '@metamask/claims-controller'; import { CONNECTIVITY_STATUSES } from '@metamask/connectivity-controller'; import { Messenger } from '@metamask/messenger'; import { InMemoryStorageAdapter } from '@metamask/storage-service'; @@ -10,6 +12,7 @@ import * as initializationModule from './initialization/initialization.js'; import { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js'; import { importSecretRecoveryPhrase } from './utilities.js'; import { Wallet } from './Wallet.js'; +import type { WalletOptions } from './types.js'; const TEST_SRP = 'test test test test test test test test test test test ball'; const TEST_PASSWORD = 'testpass'; @@ -23,20 +26,28 @@ const REMOTE_FEATURE_FLAG_OPTIONS = { }, }; +function getInstanceOptions(): WalletOptions['instanceOptions'] { + return { + connectivityController: { + connectivityAdapter: new AlwaysOnlineAdapter(), + }, + networkController: { + infuraProjectId: 'fake-infura-project-id', + }, + storageService: { + storage: new InMemoryStorageAdapter(), + }, + remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, + claimsService: { + env: Env.DEV, + fetchFunction: globalThis.fetch, + }, + }; +} + async function setupWallet(): Promise { const wallet = new Wallet({ - instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, - storageService: { - storage: new InMemoryStorageAdapter(), - }, - remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, - }, + instanceOptions: getInstanceOptions(), }); await importSecretRecoveryPhrase(wallet, TEST_PASSWORD, TEST_SRP); @@ -86,19 +97,10 @@ describe('Wallet', () => { it('supports passing instance options', async () => { const wallet = new Wallet({ instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, + ...getInstanceOptions(), keyringController: { encryptor: new MockEncryptor(), }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, - storageService: { - storage: new InMemoryStorageAdapter(), - }, - remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, }, }); @@ -137,18 +139,7 @@ describe('Wallet', () => { init: (): DummyService => new DummyService(), }, ], - instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, - storageService: { - storage: new InMemoryStorageAdapter(), - }, - remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, - }, + instanceOptions: getInstanceOptions(), }); const { state } = wallet; @@ -180,18 +171,7 @@ describe('Wallet', () => { }); const wallet = new Wallet({ - instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, - storageService: { - storage: new InMemoryStorageAdapter(), - }, - remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, - }, + instanceOptions: getInstanceOptions(), }); expect(wallet.controllerMetadata).toStrictEqual({ @@ -290,18 +270,7 @@ describe('Wallet', () => { addressBook: { '0x1': { [ADDRESS]: entry } }, }, }, - instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, - storageService: { - storage: new InMemoryStorageAdapter(), - }, - remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, - }, + instanceOptions: getInstanceOptions(), }); expect( @@ -322,18 +291,7 @@ describe('Wallet', () => { describe('ConnectivityController', () => { it('reports online connectivity status', () => { const wallet = new Wallet({ - instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, - storageService: { - storage: new InMemoryStorageAdapter(), - }, - remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, - }, + instanceOptions: getInstanceOptions(), }); expect(wallet.state.ConnectivityController.connectivityStatus).toBe( @@ -362,18 +320,7 @@ describe('Wallet', () => { vault, }, }, - instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, - storageService: { - storage: new InMemoryStorageAdapter(), - }, - remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, - }, + instanceOptions: getInstanceOptions(), }); await wallet.messenger.call( @@ -451,14 +398,8 @@ describe('Wallet', () => { it('routes injected instanceOptions through to the controller', async () => { const wallet = new Wallet({ instanceOptions: { - connectivityController: { - connectivityAdapter: new AlwaysOnlineAdapter(), - }, - networkController: { - infuraProjectId: 'fake-infura-project-id', - }, + ...getInstanceOptions(), keyringController: { encryptor: new MockEncryptor() }, - storageService: { storage: new InMemoryStorageAdapter() }, remoteFeatureFlagController: { clientConfigApiService: { fetchRemoteFeatureFlags: async (): Promise<{ @@ -484,4 +425,15 @@ describe('Wallet', () => { ).toStrictEqual({ testFlag: true }); }); }); + + describe('ClaimsController', () => { + it('is wired and exposes its state on the wallet messenger', async () => { + const wallet = await setupWallet(); + const { messenger } = wallet; + + expect(messenger.call('ClaimsController:getState')).toStrictEqual( + getDefaultClaimsControllerState(), + ); + }); + }); }); diff --git a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts new file mode 100644 index 00000000000..023043c70e1 --- /dev/null +++ b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts @@ -0,0 +1,171 @@ +import { + ClaimsController, + getDefaultClaimsControllerState, +} from '@metamask/claims-controller'; +import { Messenger } from '@metamask/messenger'; + +import { defaultConfigurations } from '../../defaults.js'; +import type { + DefaultActions, + DefaultEvents, + RootMessenger, +} from '../../defaults.js'; +import { claimsController } from './claims-controller.js'; +import { claimsService } from '../claims-service/claims-service.js'; +import type { ClaimsServiceInstanceOptions } from '../claims-service/types.js'; +import { Env } from '@metamask/claims-controller'; + +const CLAIMS_SERVICE_OPTIONS: ClaimsServiceInstanceOptions = { + env: Env.DEV, + fetchFunction: globalThis.fetch, +}; + +type ActionHandler = (...args: unknown[]) => unknown; + +type AnyMessenger = Messenger; + +/** + * Creates a root messenger for use in tests. + * + * @returns A root messenger. + */ +function getRootMessenger(): RootMessenger { + return new Messenger({ namespace: 'Root' }); +} + +function registerActionHandler( + parent: RootMessenger, + namespace: string, + actionType: string, + handler: ActionHandler, +): void { + const messenger = new Messenger({ + namespace, + parent: parent as unknown as AnyMessenger, + }); + + ( + messenger as unknown as { + registerActionHandler(type: string, handler: ActionHandler): void; + } + ).registerActionHandler(actionType, handler); +} + +describe('claimsController', () => { + it('is registered as a default initialization configuration', () => { + expect(Object.values(defaultConfigurations)).toContain(claimsController); + }); + + it('initializes a ClaimsController with default state', () => { + const messenger = claimsController.getMessenger(getRootMessenger()); + + const instance = claimsController.init({ + state: undefined, + messenger, + options: {}, + }); + + expect(instance).toBeInstanceOf(ClaimsController); + expect(instance.state).toStrictEqual(getDefaultClaimsControllerState()); + }); + + it('merges provided state over the defaults', () => { + const messenger = claimsController.getMessenger(getRootMessenger()); + + const instance = claimsController.init({ + state: { drafts: [{ id: 'draft-1' }] }, + messenger, + options: {}, + }); + + expect(instance.state.drafts).toStrictEqual([{ id: 'draft-1' }]); + }); + + it('delegates only the ClaimsService and Keyring actions it uses', () => { + const rootMessenger = getRootMessenger(); + const delegateSpy = jest.spyOn(rootMessenger, 'delegate'); + + claimsController.getMessenger(rootMessenger); + + expect(delegateSpy).toHaveBeenCalledWith({ + messenger: expect.any(Messenger), + actions: [ + 'ClaimsService:fetchClaimsConfigurations', + 'ClaimsService:getRequestHeaders', + 'ClaimsService:getClaimsApiUrl', + 'ClaimsService:generateMessageForClaimSignature', + 'ClaimsService:getClaims', + 'KeyringController:signPersonalMessage', + ], + }); + }); + + it('exposes its state through the root messenger', () => { + const rootMessenger = getRootMessenger(); + const messenger = claimsController.getMessenger(rootMessenger); + + claimsController.init({ + state: undefined, + messenger, + options: {}, + }); + + expect(rootMessenger.call('ClaimsController:getState')).toStrictEqual( + getDefaultClaimsControllerState(), + ); + }); + + it('routes ClaimsService actions when both units are initialized', async () => { + const rootMessenger = getRootMessenger(); + registerActionHandler( + rootMessenger, + 'AuthenticationController', + 'AuthenticationController:getBearerToken', + jest.fn().mockResolvedValue('test-token'), + ); + + const serviceMessenger = claimsService.getMessenger(rootMessenger); + claimsService.init({ + state: undefined, + messenger: serviceMessenger, + options: CLAIMS_SERVICE_OPTIONS, + }); + + const controllerMessenger = claimsController.getMessenger(rootMessenger); + claimsController.init({ + state: undefined, + messenger: controllerMessenger, + options: {}, + }); + + const config = await rootMessenger.call( + 'ClaimsController:getSubmitClaimConfig', + { + chainId: '0x1', + email: 'test@test.com', + impactedWalletAddress: '0x123', + impactedTxHash: '0x123', + reimbursementWalletAddress: '0x456', + description: 'test description', + signature: + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12', + }, + ); + + expect(config).toStrictEqual({ + data: { + chainId: '0x1', + email: 'test@test.com', + impactedWalletAddress: '0x123', + impactedTxHash: '0x123', + reimbursementWalletAddress: '0x456', + description: 'test description', + signature: + '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12', + }, + headers: { Authorization: 'Bearer test-token' }, + method: 'POST', + url: 'https://claims.dev-api.cx.metamask.io/claims', + }); + }); +}); diff --git a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts new file mode 100644 index 00000000000..f4d30796ffc --- /dev/null +++ b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts @@ -0,0 +1,39 @@ +import { + ClaimsController, + type ClaimsControllerMessenger, +} from '@metamask/claims-controller'; +import { Messenger } from '@metamask/messenger'; + +import type { InitializationConfiguration } from '../../types.js'; + +export const claimsController: InitializationConfiguration< + ClaimsController, + ClaimsControllerMessenger +> = { + name: 'ClaimsController', + init: ({ state, messenger }) => + new ClaimsController({ + messenger, + state, + }), + getMessenger: (parent) => { + const messenger: ClaimsControllerMessenger = new Messenger({ + namespace: 'ClaimsController', + parent, + }); + + parent.delegate({ + messenger, + actions: [ + 'ClaimsService:fetchClaimsConfigurations', + 'ClaimsService:getRequestHeaders', + 'ClaimsService:getClaimsApiUrl', + 'ClaimsService:generateMessageForClaimSignature', + 'ClaimsService:getClaims', + 'KeyringController:signPersonalMessage', + ], + }); + + return messenger; + }, +}; diff --git a/packages/wallet/src/initialization/instances/claims-controller/types.ts b/packages/wallet/src/initialization/instances/claims-controller/types.ts new file mode 100644 index 00000000000..1f02d73880d --- /dev/null +++ b/packages/wallet/src/initialization/instances/claims-controller/types.ts @@ -0,0 +1 @@ +export type ClaimsControllerInstanceOptions = Record; diff --git a/packages/wallet/src/initialization/instances/claims-service/claims-service.test.ts b/packages/wallet/src/initialization/instances/claims-service/claims-service.test.ts new file mode 100644 index 00000000000..90de61a19f8 --- /dev/null +++ b/packages/wallet/src/initialization/instances/claims-service/claims-service.test.ts @@ -0,0 +1,122 @@ +import { ClaimsService } from '@metamask/claims-controller'; +import { Env } from '@metamask/claims-controller'; +import { Messenger } from '@metamask/messenger'; + +import { defaultConfigurations } from '../../defaults.js'; +import type { + DefaultActions, + DefaultEvents, + RootMessenger, +} from '../../defaults.js'; +import { claimsService } from './claims-service.js'; +import type { ClaimsServiceInstanceOptions } from './types.js'; + +const REQUIRED_OPTIONS: ClaimsServiceInstanceOptions = { + env: Env.DEV, + fetchFunction: globalThis.fetch, +}; + +type ActionHandler = (...args: unknown[]) => unknown; + +type AnyMessenger = Messenger; + +/** + * Creates a root messenger for use in tests. + * + * @returns A root messenger. + */ +function getRootMessenger(): RootMessenger { + return new Messenger({ namespace: 'Root' }); +} + +function registerActionHandler( + parent: RootMessenger, + namespace: string, + actionType: string, + handler: ActionHandler, +): void { + const messenger = new Messenger({ + namespace, + parent: parent as unknown as AnyMessenger, + }); + + ( + messenger as unknown as { + registerActionHandler(type: string, handler: ActionHandler): void; + } + ).registerActionHandler(actionType, handler); +} + +describe('claimsService', () => { + it('is registered as a default initialization configuration', () => { + expect(Object.values(defaultConfigurations)).toContain(claimsService); + }); + + it('initializes a ClaimsService', () => { + const messenger = claimsService.getMessenger(getRootMessenger()); + + const instance = claimsService.init({ + state: undefined, + messenger, + options: REQUIRED_OPTIONS, + }); + + expect(instance).toBeInstanceOf(ClaimsService); + }); + + it('forwards env and fetchFunction to the service', async () => { + const rootMessenger = getRootMessenger(); + const mockGetBearerToken = jest.fn().mockResolvedValue('test-token'); + registerActionHandler( + rootMessenger, + 'AuthenticationController', + 'AuthenticationController:getBearerToken', + mockGetBearerToken, + ); + + const messenger = claimsService.getMessenger(rootMessenger); + claimsService.init({ + state: undefined, + messenger, + options: REQUIRED_OPTIONS, + }); + + const headers = await rootMessenger.call('ClaimsService:getRequestHeaders'); + + expect(headers).toStrictEqual({ Authorization: 'Bearer test-token' }); + expect(mockGetBearerToken).toHaveBeenCalledTimes(1); + }); + + it('delegates only AuthenticationController:getBearerToken', () => { + const rootMessenger = getRootMessenger(); + const delegateSpy = jest.spyOn(rootMessenger, 'delegate'); + + claimsService.getMessenger(rootMessenger); + + expect(delegateSpy).toHaveBeenCalledWith({ + messenger: expect.any(Messenger), + actions: ['AuthenticationController:getBearerToken'], + }); + }); + + it('exposes service actions through the root messenger', async () => { + const rootMessenger = getRootMessenger(); + registerActionHandler( + rootMessenger, + 'AuthenticationController', + 'AuthenticationController:getBearerToken', + jest.fn().mockResolvedValue('test-token'), + ); + + const messenger = claimsService.getMessenger(rootMessenger); + claimsService.init({ + state: undefined, + messenger, + options: REQUIRED_OPTIONS, + }); + + expect(rootMessenger.call('ClaimsService:getClaimsApiUrl')).toBe( + 'https://claims.dev-api.cx.metamask.io', + ); + }); +}); diff --git a/packages/wallet/src/initialization/instances/claims-service/claims-service.ts b/packages/wallet/src/initialization/instances/claims-service/claims-service.ts new file mode 100644 index 00000000000..52109154099 --- /dev/null +++ b/packages/wallet/src/initialization/instances/claims-service/claims-service.ts @@ -0,0 +1,33 @@ +import { ClaimsService } from '@metamask/claims-controller'; +import type { ClaimsServiceMessenger } from '@metamask/claims-controller'; +import { Messenger } from '@metamask/messenger'; + +import type { InitializationConfiguration } from '../../types.js'; + +export type { ClaimsServiceInstanceOptions } from './types.js'; + +export const claimsService: InitializationConfiguration< + ClaimsService, + ClaimsServiceMessenger +> = { + name: 'ClaimsService', + init: ({ messenger, options }) => + new ClaimsService({ + messenger, + env: options.env, + fetchFunction: options.fetchFunction, + }), + getMessenger: (parent) => { + const messenger: ClaimsServiceMessenger = new Messenger({ + namespace: 'ClaimsService', + parent, + }); + + parent.delegate({ + messenger, + actions: ['AuthenticationController:getBearerToken'], + }); + + return messenger; + }, +}; diff --git a/packages/wallet/src/initialization/instances/claims-service/types.ts b/packages/wallet/src/initialization/instances/claims-service/types.ts new file mode 100644 index 00000000000..bd49871ffa3 --- /dev/null +++ b/packages/wallet/src/initialization/instances/claims-service/types.ts @@ -0,0 +1,14 @@ +import type { Env } from '@metamask/claims-controller'; + +export type ClaimsServiceInstanceOptions = { + /** + * Claims API environment. Supplied by the consumer per build flavor + * (dev, uat, production). + */ + env: Env; + + /** + * Platform fetch implementation used for Claims API requests. + */ + fetchFunction: typeof fetch; +}; diff --git a/packages/wallet/src/initialization/instances/index.ts b/packages/wallet/src/initialization/instances/index.ts index 858e36e9769..33f6774ef1a 100644 --- a/packages/wallet/src/initialization/instances/index.ts +++ b/packages/wallet/src/initialization/instances/index.ts @@ -1,6 +1,8 @@ export { accountsController } from './accounts-controller/accounts-controller.js'; export { addressBookController } from './address-book-controller/address-book-controller.js'; export { approvalController } from './approval-controller/approval-controller.js'; +export { claimsController } from './claims-controller/claims-controller.js'; +export { claimsService } from './claims-service/claims-service.js'; export { connectivityController } from './connectivity-controller/connectivity-controller.js'; export { keyringController } from './keyring-controller/keyring-controller.js'; export { networkController } from './network-controller/network-controller.js'; diff --git a/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts b/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts index 4870eca7c18..c42fe2c47e5 100644 --- a/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts +++ b/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts @@ -1,4 +1,5 @@ import { Messenger } from '@metamask/messenger'; +import { Env } from '@metamask/claims-controller'; import { InMemoryStorageAdapter } from '@metamask/storage-service'; import { TransactionController } from '@metamask/transaction-controller'; @@ -122,6 +123,10 @@ function getInstanceOptions(): WalletOptions['instanceOptions'] { storage: new InMemoryStorageAdapter(), }, remoteFeatureFlagController: REMOTE_FEATURE_FLAG_OPTIONS, + claimsService: { + env: Env.DEV, + fetchFunction: globalThis.fetch, + }, }; } diff --git a/packages/wallet/src/types.ts b/packages/wallet/src/types.ts index 46bb9c5993c..310d8404544 100644 --- a/packages/wallet/src/types.ts +++ b/packages/wallet/src/types.ts @@ -6,6 +6,8 @@ import type { RootMessenger, } from './initialization/defaults.js'; import type { ApprovalControllerInstanceOptions } from './initialization/instances/approval-controller/types.js'; +import type { ClaimsControllerInstanceOptions } from './initialization/instances/claims-controller/types.js'; +import type { ClaimsServiceInstanceOptions } from './initialization/instances/claims-service/types.js'; import type { ConnectivityControllerInstanceOptions } from './initialization/instances/connectivity-controller/types.js'; import type { KeyringControllerInstanceOptions } from './initialization/instances/keyring-controller/types.js'; import type { NetworkControllerInstanceOptions } from './initialization/instances/network-controller/types.js'; @@ -28,6 +30,8 @@ export type WalletOptions = { export type InstanceSpecificOptions = { approvalController?: ApprovalControllerInstanceOptions; + claimsService: ClaimsServiceInstanceOptions; + claimsController?: ClaimsControllerInstanceOptions; connectivityController: ConnectivityControllerInstanceOptions; keyringController?: KeyringControllerInstanceOptions; networkController: NetworkControllerInstanceOptions; diff --git a/packages/wallet/tsconfig.build.json b/packages/wallet/tsconfig.build.json index d4caa50ec1c..7fcfeb7219d 100644 --- a/packages/wallet/tsconfig.build.json +++ b/packages/wallet/tsconfig.build.json @@ -9,6 +9,7 @@ { "path": "../accounts-controller/tsconfig.build.json" }, { "path": "../address-book-controller/tsconfig.build.json" }, { "path": "../approval-controller/tsconfig.build.json" }, + { "path": "../claims-controller/tsconfig.build.json" }, { "path": "../base-controller/tsconfig.build.json" }, { "path": "../connectivity-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, diff --git a/packages/wallet/tsconfig.json b/packages/wallet/tsconfig.json index 62f5137fc5b..3eeaa124483 100644 --- a/packages/wallet/tsconfig.json +++ b/packages/wallet/tsconfig.json @@ -13,6 +13,9 @@ { "path": "../approval-controller" }, + { + "path": "../claims-controller" + }, { "path": "../base-controller" }, diff --git a/yarn.lock b/yarn.lock index 8fc98d15567..a63362bd8eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6091,7 +6091,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/claims-controller@workspace:packages/claims-controller": +"@metamask/claims-controller@npm:^0.5.3, @metamask/claims-controller@workspace:^, @metamask/claims-controller@workspace:packages/claims-controller": version: 0.0.0-use.local resolution: "@metamask/claims-controller@workspace:packages/claims-controller" dependencies: @@ -9101,6 +9101,7 @@ __metadata: "@inquirer/password": "npm:^5.1.1" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" + "@metamask/claims-controller": "workspace:^" "@metamask/remote-feature-flag-controller": "npm:^4.2.2" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/scure-bip39": "npm:^2.1.1" @@ -9168,6 +9169,7 @@ __metadata: "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" "@metamask/browser-passworder": "npm:^6.0.0" + "@metamask/claims-controller": "npm:^0.5.3" "@metamask/connectivity-controller": "npm:^0.3.0" "@metamask/controller-utils": "npm:^12.3.0" "@metamask/keyring-controller": "npm:^27.1.0" From 4285b444eb2c866c88fcc62b9590d5edc5f32a3f Mon Sep 17 00:00:00 2001 From: Tuna Date: Wed, 22 Jul 2026 15:10:33 +0700 Subject: [PATCH 2/9] docs(wallet): add Claims initialization changelog entry Co-authored-by: Cursor --- packages/wallet/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index b39b7418e51..f1efa08c41d 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -9,6 +9,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- **BREAKING:** Wire `ClaimsService` and `ClaimsController` into the default wallet initialization ([#9588](https://github.com/MetaMask/core/pull/9588)) + - Passing `instanceOptions.claimsService.env` is now required. + - Passing `instanceOptions.claimsService.fetchFunction` is now required. + - `ClaimsService` delegates `AuthenticationController:getBearerToken`; hosts must register `AuthenticationController` on the supplied root messenger before authenticated Claims API calls succeed. - **BREAKING** Wire `SeedlessOnboardingController` and `PasskeyController` into the default wallet initialization ([#9533](https://github.com/MetaMask/core/pull/9533)) ### Changed From be1ebc75a1f1ff643cb52087a51ea13880eeb703 Mon Sep 17 00:00:00 2001 From: Tuna Date: Wed, 22 Jul 2026 16:25:58 +0700 Subject: [PATCH 3/9] refactor: claim controller export env --- README.md | 1 - packages/wallet-cli/package.json | 1 - packages/wallet-cli/src/daemon/wallet-factory.test.ts | 2 +- packages/wallet-cli/src/daemon/wallet-factory.ts | 2 +- packages/wallet-cli/tsconfig.build.json | 1 - packages/wallet-cli/tsconfig.json | 3 --- packages/wallet/CHANGELOG.md | 1 + packages/wallet/src/index.ts | 1 + yarn.lock | 3 +-- 9 files changed, 5 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c03de120027..dedbab16583 100644 --- a/README.md +++ b/README.md @@ -659,7 +659,6 @@ linkStyle default opacity:0.5 wallet --> storage_service; wallet --> transaction_controller; wallet_cli --> base_controller; - wallet_cli --> claims_controller; wallet_cli --> remote_feature_flag_controller; wallet_cli --> storage_service; wallet_cli --> wallet; diff --git a/packages/wallet-cli/package.json b/packages/wallet-cli/package.json index 1cbadf61908..2023edb1647 100644 --- a/packages/wallet-cli/package.json +++ b/packages/wallet-cli/package.json @@ -49,7 +49,6 @@ "@inquirer/confirm": "^6.0.11", "@inquirer/password": "^5.1.1", "@metamask/base-controller": "^9.1.0", - "@metamask/claims-controller": "workspace:^", "@metamask/remote-feature-flag-controller": "^4.2.2", "@metamask/rpc-errors": "^7.0.2", "@metamask/scure-bip39": "^2.1.1", diff --git a/packages/wallet-cli/src/daemon/wallet-factory.test.ts b/packages/wallet-cli/src/daemon/wallet-factory.test.ts index f51b0e626f0..7f9fb949724 100644 --- a/packages/wallet-cli/src/daemon/wallet-factory.test.ts +++ b/packages/wallet-cli/src/daemon/wallet-factory.test.ts @@ -1,8 +1,8 @@ -import { Env } from '@metamask/claims-controller'; import { ClientConfigApiService } from '@metamask/remote-feature-flag-controller'; import { InMemoryStorageAdapter } from '@metamask/storage-service'; import { AlwaysOnlineAdapter, + Env, importSecretRecoveryPhrase, Wallet, } from '@metamask/wallet'; diff --git a/packages/wallet-cli/src/daemon/wallet-factory.ts b/packages/wallet-cli/src/daemon/wallet-factory.ts index 98845c7ffb9..c3c54518fa2 100644 --- a/packages/wallet-cli/src/daemon/wallet-factory.ts +++ b/packages/wallet-cli/src/daemon/wallet-factory.ts @@ -1,4 +1,3 @@ -import { Env } from '@metamask/claims-controller'; import { ClientConfigApiService, ClientType, @@ -9,6 +8,7 @@ import { InMemoryStorageAdapter } from '@metamask/storage-service'; import type { Json } from '@metamask/utils'; import { AlwaysOnlineAdapter, + Env, importSecretRecoveryPhrase, Wallet, } from '@metamask/wallet'; diff --git a/packages/wallet-cli/tsconfig.build.json b/packages/wallet-cli/tsconfig.build.json index c2d3d2accf6..934a11754a4 100644 --- a/packages/wallet-cli/tsconfig.build.json +++ b/packages/wallet-cli/tsconfig.build.json @@ -7,7 +7,6 @@ }, "references": [ { "path": "../base-controller/tsconfig.build.json" }, - { "path": "../claims-controller/tsconfig.build.json" }, { "path": "../remote-feature-flag-controller/tsconfig.build.json" }, { "path": "../storage-service/tsconfig.build.json" }, { "path": "../wallet/tsconfig.build.json" } diff --git a/packages/wallet-cli/tsconfig.json b/packages/wallet-cli/tsconfig.json index a9fb1ecd172..d0fd77d3457 100644 --- a/packages/wallet-cli/tsconfig.json +++ b/packages/wallet-cli/tsconfig.json @@ -7,9 +7,6 @@ { "path": "../base-controller" }, - { - "path": "../claims-controller" - }, { "path": "../remote-feature-flag-controller" }, diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index f1efa08c41d..54b8808c411 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Passing `instanceOptions.claimsService.env` is now required. - Passing `instanceOptions.claimsService.fetchFunction` is now required. - `ClaimsService` delegates `AuthenticationController:getBearerToken`; hosts must register `AuthenticationController` on the supplied root messenger before authenticated Claims API calls succeed. + - Re-exports `Env` from `@metamask/claims-controller` for consumers setting `claimsService.env`. - **BREAKING** Wire `SeedlessOnboardingController` and `PasskeyController` into the default wallet initialization ([#9533](https://github.com/MetaMask/core/pull/9533)) ### Changed diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index 50e9b004160..b958d047d5f 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -1,3 +1,4 @@ +export { Env } from '@metamask/claims-controller'; export { Wallet } from './Wallet.js'; export { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js'; export { importSecretRecoveryPhrase } from './utilities.js'; diff --git a/yarn.lock b/yarn.lock index a63362bd8eb..8c394dcab88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6091,7 +6091,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/claims-controller@npm:^0.5.3, @metamask/claims-controller@workspace:^, @metamask/claims-controller@workspace:packages/claims-controller": +"@metamask/claims-controller@npm:^0.5.3, @metamask/claims-controller@workspace:packages/claims-controller": version: 0.0.0-use.local resolution: "@metamask/claims-controller@workspace:packages/claims-controller" dependencies: @@ -9101,7 +9101,6 @@ __metadata: "@inquirer/password": "npm:^5.1.1" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/claims-controller": "workspace:^" "@metamask/remote-feature-flag-controller": "npm:^4.2.2" "@metamask/rpc-errors": "npm:^7.0.2" "@metamask/scure-bip39": "npm:^2.1.1" From 84750e09ba2564d20909013af957760866751997 Mon Sep 17 00:00:00 2001 From: Tuna Date: Wed, 22 Jul 2026 20:37:25 +0700 Subject: [PATCH 4/9] refactor(wallet): nest ClaimsService under claims-controller init Colocate ClaimsService with ClaimsController so a single CODEOWNERS initializationPath covers both units, and revert multi-path ownership. Co-authored-by: Cursor --- .github/CODEOWNERS | 1 - codeowners.ts | 38 +++++++++---------- .../claims-controller.test.ts | 7 ++-- .../claims-service.test.ts | 0 .../claims-service.ts | 0 .../instances/claims-controller/types.ts | 15 ++++++++ .../instances/claims-service/types.ts | 14 ------- .../src/initialization/instances/index.ts | 2 +- packages/wallet/src/types.ts | 6 ++- 9 files changed, 43 insertions(+), 40 deletions(-) rename packages/wallet/src/initialization/instances/{claims-service => claims-controller}/claims-service.test.ts (100%) rename packages/wallet/src/initialization/instances/{claims-service => claims-controller}/claims-service.ts (100%) delete mode 100644 packages/wallet/src/initialization/instances/claims-service/types.ts diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 02a7c744ed9..f7735e1d3c9 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -150,7 +150,6 @@ /packages/wallet/src/initialization/instances/address-book-controller/ @MetaMask/confirmations /packages/wallet/src/initialization/instances/approval-controller/ @MetaMask/confirmations /packages/wallet/src/initialization/instances/claims-controller/ @MetaMask/web3auth -/packages/wallet/src/initialization/instances/claims-service/ @MetaMask/web3auth /packages/wallet/src/initialization/instances/connectivity-controller/ @MetaMask/core-platform /packages/wallet/src/initialization/instances/keyring-controller/ @MetaMask/accounts-engineers @MetaMask/core-platform /packages/wallet/src/initialization/instances/passkey-controller/ @MetaMask/web3auth diff --git a/codeowners.ts b/codeowners.ts index 16ec97facd4..d9cbc200f35 100644 --- a/codeowners.ts +++ b/codeowners.ts @@ -14,12 +14,12 @@ type PackageInfo = { teams: string[]; /** - * The package's directory name(s) under - * `/packages/wallet/src/initialization/instances`, used to generate its rule(s) + * The package's directory name under + * `/packages/wallet/src/initialization/instances`, used to generate its rule * in the "Initialization" section. Omit this if the package has not been * added to the Wallet Library yet. */ - initializationPaths?: string[]; + initializationPath?: string; }; /** @@ -34,11 +34,11 @@ const PACKAGES: Record = { }, 'accounts-controller': { teams: ['@MetaMask/accounts-engineers'], - initializationPaths: ['accounts-controller'], + initializationPath: 'accounts-controller', }, 'address-book-controller': { teams: ['@MetaMask/confirmations'], - initializationPaths: ['address-book-controller'], + initializationPath: 'address-book-controller', }, 'ai-controllers': { teams: ['@MetaMask/social-ai'], @@ -57,7 +57,7 @@ const PACKAGES: Record = { }, 'approval-controller': { teams: ['@MetaMask/confirmations'], - initializationPaths: ['approval-controller'], + initializationPath: 'approval-controller', }, 'assets-controller': { teams: ['@MetaMask/metamask-assets'], @@ -98,7 +98,7 @@ const PACKAGES: Record = { }, 'claims-controller': { teams: ['@MetaMask/web3auth'], - initializationPaths: ['claims-controller', 'claims-service'], + initializationPath: 'claims-controller', }, 'client-controller': { teams: [ @@ -121,7 +121,7 @@ const PACKAGES: Record = { }, 'connectivity-controller': { teams: ['@MetaMask/core-platform'], - initializationPaths: ['connectivity-controller'], + initializationPath: 'connectivity-controller', }, 'controller-utils': { teams: ['@MetaMask/core-platform'], @@ -183,7 +183,7 @@ const PACKAGES: Record = { }, 'keyring-controller': { teams: ['@MetaMask/accounts-engineers', '@MetaMask/core-platform'], - initializationPaths: ['keyring-controller'], + initializationPath: 'keyring-controller', }, 'local-node-utils': { teams: [ @@ -249,7 +249,7 @@ const PACKAGES: Record = { }, 'passkey-controller': { teams: ['@MetaMask/web3auth'], - initializationPaths: ['passkey-controller'], + initializationPath: 'passkey-controller', }, 'permission-controller': { teams: ['@MetaMask/core-platform'], @@ -293,14 +293,14 @@ const PACKAGES: Record = { '@MetaMask/mobile-platform', '@MetaMask/core-platform', ], - initializationPaths: ['remote-feature-flag-controller'], + initializationPath: 'remote-feature-flag-controller', }, 'sample-controllers': { teams: ['@MetaMask/core-platform'], }, 'seedless-onboarding-controller': { teams: ['@MetaMask/web3auth'], - initializationPaths: ['seedless-onboarding-controller'], + initializationPath: 'seedless-onboarding-controller', }, 'selected-network-controller': { teams: ['@MetaMask/core-platform'], @@ -343,14 +343,14 @@ const PACKAGES: Record = { '@MetaMask/mobile-platform', '@MetaMask/core-platform', ], - initializationPaths: ['storage-service'], + initializationPath: 'storage-service', }, 'subscription-controller': { teams: ['@MetaMask/web3auth'], }, 'transaction-controller': { teams: ['@MetaMask/confirmations'], - initializationPaths: ['transaction-controller'], + initializationPath: 'transaction-controller', }, 'transaction-pay-controller': { teams: ['@MetaMask/confirmations'], @@ -604,14 +604,14 @@ function buildInitializationSection(): CodeownersSection { return { title: 'Initialization', rules: Object.keys(PACKAGES) - .filter((name) => PACKAGES[name].initializationPaths !== undefined) + .filter((name) => PACKAGES[name].initializationPath !== undefined) .sort() - .flatMap((name) => { - const { teams, initializationPaths } = PACKAGES[name]; - return (initializationPaths ?? []).map((initializationPath) => ({ + .map((name) => { + const { teams, initializationPath } = PACKAGES[name]; + return { pattern: `/packages/wallet/src/initialization/instances/${initializationPath}/`, owners: teams, - })); + }; }), }; } diff --git a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts index 023043c70e1..f6bada1e21e 100644 --- a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts +++ b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts @@ -10,11 +10,12 @@ import type { DefaultEvents, RootMessenger, } from '../../defaults.js'; -import { claimsController } from './claims-controller.js'; -import { claimsService } from '../claims-service/claims-service.js'; -import type { ClaimsServiceInstanceOptions } from '../claims-service/types.js'; import { Env } from '@metamask/claims-controller'; +import { claimsController } from './claims-controller.js'; +import { claimsService } from './claims-service.js'; +import type { ClaimsServiceInstanceOptions } from './types.js'; + const CLAIMS_SERVICE_OPTIONS: ClaimsServiceInstanceOptions = { env: Env.DEV, fetchFunction: globalThis.fetch, diff --git a/packages/wallet/src/initialization/instances/claims-service/claims-service.test.ts b/packages/wallet/src/initialization/instances/claims-controller/claims-service.test.ts similarity index 100% rename from packages/wallet/src/initialization/instances/claims-service/claims-service.test.ts rename to packages/wallet/src/initialization/instances/claims-controller/claims-service.test.ts diff --git a/packages/wallet/src/initialization/instances/claims-service/claims-service.ts b/packages/wallet/src/initialization/instances/claims-controller/claims-service.ts similarity index 100% rename from packages/wallet/src/initialization/instances/claims-service/claims-service.ts rename to packages/wallet/src/initialization/instances/claims-controller/claims-service.ts diff --git a/packages/wallet/src/initialization/instances/claims-controller/types.ts b/packages/wallet/src/initialization/instances/claims-controller/types.ts index 1f02d73880d..8a1e5f43590 100644 --- a/packages/wallet/src/initialization/instances/claims-controller/types.ts +++ b/packages/wallet/src/initialization/instances/claims-controller/types.ts @@ -1 +1,16 @@ +import type { Env } from '@metamask/claims-controller'; + export type ClaimsControllerInstanceOptions = Record; + +export type ClaimsServiceInstanceOptions = { + /** + * Claims API environment. Supplied by the consumer per build flavor + * (dev, uat, production). + */ + env: Env; + + /** + * Platform fetch implementation used for Claims API requests. + */ + fetchFunction: typeof fetch; +}; diff --git a/packages/wallet/src/initialization/instances/claims-service/types.ts b/packages/wallet/src/initialization/instances/claims-service/types.ts deleted file mode 100644 index bd49871ffa3..00000000000 --- a/packages/wallet/src/initialization/instances/claims-service/types.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Env } from '@metamask/claims-controller'; - -export type ClaimsServiceInstanceOptions = { - /** - * Claims API environment. Supplied by the consumer per build flavor - * (dev, uat, production). - */ - env: Env; - - /** - * Platform fetch implementation used for Claims API requests. - */ - fetchFunction: typeof fetch; -}; diff --git a/packages/wallet/src/initialization/instances/index.ts b/packages/wallet/src/initialization/instances/index.ts index 33f6774ef1a..7be0b37a6b7 100644 --- a/packages/wallet/src/initialization/instances/index.ts +++ b/packages/wallet/src/initialization/instances/index.ts @@ -2,7 +2,7 @@ export { accountsController } from './accounts-controller/accounts-controller.js export { addressBookController } from './address-book-controller/address-book-controller.js'; export { approvalController } from './approval-controller/approval-controller.js'; export { claimsController } from './claims-controller/claims-controller.js'; -export { claimsService } from './claims-service/claims-service.js'; +export { claimsService } from './claims-controller/claims-service.js'; export { connectivityController } from './connectivity-controller/connectivity-controller.js'; export { keyringController } from './keyring-controller/keyring-controller.js'; export { networkController } from './network-controller/network-controller.js'; diff --git a/packages/wallet/src/types.ts b/packages/wallet/src/types.ts index 310d8404544..49f49ff0ba6 100644 --- a/packages/wallet/src/types.ts +++ b/packages/wallet/src/types.ts @@ -6,8 +6,10 @@ import type { RootMessenger, } from './initialization/defaults.js'; import type { ApprovalControllerInstanceOptions } from './initialization/instances/approval-controller/types.js'; -import type { ClaimsControllerInstanceOptions } from './initialization/instances/claims-controller/types.js'; -import type { ClaimsServiceInstanceOptions } from './initialization/instances/claims-service/types.js'; +import type { + ClaimsControllerInstanceOptions, + ClaimsServiceInstanceOptions, +} from './initialization/instances/claims-controller/types.js'; import type { ConnectivityControllerInstanceOptions } from './initialization/instances/connectivity-controller/types.js'; import type { KeyringControllerInstanceOptions } from './initialization/instances/keyring-controller/types.js'; import type { NetworkControllerInstanceOptions } from './initialization/instances/network-controller/types.js'; From 7dc5df25f432440bf01a4c1d9a546b81dd671728 Mon Sep 17 00:00:00 2001 From: Tuna Date: Wed, 22 Jul 2026 20:38:37 +0700 Subject: [PATCH 5/9] refactor(wallet): re-export claims Env as ClaimsEnv Match the SubscriptionEnv naming pattern so wallet consumers can set claimsService.env without colliding with other Env enums. Co-authored-by: Cursor --- packages/wallet-cli/src/daemon/wallet-factory.test.ts | 4 ++-- packages/wallet-cli/src/daemon/wallet-factory.ts | 4 ++-- packages/wallet/CHANGELOG.md | 2 +- packages/wallet/src/index.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/wallet-cli/src/daemon/wallet-factory.test.ts b/packages/wallet-cli/src/daemon/wallet-factory.test.ts index 7f9fb949724..000c62c0708 100644 --- a/packages/wallet-cli/src/daemon/wallet-factory.test.ts +++ b/packages/wallet-cli/src/daemon/wallet-factory.test.ts @@ -2,7 +2,7 @@ import { ClientConfigApiService } from '@metamask/remote-feature-flag-controller import { InMemoryStorageAdapter } from '@metamask/storage-service'; import { AlwaysOnlineAdapter, - Env, + ClaimsEnv, importSecretRecoveryPhrase, Wallet, } from '@metamask/wallet'; @@ -122,7 +122,7 @@ describe('createWallet', () => { expect(instanceOptions.transactionController?.disableSwaps).toBe(true); expect(instanceOptions.transactionController?.hooks).toStrictEqual({}); expect(instanceOptions.claimsService).toStrictEqual({ - env: Env.PRD, + env: ClaimsEnv.PRD, fetchFunction: globalThis.fetch, }); expect(ClientConfigApiService).toHaveBeenCalled(); diff --git a/packages/wallet-cli/src/daemon/wallet-factory.ts b/packages/wallet-cli/src/daemon/wallet-factory.ts index c3c54518fa2..8835aee5f2b 100644 --- a/packages/wallet-cli/src/daemon/wallet-factory.ts +++ b/packages/wallet-cli/src/daemon/wallet-factory.ts @@ -8,7 +8,7 @@ import { InMemoryStorageAdapter } from '@metamask/storage-service'; import type { Json } from '@metamask/utils'; import { AlwaysOnlineAdapter, - Env, + ClaimsEnv, importSecretRecoveryPhrase, Wallet, } from '@metamask/wallet'; @@ -120,7 +120,7 @@ function buildInstanceOptions( hooks: {}, }, claimsService: { - env: Env.PRD, + env: ClaimsEnv.PRD, fetchFunction: globalThis.fetch, }, }; diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index abff7a5c657..cc0d6c4c96f 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Passing `instanceOptions.claimsService.env` is now required. - Passing `instanceOptions.claimsService.fetchFunction` is now required. - `ClaimsService` delegates `AuthenticationController:getBearerToken`; hosts must register `AuthenticationController` on the supplied root messenger before authenticated Claims API calls succeed. - - Re-exports `Env` from `@metamask/claims-controller` for consumers setting `claimsService.env`. + - Re-exports `Env` from `@metamask/claims-controller` as `ClaimsEnv` for `instanceOptions.claimsService.env`. - **BREAKING** Wire `SeedlessOnboardingController` and `PasskeyController` into the default wallet initialization ([#9533](https://github.com/MetaMask/core/pull/9533)) ### Changed diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index b958d047d5f..c804ba31bb9 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -1,7 +1,7 @@ -export { Env } from '@metamask/claims-controller'; export { Wallet } from './Wallet.js'; export { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js'; export { importSecretRecoveryPhrase } from './utilities.js'; +export { Env as ClaimsEnv } from '@metamask/claims-controller'; export type { WalletOptions } from './types.js'; export type { DefaultActions, From 19de465d25e95a0d7fd593d456404c6578eedb12 Mon Sep 17 00:00:00 2001 From: Tuna Date: Wed, 22 Jul 2026 20:40:23 +0700 Subject: [PATCH 6/9] docs(wallet-cli): note claimsService instance options wiring Co-authored-by: Cursor --- packages/wallet-cli/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/wallet-cli/CHANGELOG.md b/packages/wallet-cli/CHANGELOG.md index 9e83315b448..cc128379245 100644 --- a/packages/wallet-cli/CHANGELOG.md +++ b/packages/wallet-cli/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Wire the `claimsService` slot in the daemon wallet's instance options (`ClaimsEnv.PRD` and `globalThis.fetch`), so the daemon initializes `ClaimsService` with the wallet's required configuration ([#9588](https://github.com/MetaMask/core/pull/9588)) - Wire the `transactionController` slot in the daemon wallet's instance options, so the daemon runs the `TransactionController` with an explicit CLI-appropriate configuration (swaps processing disabled, no client hooks) rather than relying on the controller's implicit defaults ([#9509](https://github.com/MetaMask/core/pull/9509)) - Add the `mm wallet unlock` command, which dispatches `KeyringController:submitPassword` over the daemon socket, allowing the keyring to be unlocked after a daemon start with no password or after a `mm daemon call KeyringController:setLocked` ([#8821](https://github.com/MetaMask/core/pull/8821)) - Add the `mm daemon list` command, which prints the messenger actions the running daemon can dispatch via `daemon call`, enumerated from the live messenger so the list cannot drift from what `call` accepts ([#9339](https://github.com/MetaMask/core/pull/9339)) From 8a31a85b66f89f4e673e80eb11a8f5b3b6fa22f5 Mon Sep 17 00:00:00 2001 From: Tuna Date: Thu, 23 Jul 2026 07:47:57 +0700 Subject: [PATCH 7/9] fix: lint --- packages/wallet/src/Wallet.test.ts | 2 +- .../instances/claims-controller/claims-controller.test.ts | 3 +-- .../transaction-controller/transaction-controller.test.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/wallet/src/Wallet.test.ts b/packages/wallet/src/Wallet.test.ts index 992709efe91..a5f13d99bf1 100644 --- a/packages/wallet/src/Wallet.test.ts +++ b/packages/wallet/src/Wallet.test.ts @@ -10,9 +10,9 @@ import { webcrypto } from 'crypto'; import MockEncryptor from '../../keyring-controller/tests/mocks/mockEncryptor.js'; import * as initializationModule from './initialization/initialization.js'; import { AlwaysOnlineAdapter } from './initialization/instances/connectivity-controller/always-online-adapter.js'; +import type { WalletOptions } from './types.js'; import { importSecretRecoveryPhrase } from './utilities.js'; import { Wallet } from './Wallet.js'; -import type { WalletOptions } from './types.js'; const TEST_SRP = 'test test test test test test test test test test test ball'; const TEST_PASSWORD = 'testpass'; diff --git a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts index f6bada1e21e..e39404adf5e 100644 --- a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts +++ b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.test.ts @@ -2,6 +2,7 @@ import { ClaimsController, getDefaultClaimsControllerState, } from '@metamask/claims-controller'; +import { Env } from '@metamask/claims-controller'; import { Messenger } from '@metamask/messenger'; import { defaultConfigurations } from '../../defaults.js'; @@ -10,8 +11,6 @@ import type { DefaultEvents, RootMessenger, } from '../../defaults.js'; -import { Env } from '@metamask/claims-controller'; - import { claimsController } from './claims-controller.js'; import { claimsService } from './claims-service.js'; import type { ClaimsServiceInstanceOptions } from './types.js'; diff --git a/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts b/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts index c42fe2c47e5..e08fd7c50cc 100644 --- a/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts +++ b/packages/wallet/src/initialization/instances/transaction-controller/transaction-controller.test.ts @@ -1,5 +1,5 @@ -import { Messenger } from '@metamask/messenger'; import { Env } from '@metamask/claims-controller'; +import { Messenger } from '@metamask/messenger'; import { InMemoryStorageAdapter } from '@metamask/storage-service'; import { TransactionController } from '@metamask/transaction-controller'; From 4446ad54684863c83e580fef6769262b347b97a2 Mon Sep 17 00:00:00 2001 From: Tuna Date: Thu, 23 Jul 2026 07:58:13 +0700 Subject: [PATCH 8/9] fix: changelog --- packages/wallet/CHANGELOG.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index 35b438de622..6d638d3dfb4 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [8.0.0] - ### Added - **BREAKING:** Wire `ClaimsService` and `ClaimsController` into the default wallet initialization ([#9588](https://github.com/MetaMask/core/pull/9588)) @@ -16,6 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Passing `instanceOptions.claimsService.fetchFunction` is now required. - `ClaimsService` delegates `AuthenticationController:getBearerToken`; hosts must register `AuthenticationController` on the supplied root messenger before authenticated Claims API calls succeed. - Re-exports `Env` from `@metamask/claims-controller` as `ClaimsEnv` for `instanceOptions.claimsService.env`. + +## [8.0.0] + +### Added + - **BREAKING:** Wire `GasFeeController` into the default wallet initialization ([#9527](https://github.com/MetaMask/core/pull/9527)) - Adds a required `instanceOptions.gasFeeController` option whose `clientId` (sent as `X-Client-Id` to the gas API) is required, so every client identifies itself; all other fields are optional and fall back to platform-agnostic defaults. - **BREAKING** Wire `SeedlessOnboardingController` and `PasskeyController` into the default wallet initialization ([#9533](https://github.com/MetaMask/core/pull/9533)) From 745fd3786290a390f8bcef34c9f5e7ed402f021e Mon Sep 17 00:00:00 2001 From: Tuna Date: Thu, 23 Jul 2026 08:19:17 +0700 Subject: [PATCH 9/9] fix: lint --- .../instances/claims-controller/claims-controller.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts index f4d30796ffc..8ebbf66a2fc 100644 --- a/packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts +++ b/packages/wallet/src/initialization/instances/claims-controller/claims-controller.ts @@ -1,7 +1,5 @@ -import { - ClaimsController, - type ClaimsControllerMessenger, -} from '@metamask/claims-controller'; +import { ClaimsController } from '@metamask/claims-controller'; +import type { ClaimsControllerMessenger } from '@metamask/claims-controller'; import { Messenger } from '@metamask/messenger'; import type { InitializationConfiguration } from '../../types.js';