diff --git a/packages/multichain-network-controller/CHANGELOG.md b/packages/multichain-network-controller/CHANGELOG.md new file mode 100644 index 0000000000..b518709c7b --- /dev/null +++ b/packages/multichain-network-controller/CHANGELOG.md @@ -0,0 +1,10 @@ +# 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] + +[Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/multichain-network-controller/LICENSE b/packages/multichain-network-controller/LICENSE new file mode 100644 index 0000000000..7d002dced3 --- /dev/null +++ b/packages/multichain-network-controller/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2025 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 diff --git a/packages/multichain-network-controller/README.md b/packages/multichain-network-controller/README.md new file mode 100644 index 0000000000..6bdb2c1323 --- /dev/null +++ b/packages/multichain-network-controller/README.md @@ -0,0 +1,15 @@ +# `@metamask/multichain-network-controller` + +... + +## Installation + +`yarn add @metamask/multichain-network-controller` + +or + +`npm install @metamask/multichain-network-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/multichain-network-controller/jest.config.js b/packages/multichain-network-controller/jest.config.js new file mode 100644 index 0000000000..ca08413339 --- /dev/null +++ b/packages/multichain-network-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/multichain-network-controller/package.json b/packages/multichain-network-controller/package.json new file mode 100644 index 0000000000..e9fb31c040 --- /dev/null +++ b/packages/multichain-network-controller/package.json @@ -0,0 +1,73 @@ +{ + "name": "@metamask/multichain-network-controller", + "version": "0.0.0", + "private": true, + "description": "Multichain network controller", + "keywords": [ + "MetaMask", + "Ethereum" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/multichain-network-controller#readme", + "bugs": { + "url": "https://github.com/MetaMask/core/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/core.git" + }, + "license": "MIT", + "sideEffects": false, + "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" + }, + "main": "./dist/index.cjs", + "types": "./dist/index.d.cts", + "files": [ + "dist/" + ], + "scripts": { + "build": "ts-bridge --project tsconfig.build.json --verbose --clean --no-references", + "build:docs": "typedoc", + "changelog:update": "../../scripts/update-changelog.sh @metamask/multichain-network-controller", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/multichain-network-controller", + "since-latest-release": "../../scripts/since-latest-release.sh", + "test": "NODE_OPTIONS=--experimental-vm-modules jest --reporters=jest-silent-reporter", + "test:clean": "NODE_OPTIONS=--experimental-vm-modules jest --clearCache", + "test:verbose": "NODE_OPTIONS=--experimental-vm-modules jest --verbose", + "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch" + }, + "dependencies": { + "@metamask/base-controller": "^7.1.1", + "@metamask/keyring-api": "^16.1.0", + "@metamask/utils": "^11.0.1" + }, + "devDependencies": { + "@metamask/auto-changelog": "^3.4.4", + "@types/jest": "^27.4.1", + "deepmerge": "^4.2.2", + "immer": "^9.0.6", + "jest": "^27.5.1", + "nock": "^13.3.1", + "ts-jest": "^27.1.4", + "typedoc": "^0.24.8", + "typedoc-plugin-missing-exports": "^2.0.0", + "typescript": "~5.2.2" + }, + "peerDependencies": { + "@metamask/accounts-controller": "^21.0.1", + "@metamask/network-controller": "^22.1.1" + }, + "engines": { + "node": "^18.18 || >=20" + } +} diff --git a/packages/multichain-network-controller/src/MultichainNetworkController.test.ts b/packages/multichain-network-controller/src/MultichainNetworkController.test.ts new file mode 100644 index 0000000000..529468c104 --- /dev/null +++ b/packages/multichain-network-controller/src/MultichainNetworkController.test.ts @@ -0,0 +1,147 @@ +import { ControllerMessenger } from '@metamask/base-controller'; +import type { + AllowedActions, + AllowedEvents, +} from './MultichainNetworkController'; + +import { MultichainNetworkController } from './MultichainNetworkController'; + +const name = 'MultichainNetworkController'; + +const buildMessenger = () => { + return new ControllerMessenger(); +}; + +const buildMultichainNetworkControllerMessenger = ( + messenger: ControllerMessenger, +) => { + return messenger.getRestricted({ + name, + allowedActions: ['NetworkController:setActiveNetwork'], + allowedEvents: [], + }); +}; + +describe('MultichainNetworkController', () => { + let controller: MultichainNetworkController; + let messenger: ControllerMessenger; + + beforeEach(() => { + messenger = buildMessenger(); + messenger.registerActionHandler( + 'NetworkController:setActiveNetwork', + jest.fn(), + ); + + jest.spyOn(messenger, 'call'); + + const restrictedMessenger = buildMultichainNetworkControllerMessenger(messenger); + + controller = new MultichainNetworkController({ + messenger: restrictedMessenger, + state: { + multichainNetworkConfigurationsByChainId: {}, + selectedMultichainNetworkChainId: bitcoinCaip2ChainId, + multichainNetworksMetadata: {}, + nonEvmSelected: false, + }, + }); + }); + + describe('setActiveNetwork', () => { + it('should set non-EVM network when valid chainId is provided', async () => { + const clientId = 'testClient'; + const chainId = 'bip122:000000000019d6689c085ae165831e93'; + messenger = buildMessenger(); + messenger.registerActionHandler( + 'NetworkController:setActiveNetwork', + jest.fn(), + ); + + jest.spyOn(messenger, 'call'); + + const restrictedMessenger = buildMultichainNetworkControllerMessenger(messenger); + + const multiChainController = new MultichainNetworkController({ + messenger: restrictedMessenger, + state: { + multichainNetworkConfigurationsByChainId: { + [chainId]: { + chainId, + name: 'Bitcoin', + nativeCurrency: 'BTC', + blockExplorerUrls: ['https://blockstream.info/'], + }, + }, + selectedMultichainNetworkChainId: bitcoinCaip2ChainId, + multichainNetworksMetadata: {}, + nonEvmSelected: false, + }, + }); + + await multiChainController.setActiveNetwork(clientId, chainId); + + expect(multiChainController.state.selectedMultichainNetworkChainId).toBe(chainId); + expect(multiChainController.state.nonEvmSelected).toBe(true); + expect(messenger.call).not.toHaveBeenCalled(); + }); + + it('should set EVM network when chainId is not provided', async () => { + const clientId = 'testClient'; + + await controller.setActiveNetwork(clientId); + + expect(controller.state.nonEvmSelected).toBe(false); + expect(messenger.call).toHaveBeenCalledWith( + 'NetworkController:setActiveNetwork', + clientId, + ); + }); + + it('should set EVM network when invalid chainId is provided', async () => { + const clientId = 'testClient'; + const invalidChainId = 'invalid-chain-id'; + + await controller.setActiveNetwork(clientId, invalidChainId); + + expect(controller.state.nonEvmSelected).toBe(false); + expect(messenger.call).toHaveBeenCalledWith( + 'NetworkController:setActiveNetwork', + clientId, + ); + }); + }); + + describe('setNonEvmSelected', () => { + it('should set nonEvmSelected to true', () => { + controller.setNonEvmSelected(); + expect(controller.state.nonEvmSelected).toBe(true); + }); + }); + + describe('setEvmSelected', () => { + it('should set nonEvmSelected to false', () => { + messenger = buildMessenger(); + messenger.registerActionHandler( + 'NetworkController:setActiveNetwork', + jest.fn(), + ); + + jest.spyOn(messenger, 'call'); + + const restrictedMessenger = buildMultichainNetworkControllerMessenger(messenger); + const multiChainController = new MultichainNetworkController({ + messenger: restrictedMessenger, + state: { + multichainNetworkConfigurationsByChainId: {}, + selectedMultichainNetworkChainId: bitcoinCaip2ChainId, + multichainNetworksMetadata: {}, + nonEvmSelected: true, + }, + }); + + multiChainController.setEvmSelected(); + expect(multiChainController.state.nonEvmSelected).toBe(false); + }); + }); +}); diff --git a/packages/multichain-network-controller/src/MultichainNetworkController.ts b/packages/multichain-network-controller/src/MultichainNetworkController.ts new file mode 100644 index 0000000000..52c310556f --- /dev/null +++ b/packages/multichain-network-controller/src/MultichainNetworkController.ts @@ -0,0 +1,221 @@ +import type { AccountsControllerSetSelectedAccountAction } from '@metamask/accounts-controller'; +import { + BaseController, + StateMetadata, + type ControllerGetStateAction, + type ControllerStateChangeEvent, + type RestrictedControllerMessenger, +} from '@metamask/base-controller'; +import { BtcScope } from '@metamask/keyring-api'; + +import type { + NetworkStatus, + NetworkControllerGetNetworkConfigurationByNetworkClientId, + NetworkControllerSetActiveNetworkAction, + NetworkControllerGetStateAction, + NetworkControllerStateChangeEvent, +} from '@metamask/network-controller'; + + +import { CaipAssetType, CaipChainId, KnownCaipNamespace, parseCaipChainId } from '@metamask/utils'; + +const controllerName = 'MultichainNetworkController'; + +export type MultichainNetworkMetadata = { + features: string[]; + status: NetworkStatus; +}; + +export type MultichainNetworkConfiguration = { + /** + * The chain ID of the network. + */ + chainId: CaipChainId; + /** + * The name of the network. + */ + name: string; + /** + * The native asset type of the network. + */ + nativeAsset: CaipAssetType; + /** + * The block explorer URLs of the network. + */ + blockExplorerUrls: string[]; + /** + * The default block explorer URL index of the network. + */ + defaultBlockExplorerUrlIndex?: number; + /** + * The last updated timestamp of the network. + */ + lastUpdated?: number; + /** + * Whether the network is an EVM network or non-evm network. + */ + isEvm: boolean; +}; + +/** + * State used by the {@link MultichainNetworkController} to cache network configurations. + */ +export type MultichainNetworkControllerState = { + /** + * The network configurations by chain ID. + */ + multichainNetworkConfigurationsByChainId: Record< + string, + MultichainNetworkConfiguration + >; + /** + * The chain ID of the selected network. + */ + selectedMultichainNetworkChainId: CaipChainId; + /** + * The metadata of the networks. + */ + multichainNetworksMetadata: Record; + /** + * Whether the non-EVM network is selected by the wallet. + */ + nonEvmSelected: boolean; +}; + +/** + * Default state of the {@link MultichainNetworkController}. + */ +export const getDefaultMultichainNetworkControllerState = (): MultichainNetworkControllerState => ({ + multichainNetworkConfigurationsByChainId: {}, + selectedMultichainNetworkChainId: BtcScope.Mainnet, + multichainNetworksMetadata: {}, + nonEvmSelected: false, +}); + +/** + * Returns the state of the {@link MultichainNetworkController}. + */ +export type MultichainNetworkControllerGetStateAction = + ControllerGetStateAction< + typeof controllerName, + MultichainNetworkControllerState + >; + +/** + * Event emitted when the state of the {@link MultichainNetworkController} changes. + */ +export type MultichainNetworkControllerStateChange = + ControllerStateChangeEvent< + typeof controllerName, + MultichainNetworkControllerState + >; + +/** + * Actions exposed by the {@link MultichainNetworkController}. + */ +export type MultichainNetworkControllerActions = + MultichainNetworkControllerGetStateAction; + +/** + * Events emitted by {@link MultichainNetworkController}. + */ +export type MultichainNetworkControllerEvents = + MultichainNetworkControllerStateChange; + +export type MultichainNetworkControllerAllowedActions = MultichainNetworkControllerActions | AllowedActions; + +export type MultichainNetworkControllerAllowedEvents = MultichainNetworkControllerEvents | AllowedEvents; + + +/** + * Actions that this controller is allowed to call. + */ +export type AllowedActions = + | NetworkControllerGetStateAction + | NetworkControllerSetActiveNetworkAction + | AccountsControllerSetSelectedAccountAction + | NetworkControllerGetNetworkConfigurationByNetworkClientId; + +/** + * Events that this controller is allowed to subscribe. + */ +export type AllowedEvents = NetworkControllerStateChangeEvent; + +/** + * Messenger type for the MultichainNetworkController. + */ +export type MultichainNetworkControllerMessenger = + RestrictedControllerMessenger< + typeof controllerName, + MultichainNetworkControllerAllowedActions, + MultichainNetworkControllerAllowedEvents, + AllowedActions['type'], + AllowedEvents['type'] + >; + +/** + * {@link MultichainNetworkController}'s metadata. + * + * This allows us to choose if fields of the state should be persisted or not + * using the `persist` flag; and if they can be sent to Sentry or not, using + * the `anonymous` flag. + */ +const multichainNetworkControllerMetadata = { + multichainNetworkConfigurationsByChainId: { persist: true, anonymous: true }, + selectedMultichainNetworkChainId: { persist: true, anonymous: true }, + multichainNetworksMetadata: { persist: true, anonymous: true }, + nonEvmSelected: { persist: true, anonymous: true }, +} satisfies StateMetadata; + +/** + * The MultichainNetworkController is responsible for fetching and caching account + * balances. + */ +export class MultichainNetworkController extends BaseController< + typeof controllerName, + MultichainNetworkControllerState, + MultichainNetworkControllerMessenger +> { + constructor({ + messenger, + state = {}, + }: { + messenger: MultichainNetworkControllerMessenger; + state?: Partial; + }) { + super({ + messenger, + name: controllerName, + metadata: multichainNetworkControllerMetadata, + state: { + ...getDefaultMultichainNetworkControllerState(), + ...state, + }, + }); + } + /** + * Sets the active network. + * + * @param clientId - The client ID of the evm network. + * @param caipChainId - The chain ID of the non-evm network. + */ + async setActiveNetwork(clientId: string, caipChainId?: CaipChainId): Promise { + if (caipChainId && Object.keys(this.state.multichainNetworkConfigurationsByChainId).includes(caipChainId)) { + this.update((state) => { + state.selectedMultichainNetworkChainId = caipChainId; + state.nonEvmSelected = true; + }); + return; + } + + await this.messagingSystem.call( + 'NetworkController:setActiveNetwork', + clientId, + ); + + this.update((state) => { + state.nonEvmSelected = false; + }); + } +} + diff --git a/packages/multichain-network-controller/src/constants.ts b/packages/multichain-network-controller/src/constants.ts new file mode 100644 index 0000000000..fe7934215b --- /dev/null +++ b/packages/multichain-network-controller/src/constants.ts @@ -0,0 +1,47 @@ +import { NetworkStatus } from '@metamask/network-controller'; + +import type { + MultichainNetworkConfiguration, + MultichainNetworkMetadata, +} from './MultichainNetworkController'; +import { BtcScope, SolScope } from '@metamask/keyring-api'; + + +export const btcNativeAsset = `${BtcScope.Mainnet}/slip44:0`; +export const solNativeAsset = `${SolScope.Mainnet}/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`; + +export const multichainNetworkConfigurations: Record = { + [BtcScope.Mainnet] : { + chainId: BtcScope.Mainnet, + + name: 'Bitcoin Mainnet', + + blockExplorerUrls: [], + + nativeAsset: btcNativeAsset, + + isEvm: false, + }, + [SolScope.Mainnet]: { + chainId: SolScope.Mainnet, + + name: 'Solana Mainnet', + + blockExplorerUrls: [], + + nativeAsset: solNativeAsset, + + isEvm: false, + }, +}; + +export const networksMetadata: Record = { + [BtcScope.Mainnet]: { + features: [], + status: NetworkStatus.Available, + }, + [SolScope.Mainnet]: { + features: [], + status: NetworkStatus.Available, + }, +}; diff --git a/packages/multichain-network-controller/src/index.ts b/packages/multichain-network-controller/src/index.ts new file mode 100644 index 0000000000..07cd878b4f --- /dev/null +++ b/packages/multichain-network-controller/src/index.ts @@ -0,0 +1,12 @@ +export type { + MultichainNetworkConfiguration, + MultichainNetworkMetadata, + MultichainNetworkControllerState, + MultichainNetworkControllerGetStateAction, + MultichainNetworkControllerMessenger, +} from './MultichainNetworkController'; +export { MultichainNetworkController } from './MultichainNetworkController'; +export { + multichainNetworkConfigurations, + networksMetadata, +} from './constants'; diff --git a/packages/multichain-network-controller/tsconfig.build.json b/packages/multichain-network-controller/tsconfig.build.json new file mode 100644 index 0000000000..7609b693a3 --- /dev/null +++ b/packages/multichain-network-controller/tsconfig.build.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "references": [ + { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../network-controller/tsconfig.build.json" }, + { "path": "../accounts-controller/tsconfig.build.json" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/multichain-network-controller/tsconfig.json b/packages/multichain-network-controller/tsconfig.json new file mode 100644 index 0000000000..1646207284 --- /dev/null +++ b/packages/multichain-network-controller/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "references": [ + { "path": "../base-controller" }, + { "path": "../accounts-controller" }, + { "path": "../network-controller" } + ], + "include": ["../../types", "./src"], +} diff --git a/packages/multichain-network-controller/typedoc.json b/packages/multichain-network-controller/typedoc.json new file mode 100644 index 0000000000..c9da015dbf --- /dev/null +++ b/packages/multichain-network-controller/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["./src/index.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs", + "tsconfig": "./tsconfig.build.json" +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 6017c40c24..1cae7942d5 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -41,7 +41,8 @@ "path": "./packages/token-search-discovery-controller/tsconfig.build.json" }, { "path": "./packages/transaction-controller/tsconfig.build.json" }, - { "path": "./packages/user-operation-controller/tsconfig.build.json" } + { "path": "./packages/user-operation-controller/tsconfig.build.json" }, + { "path": "./packages/multichain-network-controller/tsconfig.build.json" } ], "files": [], "include": [] diff --git a/yarn.lock b/yarn.lock index 40d8c3058b..32c4206f98 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3208,6 +3208,18 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-api@npm:^16.1.0": + version: 16.1.0 + resolution: "@metamask/keyring-api@npm:16.1.0" + dependencies: + "@metamask/keyring-utils": "npm:^2.0.0" + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.1.0" + bech32: "npm:^2.0.0" + checksum: 10/6a3877e8e70b02728d4dc056a0eab5d961dd3089236539827ffb4194a3acdc9c71436cc3248ed1d6bf62d3dc0b6e69e2379177db6d690af1a77d4698767324fd + languageName: node + linkType: hard + "@metamask/keyring-controller@npm:^19.0.4, @metamask/keyring-controller@workspace:packages/keyring-controller": version: 0.0.0-use.local resolution: "@metamask/keyring-controller@workspace:packages/keyring-controller" @@ -3297,6 +3309,17 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-utils@npm:^2.0.0": + version: 2.0.0 + resolution: "@metamask/keyring-utils@npm:2.0.0" + dependencies: + "@metamask/superstruct": "npm:^3.1.0" + "@metamask/utils": "npm:^11.1.0" + bitcoin-address-validation: "npm:^2.2.3" + checksum: 10/f7514821fb3bd5f5be575e0d74d5cf8becbdeac35a3e13dcd9e8bf789ba34aa2072783bdc3d0ddac479b97c986bcb54d77cdccedf5945d1c33ef310790e90efb + languageName: node + linkType: hard + "@metamask/logging-controller@npm:^6.0.3, @metamask/logging-controller@workspace:packages/logging-controller": version: 0.0.0-use.local resolution: "@metamask/logging-controller@workspace:packages/logging-controller" @@ -3344,6 +3367,29 @@ __metadata: languageName: node linkType: hard +"@metamask/multichain-network-controller@workspace:packages/multichain-network-controller": + version: 0.0.0-use.local + resolution: "@metamask/multichain-network-controller@workspace:packages/multichain-network-controller" + dependencies: + "@metamask/auto-changelog": "npm:^3.4.4" + "@metamask/base-controller": "npm:^7.1.1" + "@metamask/keyring-api": "npm:^16.1.0" + "@metamask/utils": "npm:^11.0.1" + "@types/jest": "npm:^27.4.1" + deepmerge: "npm:^4.2.2" + immer: "npm:^9.0.6" + jest: "npm:^27.5.1" + nock: "npm:^13.3.1" + ts-jest: "npm:^27.1.4" + typedoc: "npm:^0.24.8" + typedoc-plugin-missing-exports: "npm:^2.0.0" + typescript: "npm:~5.2.2" + peerDependencies: + "@metamask/accounts-controller": ^21.0.1 + "@metamask/network-controller": ^22.1.1 + languageName: unknown + linkType: soft + "@metamask/multichain-transactions-controller@workspace:packages/multichain-transactions-controller": version: 0.0.0-use.local resolution: "@metamask/multichain-transactions-controller@workspace:packages/multichain-transactions-controller" @@ -4164,6 +4210,23 @@ __metadata: languageName: node linkType: hard +"@metamask/utils@npm:^11.1.0": + version: 11.1.0 + resolution: "@metamask/utils@npm:11.1.0" + dependencies: + "@ethereumjs/tx": "npm:^4.2.0" + "@metamask/superstruct": "npm:^3.1.0" + "@noble/hashes": "npm:^1.3.1" + "@scure/base": "npm:^1.1.3" + "@types/debug": "npm:^4.1.7" + debug: "npm:^4.3.4" + pony-cause: "npm:^2.1.10" + semver: "npm:^7.5.4" + uuid: "npm:^9.0.1" + checksum: 10/756f13987881fe26adaa0a54354bc5af20cedee4dd228a736d481697dc634adb9e6e54d8f1dcc1d487b2376ab4ba8c576ecbb24beab2fb63aff721d0d5c0f5fe + languageName: node + linkType: hard + "@metamask/utils@npm:^8.2.0": version: 8.5.0 resolution: "@metamask/utils@npm:8.5.0"