diff --git a/README.md b/README.md index 0c4c21b1d2..125343e1c9 100644 --- a/README.md +++ b/README.md @@ -565,6 +565,9 @@ linkStyle default opacity:0.5 user_operation_controller --> polling_controller; user_operation_controller --> transaction_controller; user_operation_controller --> eth_block_tracker; + wallet --> base_controller; + wallet --> keyring_controller; + wallet --> messenger; ``` diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md index b518709c7b..889a107355 100644 --- a/packages/wallet/CHANGELOG.md +++ b/packages/wallet/CHANGELOG.md @@ -7,4 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Initial release ([#8838](https://github.com/MetaMask/core/pull/8838)) + [Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/wallet/jest.config.js b/packages/wallet/jest.config.js index ca08413339..571d3d50ca 100644 --- a/packages/wallet/jest.config.js +++ b/packages/wallet/jest.config.js @@ -18,9 +18,9 @@ module.exports = merge(baseConfig, { coverageThreshold: { global: { branches: 100, - functions: 100, - lines: 100, - statements: 100, + functions: 89.65, + lines: 95.77, + statements: 95.89, }, }, }); diff --git a/packages/wallet/package.json b/packages/wallet/package.json index e067b95767..1018f53f04 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -52,6 +52,14 @@ "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" }, + "dependencies": { + "@metamask/base-controller": "^9.1.0", + "@metamask/browser-passworder": "^6.0.0", + "@metamask/keyring-controller": "^25.5.0", + "@metamask/messenger": "^1.2.0", + "@metamask/scure-bip39": "^2.1.1", + "@metamask/utils": "^11.9.0" + }, "devDependencies": { "@metamask/auto-changelog": "^6.1.0", "@ts-bridge/cli": "^0.6.4", diff --git a/packages/wallet/src/Wallet.test.ts b/packages/wallet/src/Wallet.test.ts new file mode 100644 index 0000000000..97f9b1ee29 --- /dev/null +++ b/packages/wallet/src/Wallet.test.ts @@ -0,0 +1,202 @@ +import { Messenger } from '@metamask/messenger'; +import { Json } from '@metamask/utils'; +import { webcrypto } from 'crypto'; + +import MockEncryptor from '../../keyring-controller/tests/mocks/mockEncryptor'; +import * as initializationModule from './initialization/initialization'; +import { importSecretRecoveryPhrase } from './utilities'; +import { Wallet } from './Wallet'; + +const TEST_SRP = 'test test test test test test test test test test test ball'; +const TEST_PASSWORD = 'testpass'; + +async function setupWallet(): Promise { + const wallet = new Wallet({}); + + await importSecretRecoveryPhrase(wallet, TEST_PASSWORD, TEST_SRP); + + return wallet; +} + +describe('Wallet', () => { + beforeAll(() => { + // We can remove this once we drop Node 18 + // eslint-disable-next-line n/no-unsupported-features/node-builtins + globalThis.crypto ??= webcrypto as typeof globalThis.crypto; + + // eslint-disable-next-line no-restricted-syntax + if (!('CryptoKey' in globalThis)) { + Object.defineProperty(globalThis, 'CryptoKey', { + value: webcrypto.CryptoKey, + }); + } + }); + + it('exposes state', async () => { + const wallet = await setupWallet(); + const { state } = wallet; + + expect(state.KeyringController).toStrictEqual({ + isUnlocked: true, + keyrings: expect.any(Array), + encryptionKey: expect.any(String), + encryptionSalt: expect.any(String), + vault: expect.any(String), + }); + }); + + it('exposes instances', async () => { + const wallet = await setupWallet(); + + expect(wallet.getInstance('KeyringController').state).toStrictEqual({ + isUnlocked: true, + keyrings: expect.any(Array), + encryptionKey: expect.any(String), + encryptionSalt: expect.any(String), + vault: expect.any(String), + }); + }); + + it('supports passing instance options', async () => { + const wallet = new Wallet({ + instanceOptions: { + keyringController: { + encryptor: new MockEncryptor(), + }, + }, + }); + + await importSecretRecoveryPhrase(wallet, TEST_PASSWORD, TEST_SRP); + + const { state } = wallet; + + const vault = JSON.parse(state.KeyringController.vault as string); + + expect(vault).toStrictEqual({ + data: expect.any(String), + iv: 'iv', + salt: 'salt', + }); + }); + + it('supports passing additional initialization configurations', async () => { + class DummyController { + state = { foo: 'bar' }; + } + + class DummyService {} + + const wallet = new Wallet({ + initializationConfigurations: [ + { + name: 'KeyringController', + getMessenger: (): Messenger => + new Messenger({ namespace: 'KeyringController' }), + init: (): DummyController => new DummyController(), + }, + { + name: 'TestService', + getMessenger: (): Messenger => + new Messenger({ namespace: 'TestService' }), + init: (): DummyService => new DummyService(), + }, + ], + }); + const { state } = wallet; + + expect(state.KeyringController).toStrictEqual({ + foo: 'bar', + }); + + expect((state as Record).TestService).toBeUndefined(); + }); + + it('exposes controllerMetadata for each initialized controller', async () => { + const wallet = await setupWallet(); + + const names = Object.keys(wallet.controllerMetadata); + expect(names).toStrictEqual(Object.keys(wallet.state)); + for (const name of names) { + expect(wallet.controllerMetadata[name]).toBeDefined(); + } + }); + + it('omits instances without a metadata property from controllerMetadata', async () => { + const fakeMetadata = { + foo: { persist: true, includeInDebugSnapshot: false }, + }; + jest.spyOn(initializationModule, 'initialize').mockReturnValueOnce({ + // @ts-expect-error Mock data. + WithMeta: { state: {}, metadata: fakeMetadata }, + NoMeta: { state: {} }, + }); + + const wallet = new Wallet({}); + + expect(wallet.controllerMetadata).toStrictEqual({ + WithMeta: fakeMetadata, + }); + expect(Object.keys(wallet.state)).toStrictEqual(['WithMeta', 'NoMeta']); + }); + + it('disallows modifying the messenger', async () => { + const wallet = await setupWallet(); + + expect(() => { + wallet.messenger = new Messenger({ namespace: 'Root' }); + }).toThrow('The messenger cannot be directly mutated.'); + }); + + it('disallows modifying the state', async () => { + const wallet = await setupWallet(); + + expect(() => { + wallet.state = { KeyringController: { isUnlocked: false, keyrings: [] } }; + }).toThrow('Wallet state cannot be directly mutated.'); + }); + + it('disallows modifying the controller metadata', async () => { + const wallet = await setupWallet(); + + expect(() => { + wallet.controllerMetadata = {}; + }).toThrow('The controller metadata cannot be directly mutated.'); + }); + + it('calls destroy on instances exactly once', async () => { + const wallet = await setupWallet(); + + const keyringController = wallet.getInstance('KeyringController'); + + const spy = jest.spyOn(keyringController, 'destroy'); + + await wallet.destroy(); + await wallet.destroy(); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + describe('KeyringController', () => { + it('can unlock and populate accounts', async () => { + const wallet = await setupWallet(); + const { messenger } = wallet; + + expect( + await messenger.call('KeyringController:getAccounts'), + ).toStrictEqual(['0xc6d5a3c98ec9073b54fa0969957bd582e8d874bf']); + }); + + it('can lock', async () => { + const wallet = await setupWallet(); + const { messenger } = wallet; + + await messenger.call('KeyringController:setLocked'); + + expect(wallet.state.KeyringController).toStrictEqual({ + isUnlocked: false, + keyrings: [], + vault: expect.any(String), + }); + }); + }); +}); diff --git a/packages/wallet/src/Wallet.ts b/packages/wallet/src/Wallet.ts new file mode 100644 index 0000000000..02386c991e --- /dev/null +++ b/packages/wallet/src/Wallet.ts @@ -0,0 +1,144 @@ +import type { StateMetadataConstraint } from '@metamask/base-controller'; +import { Messenger } from '@metamask/messenger'; +import { hasProperty } from '@metamask/utils'; + +import type { + DefaultActions, + DefaultEvents, + DefaultInstances, + DefaultState, + RootMessenger, +} from './initialization/defaults'; +import { initialize } from './initialization/initialization'; +import { WalletOptions } from './types'; + +export class Wallet { + // TODO: Expand default types when passing additionalConfigurations. + readonly #messenger: RootMessenger; + + readonly #instances: DefaultInstances; + + readonly #controllerMetadata: Readonly< + Record> + >; + + #isDestroyed = false; + + /** + * Creates a `Wallet` instance, initializing all instances as specified by the passed options. + * + * @param options - Options bag. + * @param options.messenger - An optional messenger to override the default one. + * @param options.state - An optional state blob. + * @param options.initializationConfigurations - An optional list of additional initialization configurations + * required beyond the ones included by default. + * @param options.instanceOptions - An optional object containing options that should be passed + * to specific instances for additional customization. + */ + constructor(options: WalletOptions) { + this.#messenger = + options.messenger ?? + new Messenger({ + namespace: 'Root', + }); + + this.#instances = initialize({ + ...options, + messenger: this.#messenger, + }); + + this.#controllerMetadata = Object.fromEntries( + Object.entries(this.#instances) + .filter(([_, instance]) => hasProperty(instance, 'metadata')) + .map(([name, instance]) => [name, instance.metadata]), + ); + } + + /** + * @returns The root messenger of the wallet. + */ + get messenger(): Readonly> { + return this.#messenger; + } + + set messenger(_) { + throw new Error('The messenger cannot be directly mutated.'); + } + + /** + * @returns The combined state of the wallet. + */ + get state(): DefaultState { + return Object.entries(this.#instances).reduce>( + (totalState, [name, instance]) => { + if (instance.state) { + totalState[name] = instance.state; + } + return totalState; + }, + {}, + ) as DefaultState; + } + + set state(_) { + throw new Error('Wallet state cannot be directly mutated.'); + } + + /** + * @returns The controller metadata; containing per-controller information about what properties to persist etc. + */ + get controllerMetadata(): Readonly< + Record> + > { + return this.#controllerMetadata; + } + + set controllerMetadata(_) { + throw new Error('The controller metadata cannot be directly mutated.'); + } + + getInstance( + name: Name, + ): DefaultInstances[Name]; + + getInstance( + name: string, + ): DefaultInstances[keyof DefaultInstances] | undefined; + + /** + * Get an instantiated controller or service. + * + * @param name - The name. + * @returns The instance, if it exists. + * @deprecated - Please use the messenger instead of direct access. + */ + getInstance( + name: string, + ): DefaultInstances[keyof DefaultInstances] | undefined { + return this.#instances[name as keyof DefaultInstances]; + } + + /** + * Destroy the wallet instance. + */ + async destroy(): Promise { + if (this.#isDestroyed) { + return; + } + + this.#isDestroyed = true; + + await Promise.allSettled( + Object.values(this.#instances).map(async (instance) => { + // @ts-expect-error Accessing protected property. + if (typeof instance.destroy === 'function') { + // @ts-expect-error Accessing protected property. + // eslint-disable-next-line @typescript-eslint/await-thenable + return await instance.destroy(); + } + /* istanbul ignore next */ + return undefined; + }), + ); + } +} diff --git a/packages/wallet/src/index.test.ts b/packages/wallet/src/index.test.ts deleted file mode 100644 index bc062d3694..0000000000 --- a/packages/wallet/src/index.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import greeter from '.'; - -describe('Test', () => { - it('greets', () => { - const name = 'Huey'; - const result = greeter(name); - expect(result).toBe('Hello, Huey!'); - }); -}); diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index 6972c11729..66b6f88aa3 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -1,9 +1,9 @@ -/** - * Example function that returns a greeting for the given name. - * - * @param name - The name to greet. - * @returns The greeting. - */ -export default function greeter(name: string): string { - return `Hello, ${name}!`; -} +export { Wallet } from './Wallet'; +export type { WalletOptions } from './types'; +export type { + DefaultActions, + DefaultEvents, + DefaultInstances, + DefaultState, + RootMessenger, +} from './initialization/defaults'; diff --git a/packages/wallet/src/initialization/defaults.ts b/packages/wallet/src/initialization/defaults.ts new file mode 100644 index 0000000000..77ffb943c4 --- /dev/null +++ b/packages/wallet/src/initialization/defaults.ts @@ -0,0 +1,61 @@ +import type { + ActionConstraint, + EventConstraint, + Messenger, + MessengerActions, + MessengerEvents, +} from '@metamask/messenger'; + +import * as defaultConfigurations from './instances'; +import type { InitializationConfiguration, InstanceState } from './types'; + +export { defaultConfigurations }; + +/** + * Utility type for inferring and extracting an instance type from an initialization configuration. + */ +type ExtractInstance = + Config extends InitializationConfiguration + ? Instance + : never; + +/** + * Utility type for inferring and extracting an instance messenger type from an initialization configuration. + */ +type ExtractInstanceMessenger = + Config extends InitializationConfiguration + ? InferredMessenger + : never; + +/** + * Utility type for inferring and extracting the name of an instance from an initialization configuration. + */ +type ExtractName = + ExtractInstance extends { name: infer Name extends string } + ? Name + : never; + +type DefaultConfigs = typeof defaultConfigurations; + +type AllDefaultMessengers = ExtractInstanceMessenger< + DefaultConfigs[keyof DefaultConfigs] +>; + +export type DefaultInstances = { + [Key in keyof DefaultConfigs as ExtractName< + DefaultConfigs[Key] + >]: ExtractInstance; +}; + +export type DefaultActions = MessengerActions; + +export type DefaultEvents = MessengerEvents; + +export type RootMessenger< + AllowedActions extends ActionConstraint, + AllowedEvents extends EventConstraint, +> = Messenger<'Root', AllowedActions, AllowedEvents>; + +export type DefaultState = { + [Key in keyof DefaultInstances]: InstanceState; +}; diff --git a/packages/wallet/src/initialization/initialization.ts b/packages/wallet/src/initialization/initialization.ts new file mode 100644 index 0000000000..7374f3c614 --- /dev/null +++ b/packages/wallet/src/initialization/initialization.ts @@ -0,0 +1,60 @@ +import type { InstanceSpecificOptions, WalletOptions } from '../types'; +import type { + DefaultActions, + DefaultEvents, + DefaultInstances, +} from './defaults'; +import { defaultConfigurations, RootMessenger } from './defaults'; + +type InitializeOptions = WalletOptions & { + messenger: RootMessenger; +}; + +/** + * Initialize all instances based on th default configurations and any additional configurations specified in `options`. + * + * @param options - The wallet options. + * @returns A map containing the instances. + */ +export function initialize(options: InitializeOptions): DefaultInstances { + const { + messenger, + state = {}, + initializationConfigurations = [], + instanceOptions, + } = options; + + const overriddenConfiguration = initializationConfigurations.map( + (config) => config.name, + ); + + const configurationEntries = initializationConfigurations.concat( + Object.values(defaultConfigurations).filter( + (config) => !overriddenConfiguration.includes(config.name), + ), + ); + + const instances: Record = {}; + + for (const config of configurationEntries) { + const { name } = config; + + const instanceState = state[name]; + + const instanceMessenger = config.getMessenger(messenger); + + const camelCaseName = + `${name.charAt(0).toLowerCase()}${name.slice(1)}` as keyof InstanceSpecificOptions; + + const instance = config.init({ + // TODO: Consider whether this can be improved + state: instanceState as never, + messenger: instanceMessenger, + options: instanceOptions?.[camelCaseName] ?? {}, + }); + + instances[name] = instance as Record; + } + + return instances as DefaultInstances; +} diff --git a/packages/wallet/src/initialization/instances/index.ts b/packages/wallet/src/initialization/instances/index.ts new file mode 100644 index 0000000000..28a3bf2f23 --- /dev/null +++ b/packages/wallet/src/initialization/instances/index.ts @@ -0,0 +1 @@ +export { keyringController } from './keyring-controller'; diff --git a/packages/wallet/src/initialization/instances/keyring-controller.ts b/packages/wallet/src/initialization/instances/keyring-controller.ts new file mode 100644 index 0000000000..8e7c451efb --- /dev/null +++ b/packages/wallet/src/initialization/instances/keyring-controller.ts @@ -0,0 +1,183 @@ +import type { + DetailedEncryptionResult, + EncryptionKey, + KeyDerivationOptions, +} from '@metamask/browser-passworder'; +import { + encrypt, + encryptWithDetail, + encryptWithKey, + decrypt, + decryptWithDetail, + decryptWithKey, + isVaultUpdated, + keyFromPassword, + importKey, + exportKey, + generateSalt, +} from '@metamask/browser-passworder'; +import type { Encryptor } from '@metamask/keyring-controller'; +import { + KeyringController, + KeyringControllerMessenger, +} from '@metamask/keyring-controller'; +import { Messenger } from '@metamask/messenger'; + +import { InitializationConfiguration } from '../types'; + +/** + * A factory function for the encrypt method of the browser-passworder library, + * that encrypts with a given number of iterations. + * + * @param iterations - The number of iterations to use for the PBKDF2 algorithm. + * @returns A function that encrypts with the given number of iterations. + */ +const encryptFactory = + (iterations: number) => + async ( + password: string, + data: unknown, + key?: EncryptionKey | CryptoKey, + salt?: string, + ): Promise => + encrypt(password, data, key, salt, { + algorithm: 'PBKDF2', + params: { + iterations, + }, + }); + +/** + * A factory function for the encryptWithDetail method of the browser-passworder library, + * that encrypts with a given number of iterations. + * + * @param iterations - The number of iterations to use for the PBKDF2 algorithm. + * @returns A function that encrypts with the given number of iterations. + */ +const encryptWithDetailFactory = + (iterations: number) => + async ( + password: string, + object: unknown, + salt?: string, + ): Promise => + encryptWithDetail(password, object, salt, { + algorithm: 'PBKDF2', + params: { + iterations, + }, + }); + +/** + * A factory function for the keyFromPassword method of the browser-passworder library, + * that generates a key from a password and a salt. + * + * This factory function overrides the default key derivation options with the specified + * number of iterations, unless existing key derivation options are passed in. + * + * @param iterations - The number of iterations to use for the PBKDF2 algorithm. + * @returns A function that generates a key with a potentially overriden number of iterations. + */ +const keyFromPasswordFactory = + (iterations: number) => + async ( + password: string, + salt: string, + exportable?: boolean, + opts?: KeyDerivationOptions, + ): Promise => + keyFromPassword( + password, + salt, + exportable, + opts ?? { + algorithm: 'PBKDF2', + params: { + iterations, + }, + }, + ); + +/** + * A factory function for the isVaultUpdated method of the browser-passworder library, + * that checks if the given vault was encrypted with the given number of iterations. + * + * @param iterations - The number of iterations to use for the PBKDF2 algorithm. + * @returns A function that checks if the vault was encrypted with the given number of iterations. + */ +const isVaultUpdatedFactory = + (iterations: number) => + (vault: string): boolean => + isVaultUpdated(vault, { + algorithm: 'PBKDF2', + params: { + iterations, + }, + }); + +/** + * A factory function that returns an encryptor with the given number of iterations. + * + * The returned encryptor is a wrapper around the browser-passworder library, that + * calls the encrypt and encryptWithDetail methods with the given number of iterations. + * + * @param iterations - The number of iterations to use for the PBKDF2 algorithm. + * @returns An encryptor set with the given number of iterations. + */ +const encryptorFactory = (iterations: number): Encryptor => ({ + encrypt: encryptFactory(iterations), + encryptWithKey, + encryptWithDetail: encryptWithDetailFactory(iterations), + decrypt, + decryptWithKey, + decryptWithDetail, + keyFromPassword: keyFromPasswordFactory(iterations), + isVaultUpdated: isVaultUpdatedFactory(iterations), + importKey, + exportKey, + generateSalt, +}); + +type MobileEncryptionKey = { + key: string; + lib: string; + exportable: boolean; + keyMetadata: KeyDerivationOptions; +}; + +type MobileEncryptionResult = { + cipher: string; + iv: string; + salt?: string; + lib?: string; + keyMetadata?: KeyDerivationOptions; +}; + +export type GenericEncryptor = + | Encryptor + | Encryptor< + MobileEncryptionKey, + KeyDerivationOptions, + MobileEncryptionResult + >; + +export const keyringController: InitializationConfiguration< + KeyringController, + KeyringControllerMessenger +> = { + name: 'KeyringController', + init: ({ state, messenger, options }) => + new KeyringController({ + state, + messenger, + keyringBuilders: options.keyringBuilders, + encryptor: (options.encryptor ?? encryptorFactory(600_000)) as Encryptor< + EncryptionKey | CryptoKey + >, + }), + getMessenger: (parent) => + new Messenger({ + namespace: 'KeyringController', + parent, + }), +}; diff --git a/packages/wallet/src/initialization/types.ts b/packages/wallet/src/initialization/types.ts new file mode 100644 index 0000000000..1e7e51602b --- /dev/null +++ b/packages/wallet/src/initialization/types.ts @@ -0,0 +1,52 @@ +import type { InstanceSpecificOptions } from '../types'; +import type { DefaultActions, DefaultEvents, RootMessenger } from './defaults'; + +/** + * Utility type for inferring the state of an instance. + */ +export type InstanceState = Instance extends { state: unknown } + ? Instance['state'] + : undefined; + +/** + * Utility type for inferring the name of an instance. + */ +type InstanceName = Instance extends { + name: infer Name extends string; +} + ? Name + : string; + +/** + * Utility type for lower-casing the first character of an instance name, required for camel-casing. + */ +type LowerCaseFirstLetter = + Name extends `${infer Character1}${infer Rest}` + ? `${Lowercase}${Rest}` + : Lowercase; + +type CamelCaseInstanceName = LowerCaseFirstLetter< + InstanceName +>; + +/** + * Utility type for narrowing the InstanceSpecificOptions to just the options required for the instance. + */ +type InstanceOptions = + CamelCaseInstanceName extends keyof InstanceSpecificOptions + ? NonNullable]> + : unknown; + +export type InitFunctionArguments = { + state: InstanceState | undefined; + messenger: InstanceMessenger; + options: InstanceOptions; +}; + +export type InitializationConfiguration = { + name: InstanceName; + init(args: InitFunctionArguments): Instance; + getMessenger( + parent: RootMessenger, + ): InstanceMessenger; +}; diff --git a/packages/wallet/src/types.ts b/packages/wallet/src/types.ts new file mode 100644 index 0000000000..4d385c85a9 --- /dev/null +++ b/packages/wallet/src/types.ts @@ -0,0 +1,27 @@ +import { KeyringControllerOptions } from '@metamask/keyring-controller'; +import type { Json } from '@metamask/utils'; + +import type { + DefaultActions, + DefaultEvents, + RootMessenger, +} from './initialization/defaults'; +import { GenericEncryptor } from './initialization/instances/keyring-controller'; +import { InitializationConfiguration } from './initialization/types'; + +export type WalletOptions = { + messenger?: RootMessenger; + state?: Record | undefined>; + initializationConfigurations?: InitializationConfiguration< + unknown, + unknown + >[]; + instanceOptions?: InstanceSpecificOptions; +}; + +export type InstanceSpecificOptions = { + keyringController?: { + encryptor?: GenericEncryptor; + keyringBuilders?: KeyringControllerOptions['keyringBuilders']; + }; +}; diff --git a/packages/wallet/src/utilities.ts b/packages/wallet/src/utilities.ts new file mode 100644 index 0000000000..4630dd5460 --- /dev/null +++ b/packages/wallet/src/utilities.ts @@ -0,0 +1,26 @@ +import { wordlist } from '@metamask/scure-bip39/dist/wordlists/english'; + +import { Wallet } from './Wallet'; + +/** + * Import a secret recovery phrase using the wallet object. + * + * @param wallet - The wallet object. + * @param password - The password to the MetaMask wallet (not the SRP). + * @param phrase - The SRP as a string. + */ +export async function importSecretRecoveryPhrase( + wallet: Wallet, + password: string, + phrase: string, +): Promise { + const indices = phrase.split(' ').map((word) => wordlist.indexOf(word)); + const mnemonic = new Uint8Array(new Uint16Array(indices).buffer); + + // TODO: This should use the new MultichainAccountService. + await wallet.messenger.call( + 'KeyringController:createNewVaultAndRestore', + password, + mnemonic, + ); +} diff --git a/packages/wallet/tsconfig.build.json b/packages/wallet/tsconfig.build.json index 02a0eea03f..b16ce7cfcd 100644 --- a/packages/wallet/tsconfig.build.json +++ b/packages/wallet/tsconfig.build.json @@ -5,6 +5,10 @@ "outDir": "./dist", "rootDir": "./src" }, - "references": [], + "references": [ + { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../keyring-controller/tsconfig.build.json" }, + { "path": "../messenger/tsconfig.build.json" } + ], "include": ["../../types", "./src"] } diff --git a/packages/wallet/tsconfig.json b/packages/wallet/tsconfig.json index 025ba2ef7f..1b4b6d0a5f 100644 --- a/packages/wallet/tsconfig.json +++ b/packages/wallet/tsconfig.json @@ -3,6 +3,10 @@ "compilerOptions": { "baseUrl": "./" }, - "references": [], + "references": [ + { "path": "../base-controller/tsconfig.json" }, + { "path": "../keyring-controller/tsconfig.json" }, + { "path": "../messenger/tsconfig.json" } + ], "include": ["../../types", "./src"] } diff --git a/yarn.lock b/yarn.lock index 46aca8aaa5..dce0f91aaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5881,6 +5881,12 @@ __metadata: resolution: "@metamask/wallet@workspace:packages/wallet" dependencies: "@metamask/auto-changelog": "npm:^6.1.0" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/browser-passworder": "npm:^6.0.0" + "@metamask/keyring-controller": "npm:^25.5.0" + "@metamask/messenger": "npm:^1.2.0" + "@metamask/scure-bip39": "npm:^2.1.1" + "@metamask/utils": "npm:^11.9.0" "@ts-bridge/cli": "npm:^0.6.4" "@types/jest": "npm:^29.5.14" deepmerge: "npm:^4.2.2"