diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 360764abba6..6145f84e77c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,6 +18,7 @@ /packages/profile-sync-controller @MetaMask/accounts-engineers /packages/money-account-controller @MetaMask/accounts-engineers /packages/snap-account-service @MetaMask/accounts-engineers +/packages/entropy-controller @MetaMask/accounts-engineers ## Auth Team /packages/authenticated-user-storage @MetaMask/auth-engineers @@ -286,4 +287,6 @@ /packages/money-account-upgrade-controller/package.json @MetaMask/earn @MetaMask/delegation @MetaMask/core-platform /packages/money-account-upgrade-controller/CHANGELOG.md @MetaMask/earn @MetaMask/delegation @MetaMask/core-platform /packages/snap-account-service/package.json @MetaMask/accounts-engineers @MetaMask/core-platform -/packages/snap-account-service/CHANGELOG.md @MetaMask/accounts-engineers @MetaMask/core-platform \ No newline at end of file +/packages/snap-account-service/CHANGELOG.md @MetaMask/accounts-engineers @MetaMask/core-platform +/packages/entropy-controller/package.json @MetaMask/accounts-engineers @MetaMask/core-platform +/packages/entropy-controller/CHANGELOG.md @MetaMask/accounts-engineers @MetaMask/core-platform diff --git a/README.md b/README.md index e03df83f857..5fbdf33e586 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ yarn skills --reset # clear saved local selection - [`@metamask/eip-7702-internal-rpc-middleware`](packages/eip-7702-internal-rpc-middleware) - [`@metamask/eip1193-permission-middleware`](packages/eip1193-permission-middleware) - [`@metamask/ens-controller`](packages/ens-controller) +- [`@metamask/entropy-controller`](packages/entropy-controller) - [`@metamask/eth-block-tracker`](packages/eth-block-tracker) - [`@metamask/eth-json-rpc-middleware`](packages/eth-json-rpc-middleware) - [`@metamask/eth-json-rpc-provider`](packages/eth-json-rpc-provider) @@ -180,6 +181,7 @@ linkStyle default opacity:0.5 eip_7702_internal_rpc_middleware(["@metamask/eip-7702-internal-rpc-middleware"]); eip1193_permission_middleware(["@metamask/eip1193-permission-middleware"]); ens_controller(["@metamask/ens-controller"]); + entropy_controller(["@metamask/entropy-controller"]); eth_block_tracker(["@metamask/eth-block-tracker"]); eth_json_rpc_middleware(["@metamask/eth-json-rpc-middleware"]); eth_json_rpc_provider(["@metamask/eth-json-rpc-provider"]); diff --git a/packages/entropy-controller/CHANGELOG.md b/packages/entropy-controller/CHANGELOG.md new file mode 100644 index 00000000000..53b7c5c0184 --- /dev/null +++ b/packages/entropy-controller/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Add `EntropyController` ([#9538](https://github.com/MetaMask/core/pull/9538)) + +[Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/entropy-controller/LICENSE b/packages/entropy-controller/LICENSE new file mode 100644 index 00000000000..9ec4f4514ea --- /dev/null +++ b/packages/entropy-controller/LICENSE @@ -0,0 +1,6 @@ +This project is licensed under either of + + * MIT license ([LICENSE.MIT](LICENSE.MIT)) + * Apache License, Version 2.0 ([LICENSE.APACHE2](LICENSE.APACHE2)) + +at your option. diff --git a/packages/entropy-controller/LICENSE.APACHE2 b/packages/entropy-controller/LICENSE.APACHE2 new file mode 100644 index 00000000000..e6e77b08909 --- /dev/null +++ b/packages/entropy-controller/LICENSE.APACHE2 @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/entropy-controller/LICENSE.MIT b/packages/entropy-controller/LICENSE.MIT new file mode 100644 index 00000000000..fe29e78e0fe --- /dev/null +++ b/packages/entropy-controller/LICENSE.MIT @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 MetaMask + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/entropy-controller/README.md b/packages/entropy-controller/README.md new file mode 100644 index 00000000000..484bf57230e --- /dev/null +++ b/packages/entropy-controller/README.md @@ -0,0 +1,15 @@ +# `@metamask/entropy-controller` + +Controller that manages/owns various entropies (SRPs/mnemonic, private keys, hardware wallets, etc.) + +## Installation + +`yarn add @metamask/entropy-controller` + +or + +`npm install @metamask/entropy-controller` + +## Contributing + +This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/core#readme). diff --git a/packages/entropy-controller/jest.config.js b/packages/entropy-controller/jest.config.js new file mode 100644 index 00000000000..ca084133399 --- /dev/null +++ b/packages/entropy-controller/jest.config.js @@ -0,0 +1,26 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +const merge = require('deepmerge'); +const path = require('path'); + +const baseConfig = require('../../jest.config.packages'); + +const displayName = path.basename(__dirname); + +module.exports = merge(baseConfig, { + // The display name when running multiple projects + displayName, + + // An object that configures minimum threshold enforcement for coverage results + coverageThreshold: { + global: { + branches: 100, + functions: 100, + lines: 100, + statements: 100, + }, + }, +}); diff --git a/packages/entropy-controller/package.json b/packages/entropy-controller/package.json new file mode 100644 index 00000000000..e1cdee72bce --- /dev/null +++ b/packages/entropy-controller/package.json @@ -0,0 +1,81 @@ +{ + "name": "@metamask/entropy-controller", + "version": "0.0.0", + "description": "Controller that manages/owns various entropies (SRPs/mnemonic, private keys, hardware wallets, etc.)", + "keywords": [ + "Ethereum", + "MetaMask" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/entropy-controller#readme", + "bugs": { + "url": "https://github.com/MetaMask/core/issues" + }, + "license": "(MIT OR Apache-2.0)", + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/core.git" + }, + "files": [ + "dist/" + ], + "sideEffects": false, + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", + "build:all": "ts-bridge --project tsconfig.build.json --verbose --clean", + "build:docs": "typedoc", + "changelog:update": "../../scripts/update-changelog.sh @metamask/entropy-controller", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/entropy-controller", + "messenger-action-types:check": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --check", + "messenger-action-types:generate": "tsx ../../packages/messenger-cli/src/cli.ts --formatter oxfmt --generate", + "since-latest-release": "../../scripts/since-latest-release.sh", + "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", + "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", + "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch", + "lint:tsconfigs": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts", + "lint:tsconfigs:fix": "tsx ../../scripts/lint-tsconfigs/lint-tsconfigs.mts --fix" + }, + "dependencies": { + "@metamask/base-controller": "^9.1.0", + "@metamask/eth-hd-keyring": "^14.1.1", + "@metamask/keyring-controller": "^27.1.0", + "@metamask/messenger": "^2.0.0", + "@metamask/utils": "^11.11.0", + "@noble/hashes": "^1.8.0", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@metamask/auto-changelog": "^6.1.0", + "@ts-bridge/cli": "^0.6.4", + "@types/jest": "^29.5.14", + "deepmerge": "^4.2.2", + "jest": "^29.7.0", + "ts-jest": "^29.2.5", + "tsx": "^4.20.5", + "typedoc": "^0.25.13", + "typedoc-plugin-missing-exports": "^2.0.0", + "typescript": "~5.3.3" + }, + "engines": { + "node": "^18.18 || >=20" + } +} diff --git a/packages/entropy-controller/src/EntropyController-method-action-types.ts b/packages/entropy-controller/src/EntropyController-method-action-types.ts new file mode 100644 index 00000000000..d9fd88ead1c --- /dev/null +++ b/packages/entropy-controller/src/EntropyController-method-action-types.ts @@ -0,0 +1,34 @@ +/** + * This file is auto generated. + * Do not edit manually. + */ + +import type { EntropyController } from './EntropyController'; + +/** + * Registers an entropy source in the controller state. + * + * @param entropy - The entropy source to register, including its pre-computed + * ID and type. + */ +export type EntropyControllerAddEntropyAction = { + type: `EntropyController:addEntropy`; + handler: EntropyController['addEntropy']; +}; + +/** + * Removes an entropy source from the controller state. + * + * @param entropyId - The ID of the entropy source to remove. + */ +export type EntropyControllerRemoveEntropyAction = { + type: `EntropyController:removeEntropy`; + handler: EntropyController['removeEntropy']; +}; + +/** + * Union of all EntropyController action types. + */ +export type EntropyControllerMethodActions = + | EntropyControllerAddEntropyAction + | EntropyControllerRemoveEntropyAction; diff --git a/packages/entropy-controller/src/EntropyController.test.ts b/packages/entropy-controller/src/EntropyController.test.ts new file mode 100644 index 00000000000..5647a10f609 --- /dev/null +++ b/packages/entropy-controller/src/EntropyController.test.ts @@ -0,0 +1,233 @@ +import { deriveStateFromMetadata } from '@metamask/base-controller'; +import { Messenger, MOCK_ANY_NAMESPACE } from '@metamask/messenger'; +import type { + MockAnyNamespace, + MessengerActions, + MessengerEvents, +} from '@metamask/messenger'; + +import type { EntropyControllerMessenger } from './EntropyController'; +import { EntropyController } from './EntropyController'; +import { toEntropyId } from './utils'; + +// Stable test secrets +const HD_MNEMONIC = new TextEncoder().encode( + 'test test test test test test test test test test test junk', +); + +type RootMessenger = Messenger< + MockAnyNamespace, + MessengerActions, + MessengerEvents +>; + +async function setup({ + options = {}, +}: { + options?: Partial[0]>; +} = {}): Promise<{ + controller: EntropyController; + rootMessenger: RootMessenger; +}> { + const rootMessenger: RootMessenger = new Messenger({ + namespace: MOCK_ANY_NAMESPACE, + }); + + const messenger = new Messenger< + 'EntropyController', + MessengerActions, + MessengerEvents + >({ + namespace: 'EntropyController', + parent: rootMessenger, + }); + + const controller = new EntropyController({ messenger, ...options }); + + return { controller, rootMessenger }; +} + +describe('EntropyController', () => { + describe('constructor', () => { + it('accepts initial state', async () => { + const givenState = { + entropySources: { + 'entropy:bip44:mnemonic:some-uuid': { + type: 'bip44:mnemonic' as const, + }, + }, + }; + + const { controller } = await setup({ options: { state: givenState } }); + + expect(controller.state).toStrictEqual(givenState); + }); + + it('fills in missing initial state with defaults', async () => { + const { controller } = await setup(); + + expect(controller.state).toMatchInlineSnapshot(` + { + "entropySources": {}, + } + `); + }); + }); + + describe('addEntropy', () => { + it('registers a mnemonic entropy source', async () => { + const expectedId = await toEntropyId('bip44', 'mnemonic', HD_MNEMONIC); + + const { controller, rootMessenger } = await setup(); + + rootMessenger.call('EntropyController:addEntropy', { + type: 'bip44:mnemonic', + id: expectedId, + }); + + expect(controller.state.entropySources).toStrictEqual({ + [expectedId]: { type: 'bip44:mnemonic' }, + }); + }); + + it('merges new sources without replacing existing ones', async () => { + const firstMnemonic = HD_MNEMONIC; + const secondMnemonic = new TextEncoder().encode( + 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about', + ); + const firstId = await toEntropyId('bip44', 'mnemonic', firstMnemonic); + const secondId = await toEntropyId('bip44', 'mnemonic', secondMnemonic); + + const { controller, rootMessenger } = await setup(); + + rootMessenger.call('EntropyController:addEntropy', { + type: 'bip44:mnemonic', + id: firstId, + }); + rootMessenger.call('EntropyController:addEntropy', { + type: 'bip44:mnemonic', + id: secondId, + }); + + expect(controller.state.entropySources).toStrictEqual({ + [firstId]: { type: 'bip44:mnemonic' }, + [secondId]: { type: 'bip44:mnemonic' }, + }); + }); + + it('is idempotent — registering the same source twice yields one entry', async () => { + const id = await toEntropyId('bip44', 'mnemonic', HD_MNEMONIC); + + const { controller, rootMessenger } = await setup(); + + rootMessenger.call('EntropyController:addEntropy', { + type: 'bip44:mnemonic', + id, + }); + rootMessenger.call('EntropyController:addEntropy', { + type: 'bip44:mnemonic', + id, + }); + + expect(Object.keys(controller.state.entropySources)).toHaveLength(1); + expect(controller.state.entropySources[id]).toBeDefined(); + }); + }); + + describe('removeEntropy', () => { + it('removes an entropy source by its ID', async () => { + const id = await toEntropyId('bip44', 'mnemonic', HD_MNEMONIC); + + const { controller, rootMessenger } = await setup({ + options: { + state: { + entropySources: { + [id]: { type: 'bip44:mnemonic' }, + 'entropy:bip44:mnemonic:other-uuid': { type: 'bip44:mnemonic' }, + }, + }, + }, + }); + + rootMessenger.call('EntropyController:removeEntropy', id); + + expect(controller.state.entropySources[id]).toBeUndefined(); + expect( + controller.state.entropySources['entropy:bip44:mnemonic:other-uuid'], + ).toBeDefined(); + }); + + it('does nothing when the ID does not exist', async () => { + const { controller, rootMessenger } = await setup(); + + rootMessenger.call( + 'EntropyController:removeEntropy', + 'entropy:bip44:mnemonic:nonexistent', + ); + + expect(controller.state.entropySources).toStrictEqual({}); + }); + }); + + describe('metadata', () => { + it('does not include entropy sources in debug snapshots', async () => { + const { controller } = await setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInDebugSnapshot', + ), + ).toMatchInlineSnapshot(`{}`); + }); + + it('includes entropy sources in state logs', async () => { + const { controller } = await setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'includeInStateLogs', + ), + ).toMatchInlineSnapshot(` + { + "entropySources": {}, + } + `); + }); + + it('persists entropy sources', async () => { + const { controller } = await setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'persist', + ), + ).toMatchInlineSnapshot(` + { + "entropySources": {}, + } + `); + }); + + it('exposes entropy sources to UI', async () => { + const { controller } = await setup(); + + expect( + deriveStateFromMetadata( + controller.state, + controller.metadata, + 'usedInUi', + ), + ).toMatchInlineSnapshot(` + { + "entropySources": {}, + } + `); + }); + }); +}); diff --git a/packages/entropy-controller/src/EntropyController.ts b/packages/entropy-controller/src/EntropyController.ts new file mode 100644 index 00000000000..4380f9439b0 --- /dev/null +++ b/packages/entropy-controller/src/EntropyController.ts @@ -0,0 +1,195 @@ +import type { + ControllerGetStateAction, + ControllerStateChangeEvent, + StateMetadata, +} from '@metamask/base-controller'; +import { BaseController } from '@metamask/base-controller'; +import type { Messenger } from '@metamask/messenger'; + +import type { Entropy, EntropyId, EntropyType } from './types'; + +/** + * The name of the {@link EntropyController}, used to namespace the + * controller's actions and events and to namespace the controller's state data + * when composed with other controllers. + */ +const CONTROLLER_NAME = 'EntropyController'; + +const MESSENGER_EXPOSED_METHODS = [ + 'addEntropy', + 'removeEntropy', +] as const; + +/** + * Describes the shape of the state object for {@link EntropyController}. + */ +export type EntropyControllerState = { + /** + * The registry of entropy sources, keyed by their unique identifier. + * Each entry records the type of the entropy source. + */ + entropySources: { + [entropyId: EntropyId]: { + /** + * The type of the entropy source, expressed as `category:implementation` + * (e.g. `'bip44:mnemonic'`, `'bip44:ledger'`). + */ + type: EntropyType; + }; + }; +}; + +/** + * The metadata for each property in {@link EntropyControllerState}. + */ +const entropyControllerMetadata = { + entropySources: { + includeInDebugSnapshot: false, + includeInStateLogs: true, + persist: true, + usedInUi: true, + }, +} satisfies StateMetadata; + +/** + * Constructs the default {@link EntropyController} state. + * + * @returns The default {@link EntropyController} state. + */ +export function getDefaultEntropyControllerState(): EntropyControllerState { + return { + entropySources: {}, + }; +} + +/** + * Retrieves the state of the {@link EntropyController}. + */ +export type EntropyControllerGetStateAction = ControllerGetStateAction< + typeof CONTROLLER_NAME, + EntropyControllerState +>; + +/** + * Registers an entropy source in the controller. + * + * The caller is responsible for pre-computing the entropy ID via `toEntropyId` + * before calling this action. + */ +export type EntropyControllerAddEntropyAction = { + type: `${typeof CONTROLLER_NAME}:addEntropy`; + handler: (entropy: Entropy) => void; +}; + +/** + * Removes an entropy source by its ID. + */ +export type EntropyControllerRemoveEntropyAction = { + type: `${typeof CONTROLLER_NAME}:removeEntropy`; + handler: (entropyId: EntropyId) => void; +}; + +/** + * Actions that {@link EntropyControllerMessenger} exposes to other consumers. + */ +export type EntropyControllerActions = + | EntropyControllerGetStateAction + | EntropyControllerAddEntropyAction + | EntropyControllerRemoveEntropyAction; + +/** + * Published when the state of {@link EntropyController} changes. + */ +export type EntropyControllerStateChangeEvent = ControllerStateChangeEvent< + typeof CONTROLLER_NAME, + EntropyControllerState +>; + +/** + * Events that {@link EntropyControllerMessenger} exposes to other consumers. + */ +export type EntropyControllerEvents = EntropyControllerStateChangeEvent; + +/** + * The messenger restricted to actions and events accessed by + * {@link EntropyController}. + */ +export type EntropyControllerMessenger = Messenger< + typeof CONTROLLER_NAME, + EntropyControllerActions, + EntropyControllerEvents +>; + +/** + * `EntropyController` maintains a registry that maps entropy source identifiers + * to their type. + * + * An entropy source is any provider of key material: a Secret Recovery Phrase + * (SRP), a hardware wallet (Ledger, Trezor), or an imported private key. The + * registry is the single source of truth for which entropy sources exist and + * what kind they are. + * + * Rather than reacting to `KeyringController` state changes, this controller + * exposes `addEntropy` and `removeEntropy` actions that `KeyringController` + * calls directly after its own operations succeed. This ensures that entropy + * sources are registered atomically from the caller's perspective. + */ +export class EntropyController extends BaseController< + typeof CONTROLLER_NAME, + EntropyControllerState, + EntropyControllerMessenger +> { + /** + * Constructs a new {@link EntropyController}. + * + * @param args - The arguments to this controller. + * @param args.messenger - The messenger suited for this controller. + * @param args.state - The desired state with which to initialize this + * controller. Missing properties will be filled in with defaults. + */ + constructor({ + messenger, + state, + }: { + messenger: EntropyControllerMessenger; + state?: Partial; + }) { + super({ + messenger, + metadata: entropyControllerMetadata, + name: CONTROLLER_NAME, + state: { + ...getDefaultEntropyControllerState(), + ...state, + }, + }); + + this.messenger.registerMethodActionHandlers( + this, + MESSENGER_EXPOSED_METHODS, + ); + } + + /** + * Registers an entropy source in the controller state. + * + * @param entropy - The entropy source to register, including its pre-computed + * ID and type. + */ + addEntropy(entropy: Entropy): void { + this.update((state) => { + state.entropySources[entropy.id] = { type: entropy.type }; + }); + } + + /** + * Removes an entropy source from the controller state. + * + * @param entropyId - The ID of the entropy source to remove. + */ + removeEntropy(entropyId: EntropyId): void { + this.update((state) => { + delete state.entropySources[entropyId]; + }); + } +} diff --git a/packages/entropy-controller/src/index.ts b/packages/entropy-controller/src/index.ts new file mode 100644 index 00000000000..425cf492b4d --- /dev/null +++ b/packages/entropy-controller/src/index.ts @@ -0,0 +1,21 @@ +export { EntropyController } from './entropy-controller'; +export type { + EntropyControllerActions, + EntropyControllerAddEntropyAction, + EntropyControllerEvents, + EntropyControllerGetStateAction, + EntropyControllerMessenger, + EntropyControllerRemoveEntropyAction, + EntropyControllerState, + EntropyControllerStateChangeEvent, +} from './entropy-controller'; +export type { + Bip44MnemonicEntropy, + Entropy, + EntropyCategory, + EntropyId, + EntropyImplementation, + EntropyType, +} from './types'; +export { isBip44MnemonicEntropy } from './types'; +export { fingerprint, toEntropyFingerprint, toEntropyId } from './utils'; diff --git a/packages/entropy-controller/src/types.test.ts b/packages/entropy-controller/src/types.test.ts new file mode 100644 index 00000000000..f6d8c929c0a --- /dev/null +++ b/packages/entropy-controller/src/types.test.ts @@ -0,0 +1,21 @@ +import { isBip44MnemonicEntropy } from './types'; + +describe('isBip44MnemonicEntropy', () => { + it('returns true for a bip44:mnemonic entropy', () => { + expect( + isBip44MnemonicEntropy({ type: 'bip44:mnemonic', id: 'entropy:bip44:mnemonic:some-uuid' }), + ).toBe(true); + }); + + it('returns false for other bip44 implementations', () => { + expect( + isBip44MnemonicEntropy({ type: 'bip44:ledger', id: 'entropy:bip44:ledger:_' }), + ).toBe(false); + }); + + it('returns false for raw category types', () => { + expect( + isBip44MnemonicEntropy({ type: 'raw:private-key', id: 'entropy:raw:private-key:some-uuid' }), + ).toBe(false); + }); +}); diff --git a/packages/entropy-controller/src/types.ts b/packages/entropy-controller/src/types.ts new file mode 100644 index 00000000000..f554c0f7213 --- /dev/null +++ b/packages/entropy-controller/src/types.ts @@ -0,0 +1,69 @@ +/** + * The category of an entropy source. + * + * - `'bip44'` — Entropy that uses BIP-44 to derive signers (e.g. SRP, hardware + * wallets). + * - `'raw'` — Entropy that exposes a single key directly, without derivation + * (e.g. imported private keys, MPC). + */ +export type EntropyCategory = 'bip44' | 'raw'; + +/** + * The implementation of an entropy source within its category. + */ +export type EntropyImplementation = + | 'mnemonic' + | 'ledger' + | 'trezor' + | 'private-key' + | 'mpc'; + +/** + * The type of an entropy source, expressed as `category:implementation`. + * + * @example `'bip44:mnemonic'`, `'bip44:ledger'`, `'raw:private-key'`, `'raw:mpc'` + */ +export type EntropyType = `${EntropyCategory}:${EntropyImplementation}`; + +/** + * Unique identifier for an entropy source. + * + * Format: `entropy:{category}:{implementation}:{uuid}` where the UUID is a + * deterministic fingerprint of the underlying secret, or `'_'` for hardware + * wallets whose secret never leaves the device. + */ +export type EntropyId = string; + +/** + * Represents a source of entropy. + */ +export type Entropy = { + /** + * The unique identifier for this entropy source. + */ + id: EntropyId; + + /** + * The type of this entropy source. + */ + type: EntropyType; +}; + +/** + * An entropy source backed by a BIP-39 mnemonic (SRP). + */ +export type Bip44MnemonicEntropy = Entropy & { + type: 'bip44:mnemonic'; +}; + +/** + * Type guard for {@link Bip44MnemonicEntropy}. + * + * @param entropy - The entropy source to check. + * @returns `true` if the entropy source is a {@link Bip44MnemonicEntropy}. + */ +export function isBip44MnemonicEntropy( + entropy: Entropy, +): entropy is Bip44MnemonicEntropy { + return entropy.type === 'bip44:mnemonic'; +} diff --git a/packages/entropy-controller/src/utils.test.ts b/packages/entropy-controller/src/utils.test.ts new file mode 100644 index 00000000000..3c3449943b5 --- /dev/null +++ b/packages/entropy-controller/src/utils.test.ts @@ -0,0 +1,98 @@ +import { fingerprint, toEntropyFingerprint, toEntropyId } from './utils'; + +const SECRET = new Uint8Array(32).fill(1); + +describe('fingerprint', () => { + it('returns a valid UUID v4 string', async () => { + const fp = await fingerprint(SECRET); + expect(fp).toMatch( + /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u, + ); + }); + + it('is deterministic — same input always produces the same fingerprint', async () => { + const first = await fingerprint(SECRET); + const second = await fingerprint(SECRET); + expect(first).toBe(second); + }); + + it('produces different fingerprints for different secrets', async () => { + const otherSecret = new Uint8Array(32).fill(2); + const first = await fingerprint(SECRET); + const second = await fingerprint(otherSecret); + expect(first).not.toBe(second); + }); + + it('matches a known value to guard against accidental algorithm changes', async () => { + // uuid({ random: HMAC-SHA256( key=Uint8Array(32).fill(1), msg='metamask:fingerprint' ).slice(0, 16) }) + const fp = await fingerprint(SECRET); + expect(fp).toBe('29b22736-01c7-4096-9ae5-9f735d55b5a2'); + }); +}); + +describe('toEntropyFingerprint', () => { + it('returns a 64-character lowercase hex string', async () => { + const fp = await toEntropyFingerprint(SECRET); + expect(fp).toHaveLength(64); + expect(fp).toMatch(/^[0-9a-f]+$/u); + }); + + it('is deterministic — same input always produces the same fingerprint', async () => { + const first = await toEntropyFingerprint(SECRET); + const second = await toEntropyFingerprint(SECRET); + expect(first).toBe(second); + }); + + it('produces different fingerprints for different secrets', async () => { + const otherSecret = new Uint8Array(32).fill(2); + const first = await toEntropyFingerprint(SECRET); + const second = await toEntropyFingerprint(otherSecret); + expect(first).not.toBe(second); + }); + + it('matches a known value to guard against accidental algorithm changes', async () => { + // hex( HMAC-SHA256( key=Uint8Array(32).fill(1), msg='metamask:fingerprint' ) ) + const fp = await toEntropyFingerprint(SECRET); + expect(fp).toBe( + '29b2273601c75096dae59f735d55b5a24384e06ff17496130d059b1bd5915560', + ); + }); +}); + +describe('toEntropyId', () => { + it('returns the full entropy:category:implementation:uuid format', async () => { + const id = await toEntropyId('bip44', 'mnemonic', SECRET); + expect(id).toMatch( + /^entropy:bip44:mnemonic:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u, + ); + }); + + it('is deterministic — same inputs always produce the same ID', async () => { + const first = await toEntropyId('bip44', 'mnemonic', SECRET); + const second = await toEntropyId('bip44', 'mnemonic', SECRET); + expect(first).toBe(second); + }); + + it('produces different IDs for different secrets', async () => { + const otherSecret = new Uint8Array(32).fill(2); + const first = await toEntropyId('bip44', 'mnemonic', SECRET); + const second = await toEntropyId('bip44', 'mnemonic', otherSecret); + expect(first).not.toBe(second); + }); + + it('produces different IDs for different category:implementation combinations', async () => { + const mnemonicId = await toEntropyId('bip44', 'mnemonic', SECRET); + const privateKeyId = await toEntropyId('raw', 'private-key', SECRET); + expect(mnemonicId).not.toBe(privateKeyId); + }); + + it("uses '_' as the UUID segment when no material is provided (hardware wallets)", async () => { + const id = await toEntropyId('bip44', 'ledger'); + expect(id).toBe('entropy:bip44:ledger:_'); + }); + + it('matches a known value to guard against accidental algorithm changes', async () => { + const id = await toEntropyId('bip44', 'mnemonic', SECRET); + expect(id).toBe('entropy:bip44:mnemonic:29b22736-01c7-4096-9ae5-9f735d55b5a2'); + }); +}); diff --git a/packages/entropy-controller/src/utils.ts b/packages/entropy-controller/src/utils.ts new file mode 100644 index 00000000000..b268e56e339 --- /dev/null +++ b/packages/entropy-controller/src/utils.ts @@ -0,0 +1,65 @@ +import { hmac } from '@noble/hashes/hmac'; +import { sha256 } from '@noble/hashes/sha256'; +import { bytesToHex } from '@noble/hashes/utils'; +import { v4 as uuid } from 'uuid'; + +import type { EntropyCategory, EntropyId, EntropyImplementation } from './types'; + +/** + * Computes a deterministic, non-reversible fingerprint for a piece of entropy. + * + * The fingerprint is a UUID v4 seeded from the first 16 bytes of + * `HMAC-SHA256(key=material, msg='metamask:fingerprint')`. + * + * `@noble/hashes` is synchronous today, but the async signature is kept as a + * forward-compatible seam: a future migration to the Web Crypto API (or any + * other async primitive) won't require changes at every call site. + * + * @param material - The raw entropy bytes (e.g. BIP-39 mnemonic bytes). + * @returns A deterministic UUID v4 string that uniquely identifies the entropy + * without exposing it. + */ +export async function fingerprint(material: Uint8Array): Promise { + const message = new TextEncoder().encode('metamask:fingerprint'); + const digest = hmac(sha256, material, message); + return uuid({ random: digest.slice(0, 16) }); +} + +/** + * Computes a deterministic, non-reversible hex fingerprint for a piece of + * entropy. Suitable for comparison and auditing. + * + * @param material - The raw entropy bytes. + * @returns The lowercase hex-encoded 32-byte HMAC-SHA256 digest. + */ +export async function toEntropyFingerprint( + material: Uint8Array, +): Promise { + const message = new TextEncoder().encode('metamask:fingerprint'); + const digest = hmac(sha256, material, message); + return bytesToHex(digest); +} + +/** + * Computes a stable {@link EntropyId} for an entropy source. + * + * The ID is formatted as `entropy:{category}:{implementation}:{uuid}`, where + * the UUID segment is the {@link fingerprint} of `material` when provided, or + * `'_'` for entropy sources whose secret never leaves the device (e.g. hardware + * wallets). + * + * @param category - The entropy category (e.g. `'bip44'`, `'raw'`). + * @param implementation - The entropy implementation (e.g. `'mnemonic'`, + * `'ledger'`, `'private-key'`). + * @param material - The raw entropy bytes. Omit for hardware wallets or any + * entropy source where the secret is not directly accessible. + * @returns A stable {@link EntropyId} string. + */ +export async function toEntropyId( + category: EntropyCategory, + implementation: EntropyImplementation, + material?: Uint8Array, +): Promise { + const uuidSegment = material ? await fingerprint(material) : '_'; + return `entropy:${category}:${implementation}:${uuidSegment}`; +} diff --git a/packages/entropy-controller/tsconfig.build.json b/packages/entropy-controller/tsconfig.build.json new file mode 100644 index 00000000000..fb01dfc7b16 --- /dev/null +++ b/packages/entropy-controller/tsconfig.build.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "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/entropy-controller/tsconfig.json b/packages/entropy-controller/tsconfig.json new file mode 100644 index 00000000000..11613f15d5b --- /dev/null +++ b/packages/entropy-controller/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "references": [ + { + "path": "../base-controller" + }, + { + "path": "../keyring-controller" + }, + { + "path": "../messenger" + } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/entropy-controller/typedoc.json b/packages/entropy-controller/typedoc.json new file mode 100644 index 00000000000..c9da015dbf8 --- /dev/null +++ b/packages/entropy-controller/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["./src/index.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs", + "tsconfig": "./tsconfig.build.json" +} diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 187ddc96f12..1c69f69995c 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -59,6 +59,7 @@ "@metamask/base-controller": "^9.1.0", "@metamask/browser-passworder": "^6.0.0", "@metamask/controller-utils": "^12.3.0", + "@metamask/entropy-controller": "^0.0.0", "@metamask/eth-hd-keyring": "^14.1.1", "@metamask/eth-sig-util": "^8.2.0", "@metamask/eth-simple-keyring": "^12.0.2", diff --git a/packages/keyring-controller/src/KeyringController.test.ts b/packages/keyring-controller/src/KeyringController.test.ts index 1052dd7b082..a97e74b5245 100644 --- a/packages/keyring-controller/src/KeyringController.test.ts +++ b/packages/keyring-controller/src/KeyringController.test.ts @@ -42,11 +42,9 @@ import { buildMockTransaction } from '../tests/mocks/mockTransaction'; import { KeyringControllerErrorMessage } from './constants'; import { KeyringControllerError } from './errors'; import type { - KeyringControllerEvents, KeyringControllerMessenger, KeyringControllerState, KeyringControllerOptions, - KeyringControllerActions, KeyringMetadata, SerializedKeyring, KeyringSelector, @@ -6155,7 +6153,16 @@ function stubKeyringClassWithAccount( * @returns The root messenger. */ function buildRootMessenger(): RootMessenger { - return new Messenger({ namespace: MOCK_ANY_NAMESPACE }); + const messenger = new Messenger({ namespace: MOCK_ANY_NAMESPACE }); + messenger.registerActionHandler( + 'EntropyController:addEntropy', + jest.fn(), + ); + messenger.registerActionHandler( + 'EntropyController:removeEntropy', + jest.fn(), + ); + return messenger; } /** @@ -6167,18 +6174,23 @@ function buildRootMessenger(): RootMessenger { */ function buildKeyringControllerMessenger( messenger = buildRootMessenger(), -): Messenger< - 'KeyringController', - KeyringControllerActions, - KeyringControllerEvents, - typeof messenger -> { - return new Messenger< +): KeyringControllerMessenger { + const kcMessenger = new Messenger< 'KeyringController', - KeyringControllerActions, - KeyringControllerEvents, + AllKeyringControllerActions, + AllKeyringControllerEvents, typeof messenger >({ namespace: 'KeyringController', parent: messenger }); + + messenger.delegate({ + messenger: kcMessenger, + actions: [ + 'EntropyController:addEntropy', + 'EntropyController:removeEntropy', + ], + }); + + return kcMessenger as KeyringControllerMessenger; } /** diff --git a/packages/keyring-controller/src/KeyringController.ts b/packages/keyring-controller/src/KeyringController.ts index a16ce8802bf..de01ce59a48 100644 --- a/packages/keyring-controller/src/KeyringController.ts +++ b/packages/keyring-controller/src/KeyringController.ts @@ -15,10 +15,8 @@ import type { EthUserOperationPatch, KeyringAccount, } from '@metamask/keyring-api'; -import type { - Keyring as KeyringV2, - KeyringType, -} from '@metamask/keyring-api/v2'; +import { KeyringType } from '@metamask/keyring-api/v2'; +import type { Keyring as KeyringV2 } from '@metamask/keyring-api/v2'; import type { EthKeyring } from '@metamask/keyring-internal-api'; import type { Keyring, KeyringClass } from '@metamask/keyring-utils'; import type { Messenger } from '@metamask/messenger'; @@ -35,6 +33,13 @@ import { isValidJson, remove0x, } from '@metamask/utils'; +import { toEntropyId } from '@metamask/entropy-controller'; +import type { + Bip44MnemonicEntropy, + EntropyControllerAddEntropyAction, + EntropyControllerRemoveEntropyAction, + EntropyId, +} from '@metamask/entropy-controller'; import { Mutex } from 'async-mutex'; import type { MutexInterface } from 'async-mutex'; import Wallet, { thirdparty as importers } from 'ethereumjs-wallet'; @@ -198,7 +203,9 @@ export type KeyringControllerEvents = export type KeyringControllerMessenger = Messenger< typeof name, - KeyringControllerActions, + | KeyringControllerActions + | EntropyControllerAddEntropyAction + | EntropyControllerRemoveEntropyAction, KeyringControllerEvents >; @@ -766,6 +773,40 @@ function normalize(address: string): string | undefined { return isEthAddress(address) ? ethNormalize(address) : address; } +/** + * Returns `true` and narrows `keyringV2` to `HdKeyringV2 & { mnemonic: Uint8Array }` when + * the entry is a loaded HD keyring with accessible mnemonic bytes. + * + * @param keyring - The v1 keyring instance. + * @param keyringV2 - The optional v2 keyring instance. + * @returns `true` if the entry is an HD keyring v2 with an accessible mnemonic. + */ +function isHdKeyringV2( + keyring: EthKeyring, + keyringV2: KeyringV2 | undefined, +): keyringV2 is HdKeyringV2 & { mnemonic: Uint8Array } { + return ( + keyring.type === KeyringTypes.hd && + keyringV2?.type === KeyringType.Hd && + Boolean(keyringV2.mnemonic) + ); +} + +/** + * Computes the `EntropyId` for a BIP-44 mnemonic entropy source. + * + * Thin wrapper around {@link toEntropyId} that encodes the `'bip44'` category + * and `'mnemonic'` implementation so call sites don't repeat them. + * + * @param mnemonic - The raw mnemonic bytes. + * @returns The stable `EntropyId` for this mnemonic. + */ +async function toBip44MnemonicEntropyId( + mnemonic: Uint8Array, +): Promise { + return toEntropyId('bip44', 'mnemonic', mnemonic); +} + /** * Controller responsible for establishing and managing user identity. * @@ -1026,9 +1067,14 @@ export class KeyringController< ): Promise { this.#assertIsUnlocked(); - return this.#getKeyringMetadata( - await this.#persistOrRollback(async () => this.#newKeyring(type, opts)), + const keyring = await this.#persistOrRollback(async () => + this.#newKeyring(type, opts), ); + const entry = this.#keyrings.find((ke) => ke.keyring === keyring); + if (entry) { + await this.#registerEntropySource(entry); + } + return this.#getKeyringMetadata(keyring); } /** @@ -1338,7 +1384,10 @@ export class KeyringController< args: any[], ): Promise { this.#assertIsUnlocked(); - return this.#persistOrRollback(async () => { + + let capturedKeyring: EthKeyring | undefined; + + const result = await this.#persistOrRollback(async () => { let privateKey; switch (strategy) { case AccountImportStrategy.privateKey: { @@ -1389,9 +1438,19 @@ export class KeyringController< const newKeyring = await this.#newKeyring(KeyringTypes.simple, [ privateKey, ]); + capturedKeyring = newKeyring; const accounts = await newKeyring.getAccounts(); return accounts[0]; }); + + if (capturedKeyring) { + const entry = this.#keyrings.find((ke) => ke.keyring === capturedKeyring); + if (entry) { + await this.#registerEntropySource(entry); + } + } + + return result; } /** @@ -1404,6 +1463,8 @@ export class KeyringController< async removeAccount(address: string): Promise { this.#assertIsUnlocked(); + let removedEntropy: EntropyId | undefined; + await this.#persistOrRollback(async () => { const keyringIndex = await this.#findKeyringIndexForAccount(address); @@ -1442,12 +1503,18 @@ export class KeyringController< await keyring.removeAccount(address as Hex); if (shouldRemoveKeyring) { + if (isHdKeyringV2(keyring, keyringV2)) { + removedEntropy = await toBip44MnemonicEntropyId(keyringV2.mnemonic); + } this.#keyrings.splice(keyringIndex, 1); await this.#destroyKeyring(keyring, keyringV2); } }); this.messenger.publish(`${name}:accountRemoved`, address); + if (removedEntropy) { + this.messenger.call('EntropyController:removeEntropy', removedEntropy); + } } /** @@ -1775,6 +1842,10 @@ export class KeyringController< // since the controller is already unlocked. console.error('Failed to update vault during login:', error); } + + for (const entry of this.#keyrings) { + await this.#registerEntropySource(entry); + } } /** @@ -1825,6 +1896,10 @@ export class KeyringController< // since the controller is already unlocked. console.error('Failed to update vault during login:', error); } + + for (const entry of this.#keyrings) { + await this.#registerEntropySource(entry); + } } /** @@ -2206,10 +2281,11 @@ export class KeyringController< ): Promise { this.#assertIsUnlocked(); - return this.#persistOrRollback(async () => { - // Track created and removed keyrings during the operation execution. - const createdEntries = new Set(); - const removedEntries = new Set(); + const createdEntries = new Set(); + const removedEntries = new Set(); + const removedEntropies: EntropyId[] = []; + + const result = await this.#persistOrRollback(async () => { // Copy of the current keyrings that is mutated during the operation execution. const restrictedEntries = [...this.#keyrings]; @@ -2269,15 +2345,21 @@ export class KeyringController< ); }; - let result: CallbackResult; + let callbackResult: CallbackResult; try { - result = await operation(restrictedController); + callbackResult = await operation(restrictedController); } catch (error) { await destroyKeyrings(createdEntries); throw error; } + for (const { keyring, keyringV2 } of removedEntries) { + if (isHdKeyringV2(keyring, keyringV2)) { + removedEntropies.push(await toBip44MnemonicEntropyId(keyringV2.mnemonic)); + } + } + await destroyKeyrings(removedEntries); // We update the real keyrings only after the operation completes successfully, so that @@ -2292,14 +2374,23 @@ export class KeyringController< // still have references to them. ...removedEntries, ]) { - this.#assertNoUnsafeDirectKeyringAccess(result, keyring); + this.#assertNoUnsafeDirectKeyringAccess(callbackResult, keyring); if (keyringV2) { - this.#assertNoUnsafeDirectKeyringAccess(result, keyringV2); + this.#assertNoUnsafeDirectKeyringAccess(callbackResult, keyringV2); } } - return result; + return callbackResult; }); + + for (const entry of createdEntries) { + await this.#registerEntropySource(entry); + } + for (const entropyId of removedEntropies) { + this.messenger.call('EntropyController:removeEntropy', entropyId); + } + + return result; } /** @@ -2315,6 +2406,33 @@ export class KeyringController< return keyring.type; } + /** + * Registers an HD keyring's entropy source with `EntropyController`. + * + * Called after each successful persist. Non-HD keyrings are skipped. + * If `EntropyController` is not wired into the consumer's messenger, the + * call throws and the error propagates. + * + * @param entry - The keyring entry to register. + */ + async #registerEntropySource(entry: KeyringEntry): Promise { + const { keyring, keyringV2 } = entry; + + if (!isHdKeyringV2(keyring, keyringV2)) { + return; + } + + const entropy: Bip44MnemonicEntropy = { + type: 'bip44:mnemonic', + id: await toBip44MnemonicEntropyId(keyringV2.mnemonic), + }; + + this.messenger.call('EntropyController:addEntropy', entropy); + + // TODO: set keyringV2.entropySource = entropy.id once @metamask/eth-hd-keyring/v2 + // exposes a setter or constructor option. + } + /** * Constructor helper for registering this controller's messeger * actions. diff --git a/packages/keyring-controller/tsconfig.build.json b/packages/keyring-controller/tsconfig.build.json index df01f3c175b..55cbd978044 100644 --- a/packages/keyring-controller/tsconfig.build.json +++ b/packages/keyring-controller/tsconfig.build.json @@ -12,6 +12,9 @@ { "path": "../controller-utils/tsconfig.build.json" }, + { + "path": "../entropy-controller/tsconfig.build.json" + }, { "path": "../messenger/tsconfig.build.json" } diff --git a/packages/keyring-controller/tsconfig.json b/packages/keyring-controller/tsconfig.json index 7d7c67c579c..2d67095f9c4 100644 --- a/packages/keyring-controller/tsconfig.json +++ b/packages/keyring-controller/tsconfig.json @@ -10,6 +10,9 @@ { "path": "../controller-utils" }, + { + "path": "../entropy-controller" + }, { "path": "../messenger" } diff --git a/packages/wallet/package.json b/packages/wallet/package.json index 12526c5eecc..19e2932df6b 100644 --- a/packages/wallet/package.json +++ b/packages/wallet/package.json @@ -62,6 +62,7 @@ "@metamask/browser-passworder": "^6.0.0", "@metamask/connectivity-controller": "^0.3.0", "@metamask/controller-utils": "^12.3.0", + "@metamask/entropy-controller": "^0.0.0", "@metamask/gas-fee-controller": "^26.2.4", "@metamask/keyring-controller": "^27.1.0", "@metamask/messenger": "^2.0.0", diff --git a/packages/wallet/src/initialization/instances/entropy-controller/entropy-controller.ts b/packages/wallet/src/initialization/instances/entropy-controller/entropy-controller.ts new file mode 100644 index 00000000000..057f1e9be46 --- /dev/null +++ b/packages/wallet/src/initialization/instances/entropy-controller/entropy-controller.ts @@ -0,0 +1,29 @@ +import { + EntropyController, + EntropyControllerMessenger, +} from '@metamask/entropy-controller'; +import { Messenger } from '@metamask/messenger'; + +import type { + DefaultActions, + DefaultEvents, + RootMessenger, +} from '../../defaults'; +import type { InitializationConfiguration } from '../../types'; + +export const entropyController: InitializationConfiguration< + EntropyController, + EntropyControllerMessenger +> = { + name: 'EntropyController', + init: ({ state, messenger }) => + new EntropyController({ + state, + messenger, + }), + getMessenger: (parent: RootMessenger) => + new Messenger({ + namespace: 'EntropyController', + parent, + }) as EntropyControllerMessenger, +}; diff --git a/packages/wallet/src/initialization/instances/index.ts b/packages/wallet/src/initialization/instances/index.ts index d45bb0917b2..0903e17bf6c 100644 --- a/packages/wallet/src/initialization/instances/index.ts +++ b/packages/wallet/src/initialization/instances/index.ts @@ -2,6 +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 { connectivityController } from './connectivity-controller/connectivity-controller.js'; +export { entropyController } from './entropy-controller/entropy-controller'; export { gasFeeController } from './gas-fee-controller/gas-fee-controller.js'; export { keyringController } from './keyring-controller/keyring-controller.js'; export { networkController } from './network-controller/network-controller.js'; diff --git a/packages/wallet/tsconfig.build.json b/packages/wallet/tsconfig.build.json index dae6d88285a..0e301917cf3 100644 --- a/packages/wallet/tsconfig.build.json +++ b/packages/wallet/tsconfig.build.json @@ -11,6 +11,7 @@ { "path": "../approval-controller/tsconfig.build.json" }, { "path": "../base-controller/tsconfig.build.json" }, { "path": "../connectivity-controller/tsconfig.build.json" }, + { "path": "../entropy-controller/tsconfig.build.json" }, { "path": "../controller-utils/tsconfig.build.json" }, { "path": "../gas-fee-controller/tsconfig.build.json" }, { "path": "../keyring-controller/tsconfig.build.json" }, diff --git a/packages/wallet/tsconfig.json b/packages/wallet/tsconfig.json index 10221177468..94ff66cd95c 100644 --- a/packages/wallet/tsconfig.json +++ b/packages/wallet/tsconfig.json @@ -19,6 +19,9 @@ { "path": "../connectivity-controller" }, + { + "path": "../entropy-controller" + }, { "path": "../controller-utils" }, diff --git a/tsconfig.build.json b/tsconfig.build.json index 5e805550062..6aa2c96d9a2 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -112,6 +112,9 @@ { "path": "./packages/ens-controller/tsconfig.build.json" }, + { + "path": "./packages/entropy-controller/tsconfig.build.json" + }, { "path": "./packages/eth-block-tracker/tsconfig.build.json" }, diff --git a/yarn.lock b/yarn.lock index 13a1e8014a0..c6f782ec438 100644 --- a/yarn.lock +++ b/yarn.lock @@ -255,7 +255,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.27.1, @babel/code-frame@npm:^7.29.7": version: 7.29.7 resolution: "@babel/code-frame@npm:7.29.7" dependencies: @@ -273,7 +273,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.21.3, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.9, @babel/core@npm:^7.27.4": +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.21.3, @babel/core@npm:^7.23.2, @babel/core@npm:^7.23.9, @babel/core@npm:^7.25.9, @babel/core@npm:^7.27.4": version: 7.29.7 resolution: "@babel/core@npm:7.29.7" dependencies: @@ -296,7 +296,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.27.5, @babel/generator@npm:^7.29.7": +"@babel/generator@npm:^7.25.9, @babel/generator@npm:^7.27.5, @babel/generator@npm:^7.29.7, @babel/generator@npm:^7.7.2": version: 7.29.7 resolution: "@babel/generator@npm:7.29.7" dependencies: @@ -510,7 +510,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.29.7": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.9, @babel/parser@npm:^7.29.7": version: 7.29.7 resolution: "@babel/parser@npm:7.29.7" dependencies: @@ -700,7 +700,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.27.1, @babel/plugin-syntax-jsx@npm:^7.29.7": +"@babel/plugin-syntax-jsx@npm:^7.27.1, @babel/plugin-syntax-jsx@npm:^7.29.7, @babel/plugin-syntax-jsx@npm:^7.7.2": version: 7.29.7 resolution: "@babel/plugin-syntax-jsx@npm:7.29.7" dependencies: @@ -799,7 +799,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.27.1, @babel/plugin-syntax-typescript@npm:^7.29.7": +"@babel/plugin-syntax-typescript@npm:^7.27.1, @babel/plugin-syntax-typescript@npm:^7.29.7, @babel/plugin-syntax-typescript@npm:^7.7.2": version: 7.29.7 resolution: "@babel/plugin-syntax-typescript@npm:7.29.7" dependencies: @@ -1646,7 +1646,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.29.7": +"@babel/template@npm:^7.29.7, @babel/template@npm:^7.3.3": version: 7.29.7 resolution: "@babel/template@npm:7.29.7" dependencies: @@ -1672,7 +1672,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.23.0, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.7, @babel/types@npm:^7.4.4": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.23.0, @babel/types@npm:^7.27.3, @babel/types@npm:^7.28.2, @babel/types@npm:^7.29.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.29.7 resolution: "@babel/types@npm:7.29.7" dependencies: @@ -4728,6 +4728,20 @@ __metadata: languageName: node linkType: hard +"@jest/console@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/console@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10/4a80c750e8a31f344233cb9951dee9b77bf6b89377cb131f8b3cde07ff218f504370133a5963f6a786af4d2ce7f85642db206ff7a15f99fe58df4c38ac04899e + languageName: node + linkType: hard + "@jest/core@npm:30.4.2": version: 30.4.2 resolution: "@jest/core@npm:30.4.2" @@ -4769,6 +4783,47 @@ __metadata: languageName: node linkType: hard +"@jest/core@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/core@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/reporters": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-changed-files: "npm:^29.7.0" + jest-config: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-resolve-dependencies: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-ansi: "npm:^6.0.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10/ab6ac2e562d083faac7d8152ec1cc4eccc80f62e9579b69ed40aedf7211a6b2d57024a6cd53c4e35fd051c39a236e86257d1d99ebdb122291969a0a04563b51e + languageName: node + linkType: hard + "@jest/diff-sequences@npm:30.4.0": version: 30.4.0 resolution: "@jest/diff-sequences@npm:30.4.0" @@ -4809,6 +4864,18 @@ __metadata: languageName: node linkType: hard +"@jest/environment@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/environment@npm:29.7.0" + dependencies: + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + checksum: 10/90b5844a9a9d8097f2cf107b1b5e57007c552f64315da8c1f51217eeb0a9664889d3f145cdf8acf23a84f4d8309a6675e27d5b059659a004db0ea9546d1c81a8 + languageName: node + linkType: hard + "@jest/expect-utils@npm:30.4.1": version: 30.4.1 resolution: "@jest/expect-utils@npm:30.4.1" @@ -4818,6 +4885,15 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect-utils@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + checksum: 10/ef8d379778ef574a17bde2801a6f4469f8022a46a5f9e385191dc73bb1fc318996beaed4513fbd7055c2847227a1bed2469977821866534593a6e52a281499ee + languageName: node + linkType: hard + "@jest/expect@npm:30.4.1": version: 30.4.1 resolution: "@jest/expect@npm:30.4.1" @@ -4828,6 +4904,16 @@ __metadata: languageName: node linkType: hard +"@jest/expect@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/expect@npm:29.7.0" + dependencies: + expect: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + checksum: 10/fea6c3317a8da5c840429d90bfe49d928e89c9e89fceee2149b93a11b7e9c73d2f6e4d7cdf647163da938fc4e2169e4490be6bae64952902bc7a701033fd4880 + languageName: node + linkType: hard + "@jest/fake-timers@npm:30.4.1": version: 30.4.1 resolution: "@jest/fake-timers@npm:30.4.1" @@ -4842,6 +4928,20 @@ __metadata: languageName: node linkType: hard +"@jest/fake-timers@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/fake-timers@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@sinonjs/fake-timers": "npm:^10.0.2" + "@types/node": "npm:*" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10/9b394e04ffc46f91725ecfdff34c4e043eb7a16e1d78964094c9db3fde0b1c8803e45943a980e8c740d0a3d45661906de1416ca5891a538b0660481a3a828c27 + languageName: node + linkType: hard + "@jest/get-type@npm:30.1.0": version: 30.1.0 resolution: "@jest/get-type@npm:30.1.0" @@ -4861,6 +4961,18 @@ __metadata: languageName: node linkType: hard +"@jest/globals@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/globals@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + jest-mock: "npm:^29.7.0" + checksum: 10/97dbb9459135693ad3a422e65ca1c250f03d82b2a77f6207e7fa0edd2c9d2015fbe4346f3dc9ebff1678b9d8da74754d4d440b7837497f8927059c0642a22123 + languageName: node + linkType: hard + "@jest/pattern@npm:30.4.0": version: 30.4.0 resolution: "@jest/pattern@npm:30.4.0" @@ -4907,6 +5019,43 @@ __metadata: languageName: node linkType: hard +"@jest/reporters@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/reporters@npm:29.7.0" + dependencies: + "@bcoe/v8-coverage": "npm:^0.2.3" + "@jest/console": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + collect-v8-coverage: "npm:^1.0.0" + exit: "npm:^0.1.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + istanbul-lib-coverage: "npm:^3.0.0" + istanbul-lib-instrument: "npm:^6.0.0" + istanbul-lib-report: "npm:^3.0.0" + istanbul-lib-source-maps: "npm:^4.0.0" + istanbul-reports: "npm:^3.1.3" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + slash: "npm:^3.0.0" + string-length: "npm:^4.0.1" + strip-ansi: "npm:^6.0.0" + v8-to-istanbul: "npm:^9.0.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 10/a17d1644b26dea14445cedd45567f4ba7834f980be2ef74447204e14238f121b50d8b858fde648083d2cd8f305f81ba434ba49e37a5f4237a6f2a61180cc73dc + languageName: node + linkType: hard + "@jest/schemas@npm:30.4.1": version: 30.4.1 resolution: "@jest/schemas@npm:30.4.1" @@ -4948,6 +5097,17 @@ __metadata: languageName: node linkType: hard +"@jest/source-map@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/source-map@npm:29.6.3" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.18" + callsites: "npm:^3.0.0" + graceful-fs: "npm:^4.2.9" + checksum: 10/bcc5a8697d471396c0003b0bfa09722c3cd879ad697eb9c431e6164e2ea7008238a01a07193dfe3cbb48b1d258eb7251f6efcea36f64e1ebc464ea3c03ae2deb + languageName: node + linkType: hard + "@jest/test-result@npm:30.4.1": version: 30.4.1 resolution: "@jest/test-result@npm:30.4.1" @@ -4960,6 +5120,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-result@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-result@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/istanbul-lib-coverage": "npm:^2.0.0" + collect-v8-coverage: "npm:^1.0.0" + checksum: 10/c073ab7dfe3c562bff2b8fee6cc724ccc20aa96bcd8ab48ccb2aa309b4c0c1923a9e703cea386bd6ae9b71133e92810475bb9c7c22328fc63f797ad3324ed189 + languageName: node + linkType: hard + "@jest/test-sequencer@npm:30.4.1": version: 30.4.1 resolution: "@jest/test-sequencer@npm:30.4.1" @@ -4972,6 +5144,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-sequencer@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/test-sequencer@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + slash: "npm:^3.0.0" + checksum: 10/4420c26a0baa7035c5419b0892ff8ffe9a41b1583ec54a10db3037cd46a7e29dd3d7202f8aa9d376e9e53be5f8b1bc0d16e1de6880a6d319b033b01dc4c8f639 + languageName: node + linkType: hard + "@jest/transform@npm:30.4.1": version: 30.4.1 resolution: "@jest/transform@npm:30.4.1" @@ -4994,6 +5178,29 @@ __metadata: languageName: node linkType: hard +"@jest/transform@npm:^29.7.0": + version: 29.7.0 + resolution: "@jest/transform@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/types": "npm:^29.6.3" + "@jridgewell/trace-mapping": "npm:^0.3.18" + babel-plugin-istanbul: "npm:^6.1.1" + chalk: "npm:^4.0.0" + convert-source-map: "npm:^2.0.0" + fast-json-stable-stringify: "npm:^2.1.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + pirates: "npm:^4.0.4" + slash: "npm:^3.0.0" + write-file-atomic: "npm:^4.0.2" + checksum: 10/30f42293545ab037d5799c81d3e12515790bb58513d37f788ce32d53326d0d72ebf5b40f989e6896739aa50a5f77be44686e510966370d58511d5ad2637c68c1 + languageName: node + linkType: hard + "@jest/types@npm:30.4.1": version: 30.4.1 resolution: "@jest/types@npm:30.4.1" @@ -6684,6 +6891,30 @@ __metadata: languageName: unknown linkType: soft +"@metamask/entropy-controller@npm:^0.0.0, @metamask/entropy-controller@workspace:packages/entropy-controller": + version: 0.0.0-use.local + resolution: "@metamask/entropy-controller@workspace:packages/entropy-controller" + dependencies: + "@metamask/auto-changelog": "npm:^6.1.0" + "@metamask/base-controller": "npm:^9.1.0" + "@metamask/eth-hd-keyring": "npm:^14.1.1" + "@metamask/keyring-controller": "npm:^27.1.0" + "@metamask/messenger": "npm:^2.0.0" + "@metamask/utils": "npm:^11.11.0" + "@noble/hashes": "npm:^1.8.0" + "@ts-bridge/cli": "npm:^0.6.4" + "@types/jest": "npm:^29.5.14" + deepmerge: "npm:^4.2.2" + jest: "npm:^29.7.0" + ts-jest: "npm:^29.2.5" + tsx: "npm:^4.20.5" + typedoc: "npm:^0.25.13" + typedoc-plugin-missing-exports: "npm:^2.0.0" + typescript: "npm:~5.3.3" + uuid: "npm:^8.3.2" + languageName: unknown + linkType: soft + "@metamask/eslint-config-jest@npm:^15.0.0": version: 15.0.0 resolution: "@metamask/eslint-config-jest@npm:15.0.0" @@ -7323,6 +7554,7 @@ __metadata: "@metamask/base-controller": "npm:^9.1.0" "@metamask/browser-passworder": "npm:^6.0.0" "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/entropy-controller": "npm:^0.0.0" "@metamask/eth-hd-keyring": "npm:^14.1.1" "@metamask/eth-sig-util": "npm:^8.2.0" "@metamask/eth-simple-keyring": "npm:^12.0.2" @@ -9261,6 +9493,7 @@ __metadata: "@metamask/browser-passworder": "npm:^6.0.0" "@metamask/connectivity-controller": "npm:^0.3.0" "@metamask/controller-utils": "npm:^12.3.0" + "@metamask/entropy-controller": "npm:^0.0.0" "@metamask/gas-fee-controller": "npm:^26.2.4" "@metamask/keyring-controller": "npm:^27.1.0" "@metamask/messenger": "npm:^2.0.0" @@ -10700,7 +10933,7 @@ __metadata: languageName: node linkType: hard -"@sinonjs/commons@npm:^3.0.1": +"@sinonjs/commons@npm:^3.0.0, @sinonjs/commons@npm:^3.0.1": version: 3.0.1 resolution: "@sinonjs/commons@npm:3.0.1" dependencies: @@ -10709,6 +10942,15 @@ __metadata: languageName: node linkType: hard +"@sinonjs/fake-timers@npm:^10.0.2": + version: 10.3.0 + resolution: "@sinonjs/fake-timers@npm:10.3.0" + dependencies: + "@sinonjs/commons": "npm:^3.0.0" + checksum: 10/78155c7bd866a85df85e22028e046b8d46cf3e840f72260954f5e3ed5bd97d66c595524305a6841ffb3f681a08f6e5cef572a2cce5442a8a232dc29fb409b83e + languageName: node + linkType: hard + "@sinonjs/fake-timers@npm:^15.4.0": version: 15.4.0 resolution: "@sinonjs/fake-timers@npm:15.4.0" @@ -11172,7 +11414,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7.20.5": +"@types/babel__core@npm:^7.1.14, @types/babel__core@npm:^7.20.5": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" dependencies: @@ -11204,7 +11446,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__traverse@npm:*": +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": version: 7.28.0 resolution: "@types/babel__traverse@npm:7.28.0" dependencies: @@ -11374,6 +11616,15 @@ __metadata: languageName: node linkType: hard +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.9 + resolution: "@types/graceful-fs@npm:4.1.9" + dependencies: + "@types/node": "npm:*" + checksum: 10/79d746a8f053954bba36bd3d94a90c78de995d126289d656fb3271dd9f1229d33f678da04d10bce6be440494a5a73438e2e363e92802d16b8315b051036c5256 + languageName: node + linkType: hard + "@types/gtag.js@npm:^0.0.20": version: 0.0.20 resolution: "@types/gtag.js@npm:0.0.20" @@ -11471,6 +11722,16 @@ __metadata: languageName: node linkType: hard +"@types/jest@npm:^29.5.14": + version: 29.5.14 + resolution: "@types/jest@npm:29.5.14" + dependencies: + expect: "npm:^29.0.0" + pretty-format: "npm:^29.0.0" + checksum: 10/59ec7a9c4688aae8ee529316c43853468b6034f453d08a2e1064b281af9c81234cec986be796288f1bbb29efe943bc950e70c8fa8faae1e460d50e3cf9760f9b + languageName: node + linkType: hard + "@types/jsdom@npm:^21.1.7": version: 21.1.7 resolution: "@types/jsdom@npm:21.1.7" @@ -11760,7 +12021,7 @@ __metadata: languageName: node linkType: hard -"@types/stack-utils@npm:^2.0.3": +"@types/stack-utils@npm:^2.0.0, @types/stack-utils@npm:^2.0.3": version: 2.0.3 resolution: "@types/stack-utils@npm:2.0.3" checksum: 10/72576cc1522090fe497337c2b99d9838e320659ac57fa5560fcbdcbafcf5d0216c6b3a0a8a4ee4fdb3b1f5e3420aa4f6223ab57b82fef3578bec3206425c6cf5 @@ -12547,7 +12808,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.3.2": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.2": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -12595,7 +12856,7 @@ __metadata: languageName: node linkType: hard -"ansi-styles@npm:^5.2.0": +"ansi-styles@npm:^5.0.0, ansi-styles@npm:^5.2.0": version: 5.2.0 resolution: "ansi-styles@npm:5.2.0" checksum: 10/d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 @@ -12616,7 +12877,7 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": +"anymatch@npm:^3.0.3, anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" dependencies: @@ -12802,6 +13063,23 @@ __metadata: languageName: node linkType: hard +"babel-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "babel-jest@npm:29.7.0" + dependencies: + "@jest/transform": "npm:^29.7.0" + "@types/babel__core": "npm:^7.1.14" + babel-plugin-istanbul: "npm:^6.1.1" + babel-preset-jest: "npm:^29.6.3" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + slash: "npm:^3.0.0" + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 10/8a0953bd813b3a8926008f7351611055548869e9a53dd36d6e7e96679001f71e65fd7dbfe253265c3ba6a4e630dc7c845cf3e78b17d758ef1880313ce8fba258 + languageName: node + linkType: hard + "babel-loader@npm:^9.2.1": version: 9.2.1 resolution: "babel-loader@npm:9.2.1" @@ -12824,6 +13102,19 @@ __metadata: languageName: node linkType: hard +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": "npm:^7.0.0" + "@istanbuljs/load-nyc-config": "npm:^1.0.0" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-instrument: "npm:^5.0.4" + test-exclude: "npm:^6.0.0" + checksum: 10/ffd436bb2a77bbe1942a33245d770506ab2262d9c1b3c1f1da7f0592f78ee7445a95bc2efafe619dd9c1b6ee52c10033d6c7d29ddefe6f5383568e60f31dfe8d + languageName: node + linkType: hard + "babel-plugin-istanbul@npm:^7.0.1": version: 7.0.1 resolution: "babel-plugin-istanbul@npm:7.0.1" @@ -12846,6 +13137,18 @@ __metadata: languageName: node linkType: hard +"babel-plugin-jest-hoist@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-plugin-jest-hoist@npm:29.6.3" + dependencies: + "@babel/template": "npm:^7.3.3" + "@babel/types": "npm:^7.3.3" + "@types/babel__core": "npm:^7.1.14" + "@types/babel__traverse": "npm:^7.0.6" + checksum: 10/9bfa86ec4170bd805ab8ca5001ae50d8afcb30554d236ba4a7ffc156c1a92452e220e4acbd98daefc12bf0216fccd092d0a2efed49e7e384ec59e0597a926d65 + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs2@npm:^0.4.14, babel-plugin-polyfill-corejs2@npm:^0.4.15": version: 0.4.17 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.17" @@ -12894,7 +13197,7 @@ __metadata: languageName: node linkType: hard -"babel-preset-current-node-syntax@npm:^1.2.0": +"babel-preset-current-node-syntax@npm:^1.0.0, babel-preset-current-node-syntax@npm:^1.2.0": version: 1.2.0 resolution: "babel-preset-current-node-syntax@npm:1.2.0" dependencies: @@ -12931,6 +13234,18 @@ __metadata: languageName: node linkType: hard +"babel-preset-jest@npm:^29.6.3": + version: 29.6.3 + resolution: "babel-preset-jest@npm:29.6.3" + dependencies: + babel-plugin-jest-hoist: "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 10/aa4ff2a8a728d9d698ed521e3461a109a1e66202b13d3494e41eea30729a5e7cc03b3a2d56c594423a135429c37bf63a9fa8b0b9ce275298be3095a88c69f6fb + languageName: node + linkType: hard + "babel-runtime@npm:^6.26.0": version: 6.26.0 resolution: "babel-runtime@npm:6.26.0" @@ -13713,10 +14028,10 @@ __metadata: languageName: node linkType: hard -"cjs-module-lexer@npm:^1.3.1": - version: 1.4.0 - resolution: "cjs-module-lexer@npm:1.4.0" - checksum: 10/b041096749792526120d8b8756929f8ef5dd4596502a0e1013f857e3027acd6091915fea77037921d70ee1a99988a100d994d3d3c2e323b04dd4c5ffd516cf13 +"cjs-module-lexer@npm:^1.0.0, cjs-module-lexer@npm:^1.3.1": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10/d2b92f919a2dedbfd61d016964fce8da0035f827182ed6839c97cac56e8a8077cfa6a59388adfe2bc588a19cef9bbe830d683a76a6e93c51f65852062cfe2591 languageName: node linkType: hard @@ -13850,7 +14165,7 @@ __metadata: languageName: node linkType: hard -"collect-v8-coverage@npm:^1.0.2": +"collect-v8-coverage@npm:^1.0.0, collect-v8-coverage@npm:^1.0.2": version: 1.0.3 resolution: "collect-v8-coverage@npm:1.0.3" checksum: 10/656443261fb7b79cf79e89cba4b55622b07c1d4976c630829d7c5c585c73cda1c2ff101f316bfb19bb9e2c58d724c7db1f70a21e213dcd14099227c5e6019860 @@ -14251,6 +14566,23 @@ __metadata: languageName: node linkType: hard +"create-jest@npm:^29.7.0": + version: 29.7.0 + resolution: "create-jest@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + exit: "npm:^0.1.2" + graceful-fs: "npm:^4.2.9" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + prompts: "npm:^2.0.1" + bin: + create-jest: bin/create-jest.js + checksum: 10/847b4764451672b4174be4d5c6d7d63442ec3aa5f3de52af924e4d996d87d7801c18e125504f25232fc75840f6625b3ac85860fac6ce799b5efae7bdcaf4a2b7 + languageName: node + linkType: hard + "cron-parser@npm:^4.5.0": version: 4.9.0 resolution: "cron-parser@npm:4.9.0" @@ -14634,15 +14966,15 @@ __metadata: languageName: node linkType: hard -"dedent@npm:^1.6.0": - version: 1.7.1 - resolution: "dedent@npm:1.7.1" +"dedent@npm:^1.0.0, dedent@npm:^1.6.0": + version: 1.7.2 + resolution: "dedent@npm:1.7.2" peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: babel-plugin-macros: optional: true - checksum: 10/78785ef592e37e0b1ca7a7a5964c8f3dee1abdff46c5bb49864168579c122328f6bb55c769bc7e005046a7381c3372d3859f0f78ab083950fa146e1c24873f4f + checksum: 10/30b9062290dca72b0f5a6cd3667633448cef8cd0dec602eab61015741269ad49df90cabf0521f9a32d134ceab4e21aa7f097258c55cc3baadef94874686d6480 languageName: node linkType: hard @@ -14790,7 +15122,7 @@ __metadata: languageName: node linkType: hard -"detect-newline@npm:^3.1.0": +"detect-newline@npm:^3.0.0, detect-newline@npm:^3.1.0": version: 3.1.0 resolution: "detect-newline@npm:3.1.0" checksum: 10/ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7 @@ -14826,6 +15158,13 @@ __metadata: languageName: node linkType: hard +"diff-sequences@npm:^29.6.3": + version: 29.6.3 + resolution: "diff-sequences@npm:29.6.3" + checksum: 10/179daf9d2f9af5c57ad66d97cb902a538bcf8ed64963fa7aa0c329b3de3665ce2eb6ffdc2f69f29d445fa4af2517e5e55e5b6e00c00a9ae4f43645f97f7078cb + languageName: node + linkType: hard + "diff@npm:^5.0.0": version: 5.2.0 resolution: "diff@npm:5.2.0" @@ -15995,6 +16334,13 @@ __metadata: languageName: node linkType: hard +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: 10/387555050c5b3c10e7a9e8df5f43194e95d7737c74532c409910e585d5554eaff34960c166643f5e23d042196529daad059c292dcf1fb61b8ca878d3677f4b87 + languageName: node + linkType: hard + "expand-template@npm:^2.0.3": version: 2.0.3 resolution: "expand-template@npm:2.0.3" @@ -16016,6 +16362,19 @@ __metadata: languageName: node linkType: hard +"expect@npm:^29.0.0, expect@npm:^29.7.0": + version: 29.7.0 + resolution: "expect@npm:29.7.0" + dependencies: + "@jest/expect-utils": "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10/63f97bc51f56a491950fb525f9ad94f1916e8a014947f8d8445d3847a665b5471b768522d659f5e865db20b6c2033d2ac10f35fcbd881a4d26407a4f6f18451a + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -16256,7 +16615,7 @@ __metadata: languageName: node linkType: hard -"fb-watchman@npm:^2.0.2": +"fb-watchman@npm:^2.0.0, fb-watchman@npm:^2.0.2": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" dependencies: @@ -16596,7 +16955,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": +"fsevents@npm:^2.3.2, fsevents@npm:^2.3.3, fsevents@npm:~2.3.2, fsevents@npm:~2.3.3": version: 2.3.3 resolution: "fsevents@npm:2.3.3" dependencies: @@ -16606,7 +16965,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": +"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A^2.3.3#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.3#optional!builtin": version: 2.3.3 resolution: "fsevents@patch:fsevents@npm%3A2.3.3#optional!builtin::version=2.3.3&hash=df0bf1" dependencies: @@ -16793,7 +17152,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^7.1.4, glob@npm:^7.1.7": +"glob@npm:^7.1.3, glob@npm:^7.1.4, glob@npm:^7.1.7": version: 7.2.3 resolution: "glob@npm:7.2.3" dependencies: @@ -17558,7 +17917,7 @@ __metadata: languageName: node linkType: hard -"import-local@npm:^3.2.0": +"import-local@npm:^3.0.2, import-local@npm:^3.2.0": version: 3.2.0 resolution: "import-local@npm:3.2.0" dependencies: @@ -17815,7 +18174,7 @@ __metadata: languageName: node linkType: hard -"is-generator-fn@npm:^2.1.0": +"is-generator-fn@npm:^2.0.0, is-generator-fn@npm:^2.1.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" checksum: 10/a6ad5492cf9d1746f73b6744e0c43c0020510b59d56ddcb78a91cbc173f09b5e6beff53d75c9c5a29feb618bfef2bf458e025ecf3a57ad2268e2fb2569f56215 @@ -18066,6 +18425,19 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-instrument@npm:^5.0.4": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": "npm:^7.12.3" + "@babel/parser": "npm:^7.14.7" + "@istanbuljs/schema": "npm:^0.1.2" + istanbul-lib-coverage: "npm:^3.2.0" + semver: "npm:^6.3.0" + checksum: 10/bbc4496c2f304d799f8ec22202ab38c010ac265c441947f075c0f7d46bd440b45c00e46017cf9053453d42182d768b1d6ed0e70a142c95ab00df9843aa5ab80e + languageName: node + linkType: hard + "istanbul-lib-instrument@npm:^6.0.0, istanbul-lib-instrument@npm:^6.0.2": version: 6.0.3 resolution: "istanbul-lib-instrument@npm:6.0.3" @@ -18090,6 +18462,17 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: "npm:^4.1.1" + istanbul-lib-coverage: "npm:^3.0.0" + source-map: "npm:^0.6.1" + checksum: 10/5526983462799aced011d776af166e350191b816821ea7bcf71cab3e5272657b062c47dc30697a22a43656e3ced78893a42de677f9ccf276a28c913190953b82 + languageName: node + linkType: hard + "istanbul-lib-source-maps@npm:^5.0.0": version: 5.0.6 resolution: "istanbul-lib-source-maps@npm:5.0.6" @@ -18148,6 +18531,17 @@ __metadata: languageName: node linkType: hard +"jest-changed-files@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-changed-files@npm:29.7.0" + dependencies: + execa: "npm:^5.0.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + checksum: 10/3d93742e56b1a73a145d55b66e96711fbf87ef89b96c2fab7cfdfba8ec06612591a982111ca2b712bb853dbc16831ec8b43585a2a96b83862d6767de59cbf83d + languageName: node + linkType: hard + "jest-circus@npm:30.4.2": version: 30.4.2 resolution: "jest-circus@npm:30.4.2" @@ -18176,6 +18570,34 @@ __metadata: languageName: node linkType: hard +"jest-circus@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-circus@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/expect": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + co: "npm:^4.6.0" + dedent: "npm:^1.0.0" + is-generator-fn: "npm:^2.0.0" + jest-each: "npm:^29.7.0" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + pure-rand: "npm:^6.0.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10/716a8e3f40572fd0213bcfc1da90274bf30d856e5133af58089a6ce45089b63f4d679bd44e6be9d320e8390483ebc3ae9921981993986d21639d9019b523123d + languageName: node + linkType: hard + "jest-cli@npm:30.4.2": version: 30.4.2 resolution: "jest-cli@npm:30.4.2" @@ -18201,6 +18623,32 @@ __metadata: languageName: node linkType: hard +"jest-cli@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-cli@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + create-jest: "npm:^29.7.0" + exit: "npm:^0.1.2" + import-local: "npm:^3.0.2" + jest-config: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + yargs: "npm:^17.3.1" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10/6cc62b34d002c034203065a31e5e9a19e7c76d9e8ef447a6f70f759c0714cb212c6245f75e270ba458620f9c7b26063cd8cf6cd1f7e3afd659a7cc08add17307 + languageName: node + linkType: hard + "jest-config@npm:30.4.2": version: 30.4.2 resolution: "jest-config@npm:30.4.2" @@ -18243,6 +18691,44 @@ __metadata: languageName: node linkType: hard +"jest-config@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-config@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@jest/test-sequencer": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-jest: "npm:^29.7.0" + chalk: "npm:^4.0.0" + ci-info: "npm:^3.2.0" + deepmerge: "npm:^4.2.2" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-circus: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-runner: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + parse-json: "npm:^5.2.0" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-json-comments: "npm:^3.1.1" + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 10/6bdf570e9592e7d7dd5124fc0e21f5fe92bd15033513632431b211797e3ab57eaa312f83cc6481b3094b72324e369e876f163579d60016677c117ec4853cf02b + languageName: node + linkType: hard + "jest-diff@npm:30.4.1": version: 30.4.1 resolution: "jest-diff@npm:30.4.1" @@ -18255,6 +18741,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-diff@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + diff-sequences: "npm:^29.6.3" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10/6f3a7eb9cd9de5ea9e5aa94aed535631fa6f80221832952839b3cb59dd419b91c20b73887deb0b62230d06d02d6b6cf34ebb810b88d904bb4fe1e2e4f0905c98 + languageName: node + linkType: hard + "jest-docblock@npm:30.4.0": version: 30.4.0 resolution: "jest-docblock@npm:30.4.0" @@ -18264,6 +18762,15 @@ __metadata: languageName: node linkType: hard +"jest-docblock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-docblock@npm:29.7.0" + dependencies: + detect-newline: "npm:^3.0.0" + checksum: 10/8d48818055bc96c9e4ec2e217a5a375623c0d0bfae8d22c26e011074940c202aa2534a3362294c81d981046885c05d304376afba9f2874143025981148f3e96d + languageName: node + linkType: hard + "jest-each@npm:30.4.1": version: 30.4.1 resolution: "jest-each@npm:30.4.1" @@ -18277,6 +18784,19 @@ __metadata: languageName: node linkType: hard +"jest-each@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-each@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + pretty-format: "npm:^29.7.0" + checksum: 10/bd1a077654bdaa013b590deb5f7e7ade68f2e3289180a8c8f53bc8a49f3b40740c0ec2d3a3c1aee906f682775be2bebbac37491d80b634d15276b0aa0f2e3fda + languageName: node + linkType: hard + "jest-environment-jsdom@npm:^30.4.1": version: 30.4.1 resolution: "jest-environment-jsdom@npm:30.4.1" @@ -18308,6 +18828,27 @@ __metadata: languageName: node linkType: hard +"jest-environment-node@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-environment-node@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-mock: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + checksum: 10/9cf7045adf2307cc93aed2f8488942e39388bff47ec1df149a997c6f714bfc66b2056768973770d3f8b1bf47396c19aa564877eb10ec978b952c6018ed1bd637 + languageName: node + linkType: hard + +"jest-get-type@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-get-type@npm:29.6.3" + checksum: 10/88ac9102d4679d768accae29f1e75f592b760b44277df288ad76ce5bf038c3f5ce3719dea8aa0f035dac30e9eb034b848ce716b9183ad7cc222d029f03e92205 + languageName: node + linkType: hard + "jest-haste-map@npm:30.4.1": version: 30.4.1 resolution: "jest-haste-map@npm:30.4.1" @@ -18330,6 +18871,29 @@ __metadata: languageName: node linkType: hard +"jest-haste-map@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-haste-map@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/graceful-fs": "npm:^4.1.3" + "@types/node": "npm:*" + anymatch: "npm:^3.0.3" + fb-watchman: "npm:^2.0.0" + fsevents: "npm:^2.3.2" + graceful-fs: "npm:^4.2.9" + jest-regex-util: "npm:^29.6.3" + jest-util: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + micromatch: "npm:^4.0.4" + walker: "npm:^1.0.8" + dependenciesMeta: + fsevents: + optional: true + checksum: 10/8531b42003581cb18a69a2774e68c456fb5a5c3280b1b9b77475af9e346b6a457250f9d756bfeeae2fe6cbc9ef28434c205edab9390ee970a919baddfa08bb85 + languageName: node + linkType: hard + "jest-leak-detector@npm:30.4.1": version: 30.4.1 resolution: "jest-leak-detector@npm:30.4.1" @@ -18340,6 +18904,16 @@ __metadata: languageName: node linkType: hard +"jest-leak-detector@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-leak-detector@npm:29.7.0" + dependencies: + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10/e3950e3ddd71e1d0c22924c51a300a1c2db6cf69ec1e51f95ccf424bcc070f78664813bef7aed4b16b96dfbdeea53fe358f8aeaaea84346ae15c3735758f1605 + languageName: node + linkType: hard + "jest-matcher-utils@npm:30.4.1": version: 30.4.1 resolution: "jest-matcher-utils@npm:30.4.1" @@ -18352,6 +18926,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-matcher-utils@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + pretty-format: "npm:^29.7.0" + checksum: 10/981904a494299cf1e3baed352f8a3bd8b50a8c13a662c509b6a53c31461f94ea3bfeffa9d5efcfeb248e384e318c87de7e3baa6af0f79674e987482aa189af40 + languageName: node + linkType: hard + "jest-message-util@npm:30.4.1": version: 30.4.1 resolution: "jest-message-util@npm:30.4.1" @@ -18370,6 +18956,23 @@ __metadata: languageName: node linkType: hard +"jest-message-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-message-util@npm:29.7.0" + dependencies: + "@babel/code-frame": "npm:^7.12.13" + "@jest/types": "npm:^29.6.3" + "@types/stack-utils": "npm:^2.0.0" + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + micromatch: "npm:^4.0.4" + pretty-format: "npm:^29.7.0" + slash: "npm:^3.0.0" + stack-utils: "npm:^2.0.3" + checksum: 10/31d53c6ed22095d86bab9d14c0fa70c4a92c749ea6ceece82cf30c22c9c0e26407acdfbdb0231435dc85a98d6d65ca0d9cbcd25cd1abb377fe945e843fb770b9 + languageName: node + linkType: hard + "jest-mock@npm:30.4.1": version: 30.4.1 resolution: "jest-mock@npm:30.4.1" @@ -18381,7 +18984,18 @@ __metadata: languageName: node linkType: hard -"jest-pnp-resolver@npm:^1.2.3": +"jest-mock@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-mock@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + jest-util: "npm:^29.7.0" + checksum: 10/ae51d1b4f898724be5e0e52b2268a68fcd876d9b20633c864a6dd6b1994cbc48d62402b0f40f3a1b669b30ebd648821f086c26c08ffde192ced951ff4670d51c + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2, jest-pnp-resolver@npm:^1.2.3": version: 1.2.3 resolution: "jest-pnp-resolver@npm:1.2.3" peerDependencies: @@ -18400,6 +19014,13 @@ __metadata: languageName: node linkType: hard +"jest-regex-util@npm:^29.6.3": + version: 29.6.3 + resolution: "jest-regex-util@npm:29.6.3" + checksum: 10/0518beeb9bf1228261695e54f0feaad3606df26a19764bc19541e0fc6e2a3737191904607fb72f3f2ce85d9c16b28df79b7b1ec9443aa08c3ef0e9efda6f8f2a + languageName: node + linkType: hard + "jest-resolve-dependencies@npm:30.4.2": version: 30.4.2 resolution: "jest-resolve-dependencies@npm:30.4.2" @@ -18410,6 +19031,16 @@ __metadata: languageName: node linkType: hard +"jest-resolve-dependencies@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve-dependencies@npm:29.7.0" + dependencies: + jest-regex-util: "npm:^29.6.3" + jest-snapshot: "npm:^29.7.0" + checksum: 10/1e206f94a660d81e977bcfb1baae6450cb4a81c92e06fad376cc5ea16b8e8c6ea78c383f39e95591a9eb7f925b6a1021086c38941aa7c1b8a6a813c2f6e93675 + languageName: node + linkType: hard + "jest-resolve@npm:30.4.1": version: 30.4.1 resolution: "jest-resolve@npm:30.4.1" @@ -18426,6 +19057,23 @@ __metadata: languageName: node linkType: hard +"jest-resolve@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-resolve@npm:29.7.0" + dependencies: + chalk: "npm:^4.0.0" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-pnp-resolver: "npm:^1.2.2" + jest-util: "npm:^29.7.0" + jest-validate: "npm:^29.7.0" + resolve: "npm:^1.20.0" + resolve.exports: "npm:^2.0.0" + slash: "npm:^3.0.0" + checksum: 10/faa466fd9bc69ea6c37a545a7c6e808e073c66f46ab7d3d8a6ef084f8708f201b85d5fe1799789578b8b47fa1de47b9ee47b414d1863bc117a49e032ba77b7c7 + languageName: node + linkType: hard + "jest-runner@npm:30.4.2": version: 30.4.2 resolution: "jest-runner@npm:30.4.2" @@ -18456,6 +19104,35 @@ __metadata: languageName: node linkType: hard +"jest-runner@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runner@npm:29.7.0" + dependencies: + "@jest/console": "npm:^29.7.0" + "@jest/environment": "npm:^29.7.0" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + graceful-fs: "npm:^4.2.9" + jest-docblock: "npm:^29.7.0" + jest-environment-node: "npm:^29.7.0" + jest-haste-map: "npm:^29.7.0" + jest-leak-detector: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-resolve: "npm:^29.7.0" + jest-runtime: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + jest-watcher: "npm:^29.7.0" + jest-worker: "npm:^29.7.0" + p-limit: "npm:^3.1.0" + source-map-support: "npm:0.5.13" + checksum: 10/9d8748a494bd90f5c82acea99be9e99f21358263ce6feae44d3f1b0cd90991b5df5d18d607e73c07be95861ee86d1cbab2a3fc6ca4b21805f07ac29d47c1da1e + languageName: node + linkType: hard + "jest-runtime@npm:30.4.2": version: 30.4.2 resolution: "jest-runtime@npm:30.4.2" @@ -18486,6 +19163,36 @@ __metadata: languageName: node linkType: hard +"jest-runtime@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-runtime@npm:29.7.0" + dependencies: + "@jest/environment": "npm:^29.7.0" + "@jest/fake-timers": "npm:^29.7.0" + "@jest/globals": "npm:^29.7.0" + "@jest/source-map": "npm:^29.6.3" + "@jest/test-result": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + chalk: "npm:^4.0.0" + cjs-module-lexer: "npm:^1.0.0" + collect-v8-coverage: "npm:^1.0.0" + glob: "npm:^7.1.3" + graceful-fs: "npm:^4.2.9" + jest-haste-map: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-mock: "npm:^29.7.0" + jest-regex-util: "npm:^29.6.3" + jest-resolve: "npm:^29.7.0" + jest-snapshot: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + slash: "npm:^3.0.0" + strip-bom: "npm:^4.0.0" + checksum: 10/59eb58eb7e150e0834a2d0c0d94f2a0b963ae7182cfa6c63f2b49b9c6ef794e5193ef1634e01db41420c36a94cefc512cdd67a055cd3e6fa2f41eaf0f82f5a20 + languageName: node + linkType: hard + "jest-silent-reporter@npm:^0.6.0": version: 0.6.0 resolution: "jest-silent-reporter@npm:0.6.0" @@ -18525,6 +19232,34 @@ __metadata: languageName: node linkType: hard +"jest-snapshot@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-snapshot@npm:29.7.0" + dependencies: + "@babel/core": "npm:^7.11.6" + "@babel/generator": "npm:^7.7.2" + "@babel/plugin-syntax-jsx": "npm:^7.7.2" + "@babel/plugin-syntax-typescript": "npm:^7.7.2" + "@babel/types": "npm:^7.3.3" + "@jest/expect-utils": "npm:^29.7.0" + "@jest/transform": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + babel-preset-current-node-syntax: "npm:^1.0.0" + chalk: "npm:^4.0.0" + expect: "npm:^29.7.0" + graceful-fs: "npm:^4.2.9" + jest-diff: "npm:^29.7.0" + jest-get-type: "npm:^29.6.3" + jest-matcher-utils: "npm:^29.7.0" + jest-message-util: "npm:^29.7.0" + jest-util: "npm:^29.7.0" + natural-compare: "npm:^1.4.0" + pretty-format: "npm:^29.7.0" + semver: "npm:^7.5.3" + checksum: 10/cb19a3948256de5f922d52f251821f99657339969bf86843bd26cf3332eae94883e8260e3d2fba46129a27c3971c1aa522490e460e16c7fad516e82d10bbf9f8 + languageName: node + linkType: hard + "jest-util@npm:30.4.1": version: 30.4.1 resolution: "jest-util@npm:30.4.1" @@ -18581,6 +19316,20 @@ __metadata: languageName: node linkType: hard +"jest-validate@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-validate@npm:29.7.0" + dependencies: + "@jest/types": "npm:^29.6.3" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.0.0" + jest-get-type: "npm:^29.6.3" + leven: "npm:^3.1.0" + pretty-format: "npm:^29.7.0" + checksum: 10/8ee1163666d8eaa16d90a989edba2b4a3c8ab0ffaa95ad91b08ca42b015bfb70e164b247a5b17f9de32d096987cada63ed8491ab82761bfb9a28bc34b27ae161 + languageName: node + linkType: hard + "jest-watcher@npm:30.4.1": version: 30.4.1 resolution: "jest-watcher@npm:30.4.1" @@ -18597,6 +19346,22 @@ __metadata: languageName: node linkType: hard +"jest-watcher@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-watcher@npm:29.7.0" + dependencies: + "@jest/test-result": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + "@types/node": "npm:*" + ansi-escapes: "npm:^4.2.1" + chalk: "npm:^4.0.0" + emittery: "npm:^0.13.1" + jest-util: "npm:^29.7.0" + string-length: "npm:^4.0.1" + checksum: 10/4f616e0345676631a7034b1d94971aaa719f0cd4a6041be2aa299be437ea047afd4fe05c48873b7963f5687a2f6c7cbf51244be8b14e313b97bfe32b1e127e55 + languageName: node + linkType: hard + "jest-when@npm:^3.7.0": version: 3.7.0 resolution: "jest-when@npm:3.7.0" @@ -18630,7 +19395,7 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^29.4.3": +"jest-worker@npm:^29.4.3, jest-worker@npm:^29.7.0": version: 29.7.0 resolution: "jest-worker@npm:29.7.0" dependencies: @@ -18642,6 +19407,25 @@ __metadata: languageName: node linkType: hard +"jest@npm:^29.7.0": + version: 29.7.0 + resolution: "jest@npm:29.7.0" + dependencies: + "@jest/core": "npm:^29.7.0" + "@jest/types": "npm:^29.6.3" + import-local: "npm:^3.0.2" + jest-cli: "npm:^29.7.0" + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 10/97023d78446098c586faaa467fbf2c6b07ff06e2c85a19e3926adb5b0effe9ac60c4913ae03e2719f9c01ae8ffd8d92f6b262cedb9555ceeb5d19263d8c6362a + languageName: node + linkType: hard + "jest@npm:^30.4.2": version: 30.4.2 resolution: "jest@npm:30.4.2" @@ -20166,7 +20950,7 @@ __metadata: languageName: node linkType: hard -"micromatch@npm:^4.0.2, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": +"micromatch@npm:^4.0.2, micromatch@npm:^4.0.4, micromatch@npm:^4.0.5, micromatch@npm:^4.0.8": version: 4.0.8 resolution: "micromatch@npm:4.0.8" dependencies: @@ -21596,7 +22380,7 @@ __metadata: languageName: node linkType: hard -"pirates@npm:^4.0.7": +"pirates@npm:^4.0.4, pirates@npm:^4.0.7": version: 4.0.7 resolution: "pirates@npm:4.0.7" checksum: 10/2427f371366081ae42feb58214f04805d6b41d6b84d74480ebcc9e0ddbd7105a139f7c653daeaf83ad8a1a77214cf07f64178e76de048128fec501eab3305a96 @@ -22536,6 +23320,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^29.0.0, pretty-format@npm:^29.7.0": + version: 29.7.0 + resolution: "pretty-format@npm:29.7.0" + dependencies: + "@jest/schemas": "npm:^29.6.3" + ansi-styles: "npm:^5.0.0" + react-is: "npm:^18.0.0" + checksum: 10/dea96bc83c83cd91b2bfc55757b6b2747edcaac45b568e46de29deee80742f17bc76fe8898135a70d904f4928eafd8bb693cd1da4896e8bdd3c5e82cadf1d2bb + languageName: node + linkType: hard + "pretty-time@npm:^1.1.0": version: 1.1.0 resolution: "pretty-time@npm:1.1.0" @@ -22610,7 +23405,7 @@ __metadata: languageName: node linkType: hard -"prompts@npm:^2.4.2": +"prompts@npm:^2.0.1, prompts@npm:^2.4.2": version: 2.4.2 resolution: "prompts@npm:2.4.2" dependencies: @@ -22722,6 +23517,13 @@ __metadata: languageName: node linkType: hard +"pure-rand@npm:^6.0.0": + version: 6.1.0 + resolution: "pure-rand@npm:6.1.0" + checksum: 10/256aa4bcaf9297256f552914e03cbdb0039c8fe1db11fa1e6d3f80790e16e563eb0a859a1e61082a95e224fc0c608661839439f8ecc6a3db4e48d46d99216ee4 + languageName: node + linkType: hard + "pure-rand@npm:^7.0.0": version: 7.0.1 resolution: "pure-rand@npm:7.0.1" @@ -22870,7 +23672,7 @@ __metadata: languageName: node linkType: hard -"react-is-18@npm:react-is@^18.3.1": +"react-is-18@npm:react-is@^18.3.1, react-is@npm:^18.0.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" checksum: 10/d5f60c87d285af24b1e1e7eaeb123ec256c3c8bdea7061ab3932e3e14685708221bf234ec50b21e10dd07f008f1b966a2730a0ce4ff67905b3872ff2042aec22 @@ -23397,6 +24199,13 @@ __metadata: languageName: node linkType: hard +"resolve.exports@npm:^2.0.0": + version: 2.0.3 + resolution: "resolve.exports@npm:2.0.3" + checksum: 10/536efee0f30a10fac8604e6cdc7844dbc3f4313568d09f06db4f7ed8a5b8aeb8585966fe975083d1f2dfbc87cf5f8bc7ab65a5c23385c14acbb535ca79f8398a + languageName: node + linkType: hard + "resolve@npm:1.22.8": version: 1.22.8 resolution: "resolve@npm:1.22.8" @@ -23410,7 +24219,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.22.11, resolve@npm:^1.22.4": +"resolve@npm:^1.20.0, resolve@npm:^1.22.11, resolve@npm:^1.22.4": version: 1.22.12 resolution: "resolve@npm:1.22.12" dependencies: @@ -23437,7 +24246,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@npm%3A^1.22.11#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": +"resolve@patch:resolve@npm%3A^1.20.0#optional!builtin, resolve@patch:resolve@npm%3A^1.22.11#optional!builtin, resolve@patch:resolve@npm%3A^1.22.4#optional!builtin": version: 1.22.12 resolution: "resolve@patch:resolve@npm%3A1.22.12#optional!builtin::version=1.22.12&hash=c3c19d" dependencies: @@ -23709,7 +24518,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.3.1": +"semver@npm:^6.3.0, semver@npm:^6.3.1": version: 6.3.1 resolution: "semver@npm:6.3.1" bin: @@ -23963,7 +24772,7 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": +"signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: 10/a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 @@ -24306,7 +25115,7 @@ __metadata: languageName: node linkType: hard -"stack-utils@npm:^2.0.6": +"stack-utils@npm:^2.0.3, stack-utils@npm:^2.0.6": version: 2.0.6 resolution: "stack-utils@npm:2.0.6" dependencies: @@ -24351,7 +25160,7 @@ __metadata: languageName: node linkType: hard -"string-length@npm:^4.0.2": +"string-length@npm:^4.0.1, string-length@npm:^4.0.2": version: 4.0.2 resolution: "string-length@npm:4.0.2" dependencies: @@ -24913,7 +25722,7 @@ __metadata: languageName: node linkType: hard -"ts-jest@npm:^29.4.11": +"ts-jest@npm:^29.2.5, ts-jest@npm:^29.4.11": version: 29.4.11 resolution: "ts-jest@npm:29.4.11" dependencies: @@ -26133,6 +26942,16 @@ __metadata: languageName: node linkType: hard +"write-file-atomic@npm:^4.0.2": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^3.0.7" + checksum: 10/3be1f5508a46c190619d5386b1ac8f3af3dbe951ed0f7b0b4a0961eed6fc626bd84b50cf4be768dabc0a05b672f5d0c5ee7f42daa557b14415d18c3a13c7d246 + languageName: node + linkType: hard + "write-file-atomic@npm:^5.0.0, write-file-atomic@npm:^5.0.1": version: 5.0.1 resolution: "write-file-atomic@npm:5.0.1" @@ -26317,7 +27136,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:17.7.2, yargs@npm:^17.0.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": +"yargs@npm:17.7.2": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: @@ -26332,6 +27151,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^17.0.1, yargs@npm:^17.3.1, yargs@npm:^17.7.1, yargs@npm:^17.7.2": + version: 17.7.3 + resolution: "yargs@npm:17.7.3" + dependencies: + cliui: "npm:^8.0.1" + escalade: "npm:^3.1.1" + get-caller-file: "npm:^2.0.5" + require-directory: "npm:^2.1.1" + string-width: "npm:^4.2.3" + y18n: "npm:^5.0.5" + yargs-parser: "npm:^21.1.1" + checksum: 10/a3826798c03b159e139d0580a3b2733953889a9a1bac8e4e1ca7a1a249b55315b213c323a6a1dbdb305f6e59496a9eaa810742c87e34abcf1a0584d8f59212a1 + languageName: node + linkType: hard + "yocto-queue@npm:^0.1.0": version: 0.1.0 resolution: "yocto-queue@npm:0.1.0"