diff --git a/packages/accounts-controller/CHANGELOG.md b/packages/accounts-controller/CHANGELOG.md new file mode 100644 index 00000000000..98d6e3847af --- /dev/null +++ b/packages/accounts-controller/CHANGELOG.md @@ -0,0 +1,11 @@ +# 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 +- Initial release + +[Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/accounts-controller/LICENSE b/packages/accounts-controller/LICENSE new file mode 100644 index 00000000000..ddfbecf9020 --- /dev/null +++ b/packages/accounts-controller/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2018 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/accounts-controller/README.md b/packages/accounts-controller/README.md new file mode 100644 index 00000000000..690cc95ba32 --- /dev/null +++ b/packages/accounts-controller/README.md @@ -0,0 +1,15 @@ +# `@metamask/accounts-controller` + +Manages internal accounts used in place of regular ethereum addresses. + +## Installation + +`yarn add @metamask/accounts-controller` + +or + +`npm install @metamask/accounts-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/accounts-controller/jest.config.js b/packages/accounts-controller/jest.config.js new file mode 100644 index 00000000000..ca084133399 --- /dev/null +++ b/packages/accounts-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/accounts-controller/package.json b/packages/accounts-controller/package.json new file mode 100644 index 00000000000..b94e387c4d3 --- /dev/null +++ b/packages/accounts-controller/package.json @@ -0,0 +1,65 @@ +{ + "name": "@metamask/accounts-controller", + "version": "0.0.0", + "description": "Manages internal accounts", + "keywords": [ + "MetaMask", + "Ethereum" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/accounts-controller#readme", + "bugs": { + "url": "https://github.com/MetaMask/core/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/core.git" + }, + "license": "MIT", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/" + ], + "scripts": { + "build:docs": "typedoc", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/accounts-controller", + "publish:preview": "yarn npm publish --tag preview", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "@metamask/base-controller": "^3.2.1", + "@metamask/eth-snap-keyring": "^0.2.2", + "@metamask/keyring-api": "^0.2.5", + "@metamask/snaps-utils": "^1.0.1", + "@metamask/utils": "^6.2.0", + "deepmerge": "^4.2.2", + "eth-rpc-errors": "^4.0.2", + "ethereumjs-util": "^7.0.10", + "immer": "^9.0.6", + "nanoid": "^3.1.31", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@metamask/auto-changelog": "^3.1.0", + "@metamask/keyring-controller": "^7.4.0", + "@metamask/snaps-controllers": "^1.0.1", + "@types/jest": "^27.4.1", + "@types/readable-stream": "^2.3.0", + "jest": "^27.5.1", + "ts-jest": "^27.1.4", + "typedoc": "^0.22.15", + "typedoc-plugin-missing-exports": "^0.22.6", + "typescript": "~4.6.3" + }, + "peerDependencies": { + "@metamask/keyring-controller": "^7.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/accounts-controller/src/AccountsController.test.ts b/packages/accounts-controller/src/AccountsController.test.ts new file mode 100644 index 00000000000..b070a7ec550 --- /dev/null +++ b/packages/accounts-controller/src/AccountsController.test.ts @@ -0,0 +1,1515 @@ +import { ControllerMessenger } from '@metamask/base-controller'; +import type { InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType, EthMethod } from '@metamask/keyring-api'; +import type { SnapControllerState } from '@metamask/snaps-controllers'; +import { SnapStatus } from '@metamask/snaps-utils'; +import * as uuid from 'uuid'; + +import type { + AccountsControllerActions, + AccountsControllerEvents, + AccountsControllerState, +} from './AccountsController'; +import { AccountsController, keyringTypeToName } from './AccountsController'; + +jest.mock('uuid'); +const mockUUID = jest.spyOn(uuid, 'v4'); + +const defaultState: AccountsControllerState = { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, +}; + +const mockGetKeyringForAccount = jest.fn(); +const mockGetKeyringByType = jest.fn(); +const mockGetAccounts = jest.fn(); + +const mockAccount: InternalAccount = { + id: 'mock-id', + address: '0x123', + options: {}, + methods: [...Object.values(EthMethod)], + type: EthAccountType.Eoa, + metadata: { + name: 'Account 1', + keyring: { type: 'HD Key Tree' }, + lastSelected: 1691565967656, + }, +}; + +const mockAccount2: InternalAccount = { + id: 'mock-id2', + address: '0x1234', + options: {}, + methods: [...Object.values(EthMethod)], + type: EthAccountType.Eoa, + metadata: { + name: 'Account 2', + keyring: { type: 'HD Key Tree' }, + lastSelected: 1955565967656, + }, +}; + +/** + * Creates an `InternalAccount` object from the given normal account properties. + * + * @param props - The properties of the normal account. + * @param props.id - The ID of the account. + * @param props.name - The name of the account. + * @param props.address - The address of the account. + * @param props.keyringType - The type of the keyring associated with the account. + * @param props.snapId - The id of the snap. + * @param props.snapEnabled - The status of the snap + * @returns The `InternalAccount` object created from the normal account properties. + */ +function createExpectedInternalAccount({ + id, + name, + address, + keyringType, + snapId, + snapEnabled = true, +}: { + id: string; + name: string; + address: string; + keyringType: string; + snapId?: string; + snapEnabled?: boolean; +}): InternalAccount { + const account: InternalAccount = { + id, + address, + options: {}, + methods: [...Object.values(EthMethod)], + type: EthAccountType.Eoa, + metadata: { + name, + keyring: { type: keyringType }, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + lastSelected: undefined, + }, + }; + + if (snapId) { + account.metadata.snap = { + id: snapId, + name: 'snap-name', + enabled: Boolean(snapEnabled), + }; + } + + return account; +} + +/** + * Sets the `lastSelected` property of the given `account` to `expect.any(Number)`. + * + * @param account - The account to modify. + * @returns The modified account. + */ +function setLastSelectedAsAny(account: InternalAccount): InternalAccount { + return { + ...account, + metadata: { + ...account.metadata, + lastSelected: expect.any(Number), + }, + } as InternalAccount; +} + +/** + * Builds a new instance of the ControllerMessenger class for the AccountsController. + * + * @returns A new instance of the ControllerMessenger class for the AccountsController. + */ +function buildMessenger() { + return new ControllerMessenger< + AccountsControllerActions, + AccountsControllerEvents + >(); +} + +/** + * Builds a restricted messenger for the AccountsController. + * + * @param messenger - The messenger to restrict. + * @returns The restricted messenger. + */ +function buildAccountsControllerMessenger(messenger = buildMessenger()) { + return messenger.getRestricted({ + name: 'AccountsController', + allowedEvents: [ + 'SnapController:stateChange', + 'KeyringController:accountRemoved', + 'KeyringController:stateChange', + ], + allowedActions: [ + 'KeyringController:getAccounts', + 'KeyringController:getKeyringForAccount', + 'KeyringController:getKeyringsByType', + 'AccountsController:listAccounts', + 'AccountsController:setAccountName', + 'AccountsController:setSelectedAccount', + 'AccountsController:updateAccounts', + ], + }); +} + +/** + * Sets up an instance of the AccountsController class with the given initial state and callbacks. + * + * @param options - The options object. + * @param [options.initialState] - The initial state to use for the AccountsController. + * @param [options.keyringApiEnabled] - Whether or not the keyring API is enabled. + * @param [options.messenger] - Messenger to use for the AccountsController. + * @returns An instance of the AccountsController class. + */ +function setupAccountsController({ + initialState = {}, + keyringApiEnabled = true, + messenger = buildMessenger(), +}: { + initialState?: Partial; + keyringApiEnabled?: boolean; + messenger?: ControllerMessenger< + AccountsControllerActions, + AccountsControllerEvents + >; +}): AccountsController { + const accountsControllerMessenger = + buildAccountsControllerMessenger(messenger); + + const accountsController = new AccountsController({ + messenger: accountsControllerMessenger, + state: { ...defaultState, ...initialState }, + keyringApiEnabled, + }); + return accountsController; +} + +describe('AccountsController', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('onSnapStateChange', () => { + it('should not be used when keyringApiEnabled is false', async () => { + const messenger = buildMessenger(); + const snapStateChangeSpy = jest.fn(); + setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: false, + messenger, + }); + + messenger.publish( + 'SnapController:stateChange', + {} as any as SnapControllerState, + [], + ); + + expect(snapStateChangeSpy).not.toHaveBeenCalled(); + }); + + it('should be used enable an account if the snap is enabled and not blocked', async () => { + const messenger = buildMessenger(); + const mockSnapAccount = createExpectedInternalAccount({ + id: 'mock-id', + name: 'Snap Account 1', + address: '0x0', + keyringType: 'Snap Keyring', + snapId: 'mock-snap', + snapEnabled: false, + }); + const mockSnapChangeState = { + snaps: { + 'mock-snap': { + enabled: true, + id: 'mock-snap', + blocked: false, + status: SnapStatus.Running, + }, + }, + } as any as SnapControllerState; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockSnapAccount.id]: mockSnapAccount, + }, + selectedAccount: mockSnapAccount.id, + }, + }, + keyringApiEnabled: true, + messenger, + }); + + messenger.publish('SnapController:stateChange', mockSnapChangeState, []); + + const updatedAccount = accountsController.getAccountExpect( + mockSnapAccount.id, + ); + + expect(updatedAccount.metadata.snap?.enabled).toBe(true); + }); + + it('should be used disable an account if the snap is disabled', async () => { + const messenger = buildMessenger(); + const mockSnapAccount = createExpectedInternalAccount({ + id: 'mock-id', + name: 'Snap Account 1', + address: '0x0', + keyringType: 'Snap Keyring', + snapId: 'mock-snap', + }); + const mockSnapChangeState = { + snaps: { + 'mock-snap': { + enabled: false, + id: 'mock-snap', + blocked: false, + status: SnapStatus.Running, + }, + }, + } as any as SnapControllerState; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockSnapAccount.id]: mockSnapAccount, + }, + selectedAccount: mockSnapAccount.id, + }, + }, + keyringApiEnabled: true, + messenger, + }); + + messenger.publish('SnapController:stateChange', mockSnapChangeState, []); + + const updatedAccount = accountsController.getAccountExpect( + mockSnapAccount.id, + ); + + expect(updatedAccount.metadata.snap?.enabled).toBe(false); + }); + + it('should be used disable an account if the snap is blocked', async () => { + const messenger = buildMessenger(); + const mockSnapAccount = createExpectedInternalAccount({ + id: 'mock-id', + name: 'Snap Account 1', + address: '0x0', + keyringType: 'Snap Keyring', + snapId: 'mock-snap', + }); + const mockSnapChangeState = { + snaps: { + 'mock-snap': { + enabled: true, + id: 'mock-snap', + blocked: true, + status: SnapStatus.Running, + }, + }, + } as any as SnapControllerState; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockSnapAccount.id]: mockSnapAccount, + }, + selectedAccount: mockSnapAccount.id, + }, + }, + keyringApiEnabled: true, + messenger, + }); + + messenger.publish('SnapController:stateChange', mockSnapChangeState, []); + + const updatedAccount = accountsController.getAccountExpect( + mockSnapAccount.id, + ); + + expect(updatedAccount.metadata.snap?.enabled).toBe(false); + }); + }); + + describe('onKeyringStateChange', () => { + it('should only update if the keyring is unlocked', async () => { + const messenger = buildMessenger(); + + const mockNewKeyringState = { + isUnlocked: false, + keyrings: [ + { + accounts: [mockAccount.address, mockAccount2.address], + type: 'HD Key Tree', + }, + ], + }; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: true, + messenger, + }); + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + const accounts = accountsController.listAccounts(); + + expect(accounts).toStrictEqual([]); + }); + + it('should add new accounts', async () => { + const messenger = buildMessenger(); + mockUUID.mockReturnValueOnce('mock-id').mockReturnValueOnce('mock-id2'); + + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValueOnce([ + mockAccount.address, + mockAccount2.address, + ]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'HD Key Tree' }), + ); + + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: 'HD Key Tree', + accounts: [mockAccount.address, mockAccount2.address], + }, + ], + }; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + }, + selectedAccount: mockAccount.id, + }, + }, + keyringApiEnabled: false, + messenger, + }); + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const accounts = accountsController.listAccounts(); + + expect(accounts).toStrictEqual([ + mockAccount, + setLastSelectedAsAny(mockAccount2), + ]); + }); + + it('should handle keyring reinitialization', async () => { + const messenger = buildMessenger(); + const mockInitialAccount = createExpectedInternalAccount({ + id: 'mock-id', + name: 'Account 1', + address: '0x123', + keyringType: 'HD Key Tree', + }); + const mockReinitialisedAccount = createExpectedInternalAccount({ + id: 'mock-id2', + name: 'Account 1', + address: '0x456', + keyringType: 'HD Key Tree', + }); + mockUUID.mockReturnValueOnce('mock-id2'); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValueOnce([ + mockReinitialisedAccount.address, + ]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'HD Key Tree' }), + ); + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: 'HD Key Tree', + accounts: [mockReinitialisedAccount.address], + }, + ], + }; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockInitialAccount.id]: mockInitialAccount, + }, + selectedAccount: mockInitialAccount.id, + }, + }, + keyringApiEnabled: false, + messenger, + }); + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const selectedAccount = accountsController.getSelectedAccount(); + const accounts = accountsController.listAccounts(); + const expectedAccount = setLastSelectedAsAny(mockReinitialisedAccount); + + expect(selectedAccount).toStrictEqual(expectedAccount); + expect(accounts).toStrictEqual([expectedAccount]); + }); + + it('should delete accounts if its gone from the keyring state', async () => { + const messenger = buildMessenger(); + mockUUID.mockReturnValueOnce('mock-id2'); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValueOnce([mockAccount2.address]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'HD Key Tree' }), + ); + + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: 'HD Key Tree', + accounts: [mockAccount2.address], + }, + ], + }; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + [mockAccount2.id]: mockAccount2, + }, + selectedAccount: mockAccount.id, + }, + }, + keyringApiEnabled: false, + messenger, + }); + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const accounts = accountsController.listAccounts(); + + expect(accounts).toStrictEqual([setLastSelectedAsAny(mockAccount2)]); + expect(accountsController.getSelectedAccount()).toStrictEqual( + setLastSelectedAsAny(mockAccount2), + ); + }); + + it('should delete accounts and set the most recent lastSelected account', async () => { + const messenger = buildMessenger(); + mockUUID.mockReturnValueOnce('mock-id').mockReturnValueOnce('mock-id2'); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValue([ + mockAccount.address, + mockAccount2.address, + ]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'HD Key Tree' }), + ); + + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: 'HD Key Tree', + accounts: [mockAccount.address, mockAccount2.address], + }, + ], + }; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + 'missing-account': { + address: '0x999', + } as InternalAccount, + [mockAccount.id]: mockAccount, + [mockAccount2.id]: mockAccount2, + }, + selectedAccount: 'missing-account', + }, + }, + keyringApiEnabled: false, + messenger, + }); + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const accounts = accountsController.listAccounts(); + + expect(accounts).toStrictEqual([ + setLastSelectedAsAny(mockAccount), + setLastSelectedAsAny(mockAccount2), + ]); + expect(accountsController.getSelectedAccount()).toStrictEqual( + setLastSelectedAsAny(mockAccount2), + ); + }); + + it('should delete accounts and set the most recent lastSelected account when there are accounts that have never been selected', async () => { + const messenger = buildMessenger(); + mockUUID.mockReturnValueOnce('mock-id').mockReturnValueOnce('mock-id2'); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValue([ + mockAccount.address, + mockAccount2.address, + ]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'HD Key Tree' }), + ); + + const mockAccount2WithoutLastSelected = { + ...mockAccount2, + metadata: { + ...mockAccount2.metadata, + lastSelected: undefined, + }, + }; + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: 'HD Key Tree', + accounts: [mockAccount.address, mockAccount2.address], + }, + ], + }; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + 'missing-account': { + address: '0x999', + } as InternalAccount, + [mockAccount.id]: mockAccount, + [mockAccount2.id]: mockAccount2WithoutLastSelected, + }, + selectedAccount: 'missing-account', + }, + }, + keyringApiEnabled: false, + messenger, + }); + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const accounts = accountsController.listAccounts(); + + expect(accounts).toStrictEqual([ + setLastSelectedAsAny(mockAccount), + mockAccount2WithoutLastSelected, + ]); + expect(accountsController.getSelectedAccount()).toStrictEqual( + setLastSelectedAsAny(mockAccount), + ); + }); + + it('should delete the account and select the account with the most recent lastSelected', async () => { + const messenger = buildMessenger(); + mockUUID.mockReturnValueOnce('mock-id').mockReturnValueOnce('mock-id2'); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValue([ + mockAccount.address, + mockAccount2.address, + ]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'HD Key Tree' }), + ); + + const mockAccountWithoutLastSelected = { + ...mockAccount, + metadata: { + ...mockAccount.metadata, + lastSelected: undefined, + }, + }; + + const mockAccount2WithoutLastSelected = { + ...mockAccount2, + metadata: { + ...mockAccount2.metadata, + lastSelected: undefined, + }, + }; + + const mockNewKeyringState = { + isUnlocked: true, + keyrings: [ + { + type: 'HD Key Tree', + accounts: [ + mockAccountWithoutLastSelected.address, + mockAccount2.address, + ], + }, + ], + }; + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + 'missing-account': { + address: '0x999', + } as InternalAccount, + [mockAccount2.id]: mockAccount2WithoutLastSelected, + [mockAccount.id]: mockAccountWithoutLastSelected, + }, + selectedAccount: 'missing-account', + }, + }, + keyringApiEnabled: false, + messenger, + }); + + messenger.publish( + 'KeyringController:stateChange', + mockNewKeyringState, + [], + ); + + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const accounts = accountsController.listAccounts(); + + expect(accounts).toStrictEqual([ + setLastSelectedAsAny(mockAccountWithoutLastSelected), + mockAccount2WithoutLastSelected, + ]); + expect(accountsController.getSelectedAccount()).toStrictEqual( + setLastSelectedAsAny(mockAccountWithoutLastSelected), + ); + }); + }); + + describe('constructor', () => { + it('should select next account if selectedAccount is not found', async () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + }, + selectedAccount: 'missing account', + }, + }, + keyringApiEnabled: true, + }); + + const selectedAccount = accountsController.getSelectedAccount(); + + expect(setLastSelectedAsAny(selectedAccount)).toStrictEqual( + setLastSelectedAsAny(mockAccount), + ); + }); + }); + + describe('updateAccounts', () => { + const mockAddress1 = '0x123'; + const mockAddress2 = '0x456'; + const mockSnapAccount = { + ...mockAccount, + metadata: { + ...mockAccount.metadata, + keyring: { + type: 'Snap Keyring', + }, + snap: { + enabled: true, + id: 'mock-snap-id', + name: '', + }, + lastSelected: undefined, + }, + }; + const mockSnapAccount2 = { + ...mockAccount2, + metadata: { + ...mockAccount2.metadata, + keyring: { + type: 'Snap Keyring', + }, + snap: { + enabled: true, + id: 'mock-snap-id2', + name: 'snap-name', + }, + lastSelected: undefined, + }, + }; + + it('should update accounts with normal accounts', async () => { + mockUUID.mockReturnValueOnce('mock-id').mockReturnValueOnce('mock-id2'); + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValueOnce([mockAddress1, mockAddress2]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'HD Key Tree' }), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: false, + messenger, + }); + const expectedAccounts = [ + createExpectedInternalAccount({ + name: 'Account 1', + id: 'mock-id', + address: mockAddress1, + keyringType: 'HD Key Tree', + }), + createExpectedInternalAccount({ + name: 'Account 2', + id: 'mock-id2', + address: mockAddress2, + keyringType: 'HD Key Tree', + }), + ]; + + await accountsController.updateAccounts(); + + expect(accountsController.listAccounts()).toStrictEqual(expectedAccounts); + }); + + it('should update accounts with snap accounts when snap keyring is defined and has accounts', async () => { + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValueOnce([]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringsByType', + mockGetKeyringByType.mockResolvedValue([ + { + type: 'Snap Keyring', + listAccounts: async () => [mockSnapAccount, mockSnapAccount2], + }, + ]), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: true, + messenger, + }); + + const expectedAccount1 = { + ...mockSnapAccount, + metadata: { + ...mockSnapAccount.metadata, + name: 'Snap Account 1', + }, + }; + + const expectedAccount2 = { + ...mockSnapAccount2, + metadata: { + ...mockSnapAccount2.metadata, + name: 'Snap Account 2', + }, + }; + + const expectedAccounts = [expectedAccount1, expectedAccount2]; + + await accountsController.updateAccounts(); + + expect(accountsController.listAccounts()).toStrictEqual(expectedAccounts); + }); + + it('should return an empty array if the snap keyring is not defined', async () => { + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValueOnce([]), + ); + + messenger.registerActionHandler( + 'KeyringController:getKeyringsByType', + mockGetKeyringByType.mockReturnValueOnce([undefined]), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: true, + messenger, + }); + + const expectedAccounts: InternalAccount[] = []; + + await accountsController.updateAccounts(); + + expect(accountsController.listAccounts()).toStrictEqual(expectedAccounts); + }); + + it('should filter snap accounts from normalAccounts', async () => { + mockUUID.mockReturnValueOnce('mock-id'); + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getKeyringsByType', + mockGetKeyringByType.mockReturnValueOnce([ + { + type: 'Snap Keyring', + listAccounts: async () => [mockSnapAccount2], + }, + ]), + ); + + // first account will be normal, second will be a snap account + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValue([mockAddress1, '0x1234']), + ); + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount + .mockResolvedValueOnce({ type: 'HD Key Tree' }) + .mockResolvedValueOnce({ type: 'Snap Keyring' }), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: true, + messenger, + }); + const expectedAccounts = [ + createExpectedInternalAccount({ + name: 'Account 1', + id: 'mock-id', + address: mockAddress1, + keyringType: 'HD Key Tree', + }), + createExpectedInternalAccount({ + name: 'Snap Account 1', // it is Snap Account 1 because it is the only snap account + id: mockSnapAccount2.id, + address: mockSnapAccount2.address, + keyringType: 'Snap Keyring', + snapId: 'mock-snap-id2', + }), + ]; + + await accountsController.updateAccounts(); + + expect(accountsController.listAccounts()).toStrictEqual(expectedAccounts); + }); + + it('should filter snap accounts from normalAccounts even if the snap account is listed before normal accounts', async () => { + mockUUID.mockReturnValue('mock-id'); + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getKeyringsByType', + mockGetKeyringByType.mockReturnValueOnce([ + { + type: 'Snap Keyring', + listAccounts: async () => [mockSnapAccount2], + }, + ]), + ); + + // first account will be normal, second will be a snap account + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValue(['0x1234', mockAddress1]), + ); + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount + .mockResolvedValueOnce({ type: 'Snap Keyring' }) + .mockResolvedValueOnce({ type: 'HD Key Tree' }), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: true, + messenger, + }); + const expectedAccounts = [ + createExpectedInternalAccount({ + name: 'Account 1', + id: 'mock-id', + address: mockAddress1, + keyringType: 'HD Key Tree', + }), + createExpectedInternalAccount({ + name: 'Snap Account 1', // it is Snap Account 1 because it is the only snap account + id: mockSnapAccount2.id, + address: mockSnapAccount2.address, + keyringType: 'Snap Keyring', + snapId: 'mock-snap-id2', + snapEnabled: true, + }), + ]; + + await accountsController.updateAccounts(); + + expect(accountsController.listAccounts()).toStrictEqual(expectedAccounts); + }); + + it.each([ + 'Simple Key Pair', + 'HD Key Tree', + 'Trezor Hardware', + 'Ledger Hardware', + 'Lattice Hardware', + 'QR Hardware Wallet Device', + 'Custody', + ])('should add accounts for %s type', async (keyringType) => { + mockUUID.mockReturnValue('mock-id'); + + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValue([mockAddress1]), + ); + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: keyringType }), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: false, + messenger, + }); + + const expectedAccounts = [ + createExpectedInternalAccount({ + name: `${keyringTypeToName(keyringType)} 1`, + id: 'mock-id', + address: mockAddress1, + keyringType, + }), + ]; + + await accountsController.updateAccounts(); + + expect(accountsController.listAccounts()).toStrictEqual(expectedAccounts); + }); + + it('should throw an error if the keyring type is unknown', async () => { + mockUUID.mockReturnValue('mock-id'); + + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValue([mockAddress1]), + ); + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValue({ type: 'unknown' }), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + keyringApiEnabled: false, + messenger, + }); + + await expect(accountsController.updateAccounts()).rejects.toThrow( + 'Unknown keyring unknown', + ); + }); + }); + + describe('loadBackup', () => { + it('should load a backup', async () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, + }, + }); + + accountsController.loadBackup({ + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + }, + selectedAccount: mockAccount.id, + }, + }); + + expect(accountsController.state).toStrictEqual({ + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + }, + selectedAccount: mockAccount.id, + }, + }); + }); + + it('should not load backup if the data is undefined', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + + // @ts-expect-error incorrect state + accountsController.loadBackup({}); + + expect(accountsController.state).toStrictEqual({ + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + }, + selectedAccount: mockAccount.id, + }, + }); + }); + }); + + describe('getAccount', () => { + it('should return an account by ID', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + + const result = accountsController.getAccount(mockAccount.id); + + expect(result).toStrictEqual( + setLastSelectedAsAny(mockAccount as InternalAccount), + ); + }); + it('should return undefined for an unknown account ID', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + + const result = accountsController.getAccount("I don't exist"); + + expect(result).toBeUndefined(); + }); + }); + + describe('listAccounts', () => { + it('should return a list of accounts', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + [mockAccount2.id]: mockAccount2, + }, + selectedAccount: mockAccount.id, + }, + }, + }); + + const result = accountsController.listAccounts(); + + expect(result).toStrictEqual([ + setLastSelectedAsAny(mockAccount as InternalAccount), + setLastSelectedAsAny(mockAccount2 as InternalAccount), + ]); + }); + }); + + describe('getAccountExpect', () => { + it('should return an account by ID', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + const result = accountsController.getAccountExpect(mockAccount.id); + + expect(result).toStrictEqual( + setLastSelectedAsAny(mockAccount as InternalAccount), + ); + }); + + it('should throw an error for an unknown account ID', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + + expect(() => accountsController.getAccountExpect('unknown id')).toThrow( + `Account Id unknown id not found`, + ); + }); + + it('should handle the edge case of undefined accountId during onboarding', async () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + + // @ts-expect-error forcing undefined accountId + expect(accountsController.getAccountExpect(undefined)).toStrictEqual({ + id: '', + address: '', + options: {}, + methods: [], + type: EthAccountType.Eoa, + metadata: { + name: '', + keyring: { + type: '', + }, + }, + }); + }); + }); + + describe('getSelectedAccount', () => { + it('should return the selected account', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + const result = accountsController.getAccountExpect(mockAccount.id); + + expect(result).toStrictEqual( + setLastSelectedAsAny(mockAccount as InternalAccount), + ); + }); + }); + + describe('setSelectedAccount', () => { + it('should set the selected account', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + [mockAccount2.id]: mockAccount2, + }, + selectedAccount: mockAccount.id, + }, + }, + }); + + accountsController.setSelectedAccount(mockAccount2.id); + + expect( + accountsController.state.internalAccounts.selectedAccount, + ).toStrictEqual(mockAccount2.id); + }); + + it('should throw an error for an unknown account ID', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + [mockAccount2.id]: mockAccount2, + }, + selectedAccount: mockAccount.id, + }, + }, + }); + + expect(() => accountsController.setSelectedAccount('unknown id')).toThrow( + `Account Id unknown id not found`, + ); + }); + }); + + describe('setAccountName', () => { + it('should set the name of an existing account', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + accountsController.setAccountName(mockAccount.id, 'new name'); + + expect( + accountsController.getAccountExpect(mockAccount.id).metadata.name, + ).toBe('new name'); + }); + + it('should throw an error if the account name already exists', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { + [mockAccount.id]: mockAccount, + [mockAccount2.id]: mockAccount2, + }, + selectedAccount: mockAccount.id, + }, + }, + }); + + expect(() => + accountsController.setAccountName(mockAccount.id, 'Account 2'), + ).toThrow('Account name already exists'); + }); + + it('should throw an error if the account ID is not found', () => { + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + }); + expect(() => + accountsController.setAccountName('unknown account', 'new name'), + ).toThrow(`Account Id unknown account not found`); + }); + }); + + describe('actions', () => { + beforeEach(() => { + jest.spyOn(AccountsController.prototype, 'setSelectedAccount'); + jest.spyOn(AccountsController.prototype, 'listAccounts'); + jest.spyOn(AccountsController.prototype, 'setAccountName'); + jest.spyOn(AccountsController.prototype, 'updateAccounts'); + }); + + describe('setSelectedAccount', () => { + it('should set the selected account', async () => { + const messenger = buildMessenger(); + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + messenger, + }); + + await messenger.call( + 'AccountsController:setSelectedAccount', + 'mock-id', + ); + expect(accountsController.setSelectedAccount).toHaveBeenCalledWith( + 'mock-id', + ); + }); + }); + + describe('listAccounts', () => { + it('should retrieve a list of accounts', async () => { + const messenger = buildMessenger(); + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + messenger, + }); + + await messenger.call('AccountsController:listAccounts'); + expect(accountsController.listAccounts).toHaveBeenCalledWith(); + }); + }); + + describe('setAccountName', () => { + it('should set the account name', async () => { + const messenger = buildMessenger(); + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + messenger, + }); + + await messenger.call( + 'AccountsController:setAccountName', + 'mock-id', + 'new name', + ); + expect(accountsController.setAccountName).toHaveBeenCalledWith( + 'mock-id', + 'new name', + ); + }); + }); + + describe('updateAccounts', () => { + it('should update accounts', async () => { + const messenger = buildMessenger(); + messenger.registerActionHandler( + 'KeyringController:getAccounts', + mockGetAccounts.mockResolvedValueOnce([]), + ); + messenger.registerActionHandler( + 'KeyringController:getKeyringsByType', + mockGetKeyringByType.mockResolvedValueOnce([]), + ); + messenger.registerActionHandler( + 'KeyringController:getKeyringForAccount', + mockGetKeyringForAccount.mockResolvedValueOnce([]), + ); + + const accountsController = setupAccountsController({ + initialState: { + internalAccounts: { + accounts: { [mockAccount.id]: mockAccount }, + selectedAccount: mockAccount.id, + }, + }, + messenger, + }); + + await messenger.call('AccountsController:updateAccounts'); + expect(accountsController.updateAccounts).toHaveBeenCalledWith(); + }); + }); + }); +}); diff --git a/packages/accounts-controller/src/AccountsController.ts b/packages/accounts-controller/src/AccountsController.ts new file mode 100644 index 00000000000..2307d0a4362 --- /dev/null +++ b/packages/accounts-controller/src/AccountsController.ts @@ -0,0 +1,607 @@ +import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { BaseControllerV2 } from '@metamask/base-controller'; +import { SnapKeyring } from '@metamask/eth-snap-keyring'; +import type { InternalAccount } from '@metamask/keyring-api'; +import { EthAccountType } from '@metamask/keyring-api'; +import type { + KeyringControllerState, + KeyringControllerEvents, + KeyringControllerGetKeyringForAccountAction, + KeyringControllerGetKeyringsByTypeAction, + KeyringControllerGetAccountsAction, +} from '@metamask/keyring-controller'; +import type { + SnapControllerEvents, + SnapControllerState, +} from '@metamask/snaps-controllers'; +import type { Keyring, Json } from '@metamask/utils'; +import { sha256FromString } from 'ethereumjs-util'; +import type { Patch } from 'immer'; +import { v4 as uuid } from 'uuid'; + +const controllerName = 'AccountsController'; + +export type AccountsControllerState = { + internalAccounts: { + accounts: Record; + selectedAccount: string; // id of the selected account + }; +}; + +export type AccountsControllerGetStateAction = { + type: `${typeof controllerName}:getState`; + handler: () => AccountsControllerState; +}; + +export type AccountsControllerSetSelectedAccount = { + type: `${typeof controllerName}:setSelectedAccount`; + handler: AccountsController['setSelectedAccount']; +}; + +export type AccountsControllerSetAccountName = { + type: `${typeof controllerName}:setAccountName`; + handler: AccountsController['setAccountName']; +}; + +export type AccountsControllerListAccounts = { + type: `${typeof controllerName}:listAccounts`; + handler: AccountsController['listAccounts']; +}; + +export type AccountsControllerUpdateAccounts = { + type: `${typeof controllerName}:updateAccounts`; + handler: AccountsController['updateAccounts']; +}; + +export type AccountsControllerActions = + | AccountsControllerGetStateAction + | AccountsControllerSetSelectedAccount + | AccountsControllerListAccounts + | AccountsControllerSetAccountName + | AccountsControllerUpdateAccounts + | KeyringControllerGetKeyringForAccountAction + | KeyringControllerGetKeyringsByTypeAction + | KeyringControllerGetAccountsAction; + +export type AccountsControllerChangeEvent = { + type: `${typeof controllerName}:stateChange`; + payload: [AccountsControllerState, Patch[]]; +}; + +export type AccountsControllerSelectedAccountChangeEvent = { + type: `${typeof controllerName}:selectedAccountChange`; + payload: [InternalAccount]; +}; + +export type AccountsControllerEvents = + | AccountsControllerChangeEvent + | AccountsControllerSelectedAccountChangeEvent + | SnapControllerEvents + | KeyringControllerEvents; + +export type AccountsControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + AccountsControllerActions, + AccountsControllerEvents, + string, + string +>; + +const accountsControllerMetadata = { + internalAccounts: { + persist: true, + anonymous: false, + }, + selectedAccount: { + persist: true, + anonymous: false, + }, +}; + +const defaultState: AccountsControllerState = { + internalAccounts: { + accounts: {}, + selectedAccount: '', + }, +}; + +/** + * Controller that manages internal accounts. + * The accounts controller is responsible for creating and managing internal accounts. + * It also provides convenience methods for accessing and updating the internal accounts. + * The accounts controller also listens for keyring state changes and updates the internal accounts accordingly. + * The accounts controller also listens for snap state changes and updates the internal accounts accordingly. + * + */ +export class AccountsController extends BaseControllerV2< + typeof controllerName, + AccountsControllerState, + AccountsControllerMessenger +> { + keyringApiEnabled: boolean; + + /** + * Constructor for AccountsController. + * + * @param options - The controller options. + * @param options.messenger - The messenger object. + * @param options.state - Initial state to set on this controller + * @param [options.keyringApiEnabled] - The keyring API enabled flag. + */ + constructor({ + messenger, + state, + keyringApiEnabled, + }: { + messenger: AccountsControllerMessenger; + state: AccountsControllerState; + keyringApiEnabled?: boolean; + }) { + super({ + messenger, + name: controllerName, + metadata: accountsControllerMetadata, + state: { + ...defaultState, + ...state, + }, + }); + + this.keyringApiEnabled = Boolean(keyringApiEnabled); + + if (this.keyringApiEnabled) { + this.messagingSystem.subscribe( + 'SnapController:stateChange', + async (snapStateState) => + await this.#handleOnSnapStateChange(snapStateState), + ); + } + + this.messagingSystem.subscribe( + 'KeyringController:stateChange', + async (keyringState) => + await this.#handleOnKeyringStateChange(keyringState), + ); + + this.#registerMessageHandlers(); + + // if somehow the selected account becomes lost then select the first account + if ( + this.state.internalAccounts.selectedAccount !== '' && + !this.getAccount(this.state.internalAccounts.selectedAccount) && + this.listAccounts().length > 0 + ) { + this.setSelectedAccount(this.listAccounts()[0].id); + } + } + + /** + * Returns the internal account object for the given account ID, if it exists. + * + * @param accountId - The ID of the account to retrieve. + * @returns The internal account object, or undefined if the account does not exist. + */ + getAccount(accountId: string): InternalAccount | undefined { + return this.state.internalAccounts.accounts[accountId]; + } + + /** + * Returns an array of all internal accounts. + * + * @returns An array of InternalAccount objects. + */ + listAccounts(): InternalAccount[] { + return Object.values(this.state.internalAccounts.accounts); + } + + /** + * Returns the internal account object for the given account ID. + * + * @param accountId - The ID of the account to retrieve. + * @returns The internal account object. + * @throws An error if the account ID is not found. + */ + getAccountExpect(accountId: string): InternalAccount { + // Edge case where the extension is setup but the srp is not yet created + // certain ui elements will query the selected address before any accounts are created. + if (!accountId) { + return { + id: '', + address: '', + options: {}, + methods: [], + type: EthAccountType.Eoa, + metadata: { + name: '', + keyring: { + type: '', + }, + }, + }; + } + + const account = this.getAccount(accountId); + if (account === undefined) { + throw new Error(`Account Id ${accountId} not found`); + } + return account; + } + + /** + * Returns the selected internal account. + * + * @returns The selected internal account. + */ + getSelectedAccount(): InternalAccount { + return this.getAccountExpect(this.state.internalAccounts.selectedAccount); + } + + /** + * Sets the selected account by its ID. + * + * @param accountId - The ID of the account to be selected. + */ + setSelectedAccount(accountId: string): void { + const account = this.getAccountExpect(accountId); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Type instantiation is excessively deep and possibly infinite. + this.update((currentState: AccountsControllerState) => { + currentState.internalAccounts.accounts[account.id].metadata.lastSelected = + Date.now(); + currentState.internalAccounts.selectedAccount = account.id; + }); + + this.messagingSystem.publish(`${this.name}:selectedAccountChange`, account); + } + + /** + * Sets the name of the account with the given ID. + * + * @param accountId - The ID of the account to set the name for. + * @param accountName - The new name for the account. + * @throws An error if an account with the same name already exists. + */ + setAccountName(accountId: string, accountName: string): void { + const account = this.getAccountExpect(accountId); + + if ( + this.listAccounts().find( + (internalAccount) => + internalAccount.metadata.name === accountName && + internalAccount.id !== accountId, + ) + ) { + throw new Error('Account name already exists'); + } + + this.update((currentState: AccountsControllerState) => { + currentState.internalAccounts.accounts[accountId] = { + ...account, + metadata: { + ...account.metadata, + name: accountName, + }, + }; + }); + } + + /** + * Updates the internal accounts list by retrieving normal and snap accounts, + * removing duplicates, and updating the metadata of each account. + * + * @returns A Promise that resolves when the accounts have been updated. + */ + async updateAccounts(): Promise { + let normalAccounts = await this.#listNormalAccounts(); + let snapAccounts: InternalAccount[] = []; + if (this.keyringApiEnabled) { + snapAccounts = await this.#listSnapAccounts(); + // remove duplicate accounts that are retrieved from the snap keyring. + normalAccounts = normalAccounts.filter( + (account) => + !snapAccounts.find( + (snapAccount) => snapAccount.address === account.address, + ), + ); + } + + // keyring type map. + const keyringTypes = new Map(); + const previousAccounts = this.state.internalAccounts.accounts; + + const accounts: Record = [ + ...normalAccounts, + ...snapAccounts, + ].reduce((internalAccountMap, internalAccount) => { + const keyringTypeName = keyringTypeToName( + internalAccount.metadata.keyring.type, + ); + const keyringAccountIndex = keyringTypes.get(keyringTypeName) ?? 0; + if (keyringAccountIndex) { + keyringTypes.set(keyringTypeName, keyringAccountIndex + 1); + } else { + keyringTypes.set(keyringTypeName, 1); + } + + const existingAccount = previousAccounts[internalAccount.id]; + + internalAccountMap[internalAccount.id] = { + ...internalAccount, + + metadata: { + ...internalAccount.metadata, + name: + existingAccount && existingAccount.metadata.name !== '' + ? existingAccount.metadata.name + : `${keyringTypeName} ${keyringAccountIndex + 1}`, + lastSelected: existingAccount?.metadata?.lastSelected, + }, + }; + + return internalAccountMap; + }, {} as Record); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Type instantiation is excessively deep and possibly infinite. + this.update((currentState: AccountsControllerState) => { + currentState.internalAccounts.accounts = accounts; + }); + } + + /** + * Loads the backup state of the accounts controller. + * + * @param backup - The backup state to load. + */ + loadBackup(backup: AccountsControllerState): void { + if (backup.internalAccounts) { + this.update((currentState: AccountsControllerState) => { + currentState.internalAccounts = backup.internalAccounts; + }); + } + } + + /** + * Returns a list of internal accounts created using the SnapKeyring. + * + * @returns A promise that resolves to an array of InternalAccount objects. + */ + async #listSnapAccounts(): Promise { + const [snapKeyring] = await this.messagingSystem.call( + 'KeyringController:getKeyringsByType', + SnapKeyring.type, + ); + // snap keyring is not available until the first account is created in the keyring controller + if (!snapKeyring) { + return []; + } + + const snapAccounts = await (snapKeyring as SnapKeyring).listAccounts(); + + return snapAccounts; + } + + /** + * Returns a list of normal accounts. + * Note: listNormalAccounts is a temporary method until the keyrings all implement the InternalAccount interface. + * Once all keyrings implement the InternalAccount interface, this method can be removed and getAccounts can be used instead. + * + * @returns A Promise that resolves to an array of InternalAccount objects. + */ + async #listNormalAccounts(): Promise { + const addresses = await this.messagingSystem.call( + 'KeyringController:getAccounts', + ); + const internalAccounts: InternalAccount[] = []; + for (const address of addresses) { + const keyring = await this.messagingSystem.call( + 'KeyringController:getKeyringForAccount', + address, + ); + const v4options = { + random: sha256FromString(address).slice(0, 16), + }; + + internalAccounts.push({ + id: uuid(v4options), + address, + options: {}, + methods: [ + 'personal_sign', + 'eth_sign', + 'eth_signTransaction', + 'eth_signTypedData_v1', + 'eth_signTypedData_v3', + 'eth_signTypedData_v4', + ], + type: EthAccountType.Eoa, + metadata: { + name: '', + keyring: { + type: (keyring as Keyring).type, + }, + }, + }); + } + + return internalAccounts.filter( + (account) => account.metadata.keyring.type !== 'Snap Keyring', + ); + } + + /** + * Handles the removal of the currently selected account by selecting the previous account in the list. + */ + #handleSelectedAccountRemoved() { + const previousAccount = this.listAccounts() + .filter( + (account) => account.id !== this.state.internalAccounts.selectedAccount, + ) + .sort((accountA, accountB) => { + // sort by lastSelected descending + return ( + (accountB.metadata.lastSelected ?? 0) - + (accountA.metadata.lastSelected ?? 0) + ); + })[0]; + + this.setSelectedAccount(previousAccount.id); + } + + /** + * Handles changes in the keyring state, specifically when new accounts are added or removed. + * + * @param keyringState - The new state of the keyring controller. + * @returns A Promise that resolves when the function has finished executing. + */ + async #handleOnKeyringStateChange( + keyringState: KeyringControllerState, + ): Promise { + // check if there are any new accounts added + // TODO: change when accountAdded event is added to the keyring controller + + if (keyringState.isUnlocked) { + // TODO: ACCOUNTS_CONTROLLER keyring will return accounts instead of addresses, remove this flatMap after and just get the latest id + const updatedKeyringAddresses = keyringState.keyrings.flatMap( + (keyring) => keyring.accounts, + ); + const previousAccounts = this.listAccounts(); + + // if there are no overlaps between the addresses in the keyring and previous accounts, + // it means the keyring is being reinitialized because the vault is being restored with the same SRP + const overlaps = updatedKeyringAddresses.filter((address) => + previousAccounts.find( + (account) => account.address.toLowerCase() === address.toLowerCase(), + ), + ); + + await this.updateAccounts(); + + if (updatedKeyringAddresses.length > previousAccounts.length) { + this.#handleNewAccountAdded(updatedKeyringAddresses, previousAccounts); + } else if (updatedKeyringAddresses.length > 0 && overlaps.length === 0) { + // if the keyring is being reinitialized, the selected account will be reset to the first account + this.setSelectedAccount(this.listAccounts()[0].id); + } else if ( + updatedKeyringAddresses.length < previousAccounts.length && + overlaps.length > 0 && + !this.getAccount(this.state.internalAccounts.selectedAccount) + ) { + this.#handleSelectedAccountRemoved(); + } + } + } + + /** + * Handles the change in SnapControllerState by updating the metadata of accounts that have a snap enabled. + * + * @param snapState - The new SnapControllerState. + * @returns A Promise that resolves when the update is complete. + */ + async #handleOnSnapStateChange( + snapState: SnapControllerState, + ): Promise { + // only check if snaps changed in status + const { snaps } = snapState; + const accounts = this.listAccounts().filter( + (account) => account.metadata.snap, + ); + + this.update((currentState: AccountsControllerState) => { + accounts.forEach((account) => { + const currentAccount = + currentState.internalAccounts.accounts[account.id]; + if (currentAccount.metadata.snap) { + currentAccount.metadata.snap.enabled = + snaps[currentAccount.metadata.snap.id].enabled && + !snaps[currentAccount.metadata.snap.id].blocked; + } + }); + }); + } + + /** + * Handles the event when a new account is added to the keyring. + * + * @param updatedKeyringAddresses - An array of updated keyring addresses. + * @param previousAccounts - An array of previous internal accounts. + */ + #handleNewAccountAdded( + updatedKeyringAddresses: string[], + previousAccounts: InternalAccount[], + ) { + const [newAddress] = updatedKeyringAddresses.filter( + (address) => + !previousAccounts.find( + (account) => account.address.toLowerCase() === address.toLowerCase(), + ), + ); + + const [newAccount] = this.listAccounts().filter( + (account) => account.address.toLowerCase() === newAddress.toLowerCase(), + ); + + this.setSelectedAccount(newAccount.id); + } + + #registerMessageHandlers() { + this.messagingSystem.registerActionHandler( + `${controllerName}:setSelectedAccount`, + this.setSelectedAccount.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:listAccounts`, + this.listAccounts.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:setAccountName`, + this.setAccountName.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:updateAccounts`, + this.updateAccounts.bind(this), + ); + } +} + +/** + * Returns the name of the keyring type. + * + * @param keyringType - The type of the keyring. + * @returns The name of the keyring type. + */ +export function keyringTypeToName(keyringType: string): string { + switch (keyringType) { + case 'Simple Key Pair': { + return 'Account'; + } + case 'HD Key Tree': { + return 'Account'; + } + case 'Trezor Hardware': { + return 'Trezor'; + } + case 'Ledger Hardware': { + return 'Ledger'; + } + case 'Lattice Hardware': { + return 'Lattice'; + } + case 'QR Hardware Wallet Device': { + return 'QR'; + } + case 'Snap Keyring': { + return 'Snap Account'; + } + case 'Custody': { + return 'Custody'; + } + default: { + throw new Error(`Unknown keyring ${keyringType}`); + } + } +} diff --git a/packages/accounts-controller/src/index.ts b/packages/accounts-controller/src/index.ts new file mode 100644 index 00000000000..6197f0e008e --- /dev/null +++ b/packages/accounts-controller/src/index.ts @@ -0,0 +1 @@ +export * from './AccountsController'; diff --git a/packages/accounts-controller/tsconfig.build.json b/packages/accounts-controller/tsconfig.build.json new file mode 100644 index 00000000000..a206c483c3f --- /dev/null +++ b/packages/accounts-controller/tsconfig.build.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "references": [ + { + "path": "../base-controller/tsconfig.build.json" + }, + { "path": "../keyring-controller/tsconfig.build.json" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/accounts-controller/tsconfig.json b/packages/accounts-controller/tsconfig.json new file mode 100644 index 00000000000..7263c934b6b --- /dev/null +++ b/packages/accounts-controller/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "references": [ + { + "path": "../base-controller" + }, + { + "path": "../keyring-controller" + } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/accounts-controller/typedoc.json b/packages/accounts-controller/typedoc.json new file mode 100644 index 00000000000..c9da015dbf8 --- /dev/null +++ b/packages/accounts-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 825d922c708..77f9861a658 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -1,5 +1,8 @@ { "references": [ + { + "path": "./packages/accounts-controller/tsconfig.build.json" + }, { "path": "./packages/address-book-controller/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index ef3f5344f47..4748f1f4418 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,9 @@ "noEmit": true }, "references": [ + { + "path": "./packages/accounts-controller" + }, { "path": "./packages/address-book-controller" }, diff --git a/yarn.lock b/yarn.lock index 3da6f6a99a5..04fe5d55079 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,12 +29,13 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/code-frame@npm:7.22.5" +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.10, @babel/code-frame@npm:^7.22.5": + version: 7.22.13 + resolution: "@babel/code-frame@npm:7.22.13" dependencies: - "@babel/highlight": ^7.22.5 - checksum: cfe804f518f53faaf9a1d3e0f9f74127ab9a004912c3a16fda07fb6a633393ecb9918a053cb71804204c1b7ec3d49e1699604715e2cfb0c9f7bc4933d324ebb6 + "@babel/highlight": ^7.22.13 + chalk: ^2.4.2 + checksum: 22e342c8077c8b77eeb11f554ecca2ba14153f707b85294fcf6070b6f6150aae88a7b7436dd88d8c9289970585f3fe5b9b941c5aa3aa26a6d5a8ef3f292da058 languageName: node linkType: hard @@ -45,53 +46,51 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.1.0, @babel/core@npm:^7.12.3, @babel/core@npm:^7.7.2, @babel/core@npm:^7.8.0": - version: 7.22.9 - resolution: "@babel/core@npm:7.22.9" +"@babel/core@npm:^7.1.0, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.6, @babel/core@npm:^7.20.12, @babel/core@npm:^7.7.2, @babel/core@npm:^7.8.0": + version: 7.22.11 + resolution: "@babel/core@npm:7.22.11" dependencies: "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.22.5 - "@babel/generator": ^7.22.9 - "@babel/helper-compilation-targets": ^7.22.9 + "@babel/code-frame": ^7.22.10 + "@babel/generator": ^7.22.10 + "@babel/helper-compilation-targets": ^7.22.10 "@babel/helper-module-transforms": ^7.22.9 - "@babel/helpers": ^7.22.6 - "@babel/parser": ^7.22.7 + "@babel/helpers": ^7.22.11 + "@babel/parser": ^7.22.11 "@babel/template": ^7.22.5 - "@babel/traverse": ^7.22.8 - "@babel/types": ^7.22.5 + "@babel/traverse": ^7.22.11 + "@babel/types": ^7.22.11 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 - json5: ^2.2.2 + json5: ^2.2.3 semver: ^6.3.1 - checksum: 7bf069aeceb417902c4efdaefab1f7b94adb7dea694a9aed1bda2edf4135348a080820529b1a300c6f8605740a00ca00c19b2d5e74b5dd489d99d8c11d5e56d1 + checksum: f258b2539ea2e5bfe55a708c2f3e1093a1b4744f12becc35abeb896037b66210de9a8ad6296a706046d5dc3a24e564362b73a9b814e5bfe500c8baab60c22d2e languageName: node linkType: hard -"@babel/generator@npm:^7.22.7, @babel/generator@npm:^7.22.9, @babel/generator@npm:^7.7.2": - version: 7.22.9 - resolution: "@babel/generator@npm:7.22.9" +"@babel/generator@npm:^7.22.10, @babel/generator@npm:^7.7.2": + version: 7.22.10 + resolution: "@babel/generator@npm:7.22.10" dependencies: - "@babel/types": ^7.22.5 + "@babel/types": ^7.22.10 "@jridgewell/gen-mapping": ^0.3.2 "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: 7c9d2c58b8d5ac5e047421a6ab03ec2ff5d9a5ff2c2212130a0055e063ac349e0b19d435537d6886c999771aef394832e4f54cd9fc810100a7f23d982f6af06b + checksum: 59a79730abdff9070692834bd3af179e7a9413fa2ff7f83dff3eb888765aeaeb2bfc7b0238a49613ed56e1af05956eff303cc139f2407eda8df974813e486074 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.22.9": - version: 7.22.9 - resolution: "@babel/helper-compilation-targets@npm:7.22.9" +"@babel/helper-compilation-targets@npm:^7.22.10": + version: 7.22.10 + resolution: "@babel/helper-compilation-targets@npm:7.22.10" dependencies: "@babel/compat-data": ^7.22.9 "@babel/helper-validator-option": ^7.22.5 browserslist: ^4.21.9 lru-cache: ^5.1.1 semver: ^6.3.1 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: ea0006c6a93759025f4a35a25228ae260538c9f15023e8aac2a6d45ca68aef4cf86cfc429b19af9a402cbdd54d5de74ad3fbcf6baa7e48184dc079f1a791e178 + checksum: f6f1896816392bcff671bbe6e277307729aee53befb4a66ea126e2a91eda78d819a70d06fa384c74ef46c1595544b94dca50bef6c78438d9ffd31776dafbd435 languageName: node linkType: hard @@ -191,34 +190,34 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.22.6": - version: 7.22.6 - resolution: "@babel/helpers@npm:7.22.6" +"@babel/helpers@npm:^7.22.11": + version: 7.22.11 + resolution: "@babel/helpers@npm:7.22.11" dependencies: "@babel/template": ^7.22.5 - "@babel/traverse": ^7.22.6 - "@babel/types": ^7.22.5 - checksum: 5c1f33241fe7bf7709868c2105134a0a86dca26a0fbd508af10a89312b1f77ca38ebae43e50be3b208613c5eacca1559618af4ca236f0abc55d294800faeff30 + "@babel/traverse": ^7.22.11 + "@babel/types": ^7.22.11 + checksum: 93186544228b5e371486466ec3b86a77cce91beeff24a5670ca8ec46d50328f7700dab82d532351286e9d68624dc51d6d71589b051dd9535e44be077a43ec013 languageName: node linkType: hard -"@babel/highlight@npm:^7.22.5": - version: 7.22.5 - resolution: "@babel/highlight@npm:7.22.5" +"@babel/highlight@npm:^7.22.13": + version: 7.22.13 + resolution: "@babel/highlight@npm:7.22.13" dependencies: "@babel/helper-validator-identifier": ^7.22.5 - chalk: ^2.0.0 + chalk: ^2.4.2 js-tokens: ^4.0.0 - checksum: f61ae6de6ee0ea8d9b5bcf2a532faec5ab0a1dc0f7c640e5047fc61630a0edb88b18d8c92eb06566d30da7a27db841aca11820ecd3ebe9ce514c9350fbed39c4 + checksum: 7266d2bff8aa8fc78eb65b6e92a8211e12897a731126a282d2f9bb50d8fcaa4c1b02af2284f990ac7e3ab8d892d448a2cab8f5ed0ea8a90bce2c025b11ebe802 languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.5, @babel/parser@npm:^7.22.7": - version: 7.22.7 - resolution: "@babel/parser@npm:7.22.7" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.11, @babel/parser@npm:^7.22.5": + version: 7.22.13 + resolution: "@babel/parser@npm:7.22.13" bin: parser: ./bin/babel-parser.js - checksum: 02209ddbd445831ee8bf966fdf7c29d189ed4b14343a68eb2479d940e7e3846340d7cc6bd654a5f3d87d19dc84f49f50a58cf9363bee249dc5409ff3ba3dab54 + checksum: b774ae635fc0f019cc9e366fe42e5de35b46b52a0e61e6a2f136f8c822bfde16e13505a47a1aedc846312dd8fd4db7054c7d4039dc7641320d9c17f07004f200 languageName: node linkType: hard @@ -376,32 +375,32 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.22.6, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.7.2": - version: 7.22.8 - resolution: "@babel/traverse@npm:7.22.8" +"@babel/traverse@npm:^7.22.11, @babel/traverse@npm:^7.7.2": + version: 7.22.11 + resolution: "@babel/traverse@npm:7.22.11" dependencies: - "@babel/code-frame": ^7.22.5 - "@babel/generator": ^7.22.7 + "@babel/code-frame": ^7.22.10 + "@babel/generator": ^7.22.10 "@babel/helper-environment-visitor": ^7.22.5 "@babel/helper-function-name": ^7.22.5 "@babel/helper-hoist-variables": ^7.22.5 "@babel/helper-split-export-declaration": ^7.22.6 - "@babel/parser": ^7.22.7 - "@babel/types": ^7.22.5 + "@babel/parser": ^7.22.11 + "@babel/types": ^7.22.11 debug: ^4.1.0 globals: ^11.1.0 - checksum: a381369bc3eedfd13ed5fef7b884657f1c29024ea7388198149f0edc34bd69ce3966e9f40188d15f56490a5e12ba250ccc485f2882b53d41b054fccefb233e33 + checksum: 4ad62d548ca8b95dbf45bae16cc167428f174f3c837d55a5878b1f17bdbc8b384d6df741dc7c461b62c04d881cf25644d3ab885909ba46e3ac43224e2b15b504 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": - version: 7.22.5 - resolution: "@babel/types@npm:7.22.5" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.7, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.11, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": + version: 7.22.11 + resolution: "@babel/types@npm:7.22.11" dependencies: "@babel/helper-string-parser": ^7.22.5 "@babel/helper-validator-identifier": ^7.22.5 to-fast-properties: ^2.0.0 - checksum: c13a9c1dc7d2d1a241a2f8363540cb9af1d66e978e8984b400a20c4f38ba38ca29f06e26a0f2d49a70bad9e57615dac09c35accfddf1bb90d23cd3e0a0bab892 + checksum: 431a6446896adb62c876d0fe75263835735d3c974aae05356a87eb55f087c20a777028cf08eadcace7993e058bbafe3b21ce2119363222c6cef9eedd7a204810 languageName: node linkType: hard @@ -421,6 +420,13 @@ __metadata: languageName: node linkType: hard +"@endo/env-options@npm:^0.1.3": + version: 0.1.3 + resolution: "@endo/env-options@npm:0.1.3" + checksum: da8c66865d4d30b0053a00960657dc36f022975a888f0dd6a2f6bb37b9fe731f45a02a2cf263d93b1a40fcb37b25f8ba7076cb8af9e93fd95f496365d9382930 + languageName: node + linkType: hard + "@es-joy/jsdoccomment@npm:~0.36.1": version: 0.36.1 resolution: "@es-joy/jsdoccomment@npm:0.36.1" @@ -1281,6 +1287,36 @@ __metadata: languageName: node linkType: hard +"@metamask/accounts-controller@workspace:packages/accounts-controller": + version: 0.0.0-use.local + resolution: "@metamask/accounts-controller@workspace:packages/accounts-controller" + dependencies: + "@metamask/auto-changelog": ^3.1.0 + "@metamask/base-controller": ^3.2.1 + "@metamask/eth-snap-keyring": ^0.2.2 + "@metamask/keyring-api": ^0.2.5 + "@metamask/keyring-controller": ^7.4.0 + "@metamask/snaps-controllers": ^1.0.1 + "@metamask/snaps-utils": ^1.0.1 + "@metamask/utils": ^6.2.0 + "@types/jest": ^27.4.1 + "@types/readable-stream": ^2.3.0 + deepmerge: ^4.2.2 + eth-rpc-errors: ^4.0.2 + ethereumjs-util: ^7.0.10 + immer: ^9.0.6 + jest: ^27.5.1 + nanoid: ^3.1.31 + ts-jest: ^27.1.4 + typedoc: ^0.22.15 + typedoc-plugin-missing-exports: ^0.22.6 + typescript: ~4.6.3 + uuid: ^8.3.2 + peerDependencies: + "@metamask/keyring-controller": ^7.4.0 + languageName: unknown + linkType: soft + "@metamask/action-utils@npm:^1.0.0": version: 1.0.0 resolution: "@metamask/action-utils@npm:1.0.0" @@ -1327,7 +1363,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/approval-controller@^3.5.1, @metamask/approval-controller@workspace:packages/approval-controller": +"@metamask/approval-controller@^3.0.0, @metamask/approval-controller@^3.5.0, @metamask/approval-controller@^3.5.1, @metamask/approval-controller@workspace:packages/approval-controller": version: 0.0.0-use.local resolution: "@metamask/approval-controller@workspace:packages/approval-controller" dependencies: @@ -1409,7 +1445,7 @@ __metadata: languageName: node linkType: hard -"@metamask/base-controller@^3.2.1, @metamask/base-controller@workspace:packages/base-controller": +"@metamask/base-controller@^3.0.0, @metamask/base-controller@^3.2.0, @metamask/base-controller@^3.2.1, @metamask/base-controller@workspace:packages/base-controller": version: 0.0.0-use.local resolution: "@metamask/base-controller@workspace:packages/base-controller" dependencies: @@ -1428,7 +1464,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/browser-passworder@npm:^4.1.0": +"@metamask/browser-passworder@npm:^4.0.2, @metamask/browser-passworder@npm:^4.1.0": version: 4.1.0 resolution: "@metamask/browser-passworder@npm:4.1.0" checksum: f1edb3b75594b8e8d075b3134c9ce6c73573160eb48184ef722b9d96a5763db1e2e9acb166fc5c66c7c82936e134a02a3fb4c0022ca9a948857a30181cb84d7e @@ -1749,6 +1785,22 @@ __metadata: languageName: node linkType: hard +"@metamask/eth-snap-keyring@npm:^0.2.2": + version: 0.2.2 + resolution: "@metamask/eth-snap-keyring@npm:0.2.2" + dependencies: + "@ethereumjs/tx": ^4.2.0 + "@metamask/eth-sig-util": ^7.0.0 + "@metamask/keyring-api": ^0.2.3 + "@metamask/snaps-controllers": ^0.38.2-flask.1 + "@metamask/utils": ^8.1.0 + "@types/uuid": ^9.0.1 + superstruct: ^1.0.3 + uuid: ^9.0.0 + checksum: 3a3a3f68a04191ae3961eeb4ca1f7062e592a255a6f1db4a0a63b4f07f9b4ec2c62fe1278b0ea365044cda0748c95402c6c5604e149853b730c79943c835bc06 + languageName: node + linkType: hard + "@metamask/gas-fee-controller@workspace:packages/gas-fee-controller": version: 0.0.0-use.local resolution: "@metamask/gas-fee-controller@workspace:packages/gas-fee-controller" @@ -1780,6 +1832,61 @@ __metadata: languageName: unknown linkType: soft +"@metamask/json-rpc-engine@npm:^7.1.1": + version: 7.1.1 + resolution: "@metamask/json-rpc-engine@npm:7.1.1" + dependencies: + "@metamask/rpc-errors": ^6.0.0 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^8.1.0 + checksum: 9dddd9142965ccd86313cda5bf13f15bf99c6c14631f93aab78de353317d548a334b5b125cdc134edd7d54e2f2e4961a0bdcd24fba997b2913083955df8fefa1 + languageName: node + linkType: hard + +"@metamask/key-tree@npm:^7.1.1": + version: 7.1.1 + resolution: "@metamask/key-tree@npm:7.1.1" + dependencies: + "@metamask/scure-bip39": ^2.1.0 + "@metamask/utils": ^6.0.1 + "@noble/ed25519": ^1.6.0 + "@noble/hashes": ^1.0.0 + "@noble/secp256k1": ^1.5.5 + "@scure/base": ^1.0.0 + checksum: ddab7917e1214c51508f821a680f8e8819a8c866fd91244e0debf28464331dfcbf9a357e01716fc593cf00a8c71f95fd45ed4ff686ee6abc4c4061aed629537c + languageName: node + linkType: hard + +"@metamask/key-tree@npm:^9.0.0": + version: 9.0.0 + resolution: "@metamask/key-tree@npm:9.0.0" + dependencies: + "@metamask/scure-bip39": ^2.1.0 + "@metamask/utils": ^6.0.1 + "@noble/ed25519": ^1.6.0 + "@noble/hashes": ^1.0.0 + "@noble/secp256k1": ^1.5.5 + "@scure/base": ^1.0.0 + checksum: 5c81f07351ca59b37570d52edcc80d60424630b2a8403ed7149c3343c264878ac5d3fc0584a61635ea7ddda4a789295ded1247846606dc529d8e2fd42f6fc61a + languageName: node + linkType: hard + +"@metamask/keyring-api@npm:^0.2.3, @metamask/keyring-api@npm:^0.2.5": + version: 0.2.5 + resolution: "@metamask/keyring-api@npm:0.2.5" + dependencies: + "@metamask/providers": ^13.0.0 + "@metamask/rpc-methods": ^0.38.1-flask.1 + "@metamask/snaps-controllers": ^0.38.2-flask.1 + "@metamask/snaps-utils": ^0.38.2-flask.1 + "@metamask/utils": ^8.1.0 + "@types/uuid": ^9.0.1 + superstruct: ^1.0.3 + uuid: ^9.0.0 + checksum: ca567388f5963f7c6adaf9d7bfdfa783f5869ab85d906181cc32d2c3c1328af78c15eea661522d1c5dfb7b23b7dd3c6a9917b4c9eae794d187e03483ba42cd3e + languageName: node + linkType: hard + "@metamask/keyring-controller@^7.4.0, @metamask/keyring-controller@workspace:packages/keyring-controller": version: 0.0.0-use.local resolution: "@metamask/keyring-controller@workspace:packages/keyring-controller" @@ -1934,6 +2041,17 @@ __metadata: languageName: unknown linkType: soft +"@metamask/object-multiplex@npm:^1.1.0, @metamask/object-multiplex@npm:^1.2.0": + version: 1.2.0 + resolution: "@metamask/object-multiplex@npm:1.2.0" + dependencies: + end-of-stream: ^1.4.4 + once: ^1.4.0 + readable-stream: ^2.3.3 + checksum: 7c622639cc164c3b780294d790311e4bcb327faf14626717728022e95da5834f32fe4e242d8f4e7d9b8c2b83f0c76450922786b2f6ef50e777bfe119b78bdab7 + languageName: node + linkType: hard + "@metamask/obs-store@npm:^7.0.0": version: 7.0.0 resolution: "@metamask/obs-store@npm:7.0.0" @@ -1954,7 +2072,7 @@ __metadata: languageName: node linkType: hard -"@metamask/permission-controller@workspace:packages/permission-controller": +"@metamask/permission-controller@^4.0.0, @metamask/permission-controller@^4.1.0, @metamask/permission-controller@workspace:packages/permission-controller": version: 0.0.0-use.local resolution: "@metamask/permission-controller@workspace:packages/permission-controller" dependencies: @@ -2003,6 +2121,16 @@ __metadata: languageName: unknown linkType: soft +"@metamask/post-message-stream@npm:^6.1.1, @metamask/post-message-stream@npm:^6.1.2": + version: 6.1.2 + resolution: "@metamask/post-message-stream@npm:6.1.2" + dependencies: + "@metamask/utils": ^5.0.0 + readable-stream: 2.3.3 + checksum: 3591ec9b7fd602806b07cbc0fed5075fb7a347c279c43ef1f25fbdd8634dfcad9ce192ae59457fb76554ef0bc15cbf25cfaa5875aee2d72668a273b7a6852c32 + languageName: node + linkType: hard + "@metamask/preferences-controller@^4.4.0, @metamask/preferences-controller@workspace:packages/preferences-controller": version: 0.0.0-use.local resolution: "@metamask/preferences-controller@workspace:packages/preferences-controller" @@ -2020,6 +2148,64 @@ __metadata: languageName: unknown linkType: soft +"@metamask/providers@npm:^10.2.0, @metamask/providers@npm:^10.2.1": + version: 10.2.1 + resolution: "@metamask/providers@npm:10.2.1" + dependencies: + "@metamask/object-multiplex": ^1.1.0 + "@metamask/safe-event-emitter": ^2.0.0 + "@types/chrome": ^0.0.136 + detect-browser: ^5.2.0 + eth-rpc-errors: ^4.0.2 + extension-port-stream: ^2.0.1 + fast-deep-equal: ^2.0.1 + is-stream: ^2.0.0 + json-rpc-engine: ^6.1.0 + json-rpc-middleware-stream: ^4.2.1 + pump: ^3.0.0 + webextension-polyfill-ts: ^0.25.0 + checksum: e88b2db8c4673cc6a7e47d9f0531df3fac73f05f8e9ff6d02c3420dfb3c7a82335d9c44876f2d472c44eac36d66491d2022be4f39600bee561d5de8ad59c5b07 + languageName: node + linkType: hard + +"@metamask/providers@npm:^11.1.1": + version: 11.1.1 + resolution: "@metamask/providers@npm:11.1.1" + dependencies: + "@metamask/object-multiplex": ^1.1.0 + "@metamask/safe-event-emitter": ^3.0.0 + detect-browser: ^5.2.0 + eth-rpc-errors: ^4.0.2 + extension-port-stream: ^2.0.1 + fast-deep-equal: ^3.1.3 + is-stream: ^2.0.0 + json-rpc-engine: ^6.1.0 + json-rpc-middleware-stream: ^4.2.1 + pump: ^3.0.0 + webextension-polyfill: ^0.10.0 + checksum: ca0339e5219ef43bccdf1debbf610f5f06c95d879c7bba124463c4c6e6dd34fae30c0cdf673985d85d9326a87e94a6ee7ad5d29ef154dca85a0c672fdab152d1 + languageName: node + linkType: hard + +"@metamask/providers@npm:^13.0.0": + version: 13.0.0 + resolution: "@metamask/providers@npm:13.0.0" + dependencies: + "@metamask/json-rpc-engine": ^7.1.1 + "@metamask/object-multiplex": ^1.1.0 + "@metamask/rpc-errors": ^6.0.0 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^8.1.0 + detect-browser: ^5.2.0 + extension-port-stream: ^2.1.1 + fast-deep-equal: ^3.1.3 + is-stream: ^2.0.0 + json-rpc-middleware-stream: ^4.2.1 + webextension-polyfill: ^0.10.0 + checksum: b41748cf179794bf7a68d3028e84234e876498611221ba40846ed0859b4470a806c4cf99ff9fcdc60fde1af2f0d999281e6edbdd42113223d8f7018a009ff0ab + languageName: node + linkType: hard + "@metamask/rate-limit-controller@workspace:packages/rate-limit-controller": version: 0.0.0-use.local resolution: "@metamask/rate-limit-controller@workspace:packages/rate-limit-controller" @@ -2048,6 +2234,52 @@ __metadata: languageName: node linkType: hard +"@metamask/rpc-errors@npm:^6.0.0": + version: 6.0.0 + resolution: "@metamask/rpc-errors@npm:6.0.0" + dependencies: + "@metamask/utils": ^8.0.0 + fast-safe-stringify: ^2.0.6 + checksum: 7e1ee1a98972266af4a34f0bbc842cdc11dc565056f0b8fbc93aa95663a7027eab8ff1fecbe3e09c38a1dc199f8219a6c69b2237015b2fdb8de0e5b35027c3f8 + languageName: node + linkType: hard + +"@metamask/rpc-methods@npm:^0.38.1-flask.1": + version: 0.38.1-flask.1 + resolution: "@metamask/rpc-methods@npm:0.38.1-flask.1" + dependencies: + "@metamask/key-tree": ^9.0.0 + "@metamask/permission-controller": ^4.1.0 + "@metamask/snaps-ui": ^0.37.4-flask.1 + "@metamask/snaps-utils": ^0.38.2-flask.1 + "@metamask/types": ^1.1.0 + "@metamask/utils": ^6.0.1 + "@noble/hashes": ^1.3.1 + eth-rpc-errors: ^4.0.3 + superstruct: ^1.0.3 + checksum: 49700a41a1340d36d484086a4f6ca0a3fea27f5f8119c9a9af9c2cd0aeecf7f603f6c89a2f122232c66a238e281023df82d4ad38c799da5a137ccfcf0bca6857 + languageName: node + linkType: hard + +"@metamask/rpc-methods@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/rpc-methods@npm:1.0.1" + dependencies: + "@metamask/browser-passworder": ^4.0.2 + "@metamask/key-tree": ^7.1.1 + "@metamask/permission-controller": ^4.0.0 + "@metamask/snaps-ui": ^1.0.1 + "@metamask/snaps-utils": ^1.0.1 + "@metamask/types": ^1.1.0 + "@metamask/utils": ^6.0.1 + "@noble/hashes": ^1.1.3 + eth-rpc-errors: ^4.0.2 + nanoid: ^3.1.31 + superstruct: ^1.0.3 + checksum: 9d5c91f7af7c66b5f7afe23ce08a1d959bec1c7423063e955ae30ca41ed048e793a4e075b0fd23b9a8e54ff3a3fee018994e8d2fa494adebcc236f0bdd484340 + languageName: node + linkType: hard + "@metamask/safe-event-emitter@npm:^2.0.0": version: 2.0.0 resolution: "@metamask/safe-event-emitter@npm:2.0.0" @@ -2123,6 +2355,192 @@ __metadata: languageName: unknown linkType: soft +"@metamask/snaps-controllers@npm:^0.38.2-flask.1": + version: 0.38.2-flask.1 + resolution: "@metamask/snaps-controllers@npm:0.38.2-flask.1" + dependencies: + "@metamask/approval-controller": ^3.5.0 + "@metamask/base-controller": ^3.2.0 + "@metamask/object-multiplex": ^1.2.0 + "@metamask/permission-controller": ^4.1.0 + "@metamask/post-message-stream": ^6.1.2 + "@metamask/rpc-methods": ^0.38.1-flask.1 + "@metamask/snaps-execution-environments": ^0.38.2-flask.1 + "@metamask/snaps-registry": ^1.2.1 + "@metamask/snaps-utils": ^0.38.2-flask.1 + "@metamask/utils": ^6.0.1 + "@xstate/fsm": ^2.0.0 + concat-stream: ^2.0.0 + eth-rpc-errors: ^4.0.3 + gunzip-maybe: ^1.4.2 + immer: ^9.0.6 + json-rpc-engine: ^6.1.0 + json-rpc-middleware-stream: ^4.2.0 + nanoid: ^3.1.31 + pump: ^3.0.0 + readable-web-to-node-stream: ^3.0.2 + tar-stream: ^2.2.0 + checksum: 738044e70b079fd62195219aa68260b4afae4855826b8d7b94f53edad69d8befaf77c67c2a9dbf2363457223b0560652578eeea438150d37a56a4adcd89e6e43 + languageName: node + linkType: hard + +"@metamask/snaps-controllers@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-controllers@npm:1.0.1" + dependencies: + "@metamask/approval-controller": ^3.0.0 + "@metamask/base-controller": ^3.0.0 + "@metamask/object-multiplex": ^1.2.0 + "@metamask/permission-controller": ^4.0.0 + "@metamask/post-message-stream": ^6.1.2 + "@metamask/rpc-methods": ^1.0.1 + "@metamask/snaps-execution-environments": ^1.0.1 + "@metamask/snaps-registry": ^1.2.1 + "@metamask/snaps-utils": ^1.0.1 + "@metamask/utils": ^6.0.1 + "@xstate/fsm": ^2.0.0 + concat-stream: ^2.0.0 + cron-parser: ^4.5.0 + eth-rpc-errors: ^4.0.2 + gunzip-maybe: ^1.4.2 + immer: ^9.0.6 + json-rpc-engine: ^6.1.0 + json-rpc-middleware-stream: ^4.2.0 + nanoid: ^3.1.31 + pump: ^3.0.0 + readable-web-to-node-stream: ^3.0.2 + tar-stream: ^2.2.0 + checksum: 31450c79705786431e76ad20e3ee35294f997a049337729301aac50f7b27aa1b03e617549b62d3eff067b93e0ab51ade883a4f6e9d67a56ff0bba1f2f0b0fbc4 + languageName: node + linkType: hard + +"@metamask/snaps-execution-environments@npm:^0.38.2-flask.1": + version: 0.38.2-flask.1 + resolution: "@metamask/snaps-execution-environments@npm:0.38.2-flask.1" + dependencies: + "@metamask/object-multiplex": ^1.2.0 + "@metamask/post-message-stream": ^6.1.2 + "@metamask/providers": ^11.1.1 + "@metamask/rpc-methods": ^0.38.1-flask.1 + "@metamask/snaps-utils": ^0.38.2-flask.1 + "@metamask/utils": ^6.0.1 + eth-rpc-errors: ^4.0.3 + json-rpc-engine: ^6.1.0 + nanoid: ^3.1.31 + pump: ^3.0.0 + superstruct: ^1.0.3 + checksum: 68c1adcb8fe227c989c02ea5f7820f0d58b0e9442b21d0043e2f06a7ea3a6607a5e0bdde82aceecc12e265b27bd1e5a719fb4ec901f19adea3a37d5412ca8415 + languageName: node + linkType: hard + +"@metamask/snaps-execution-environments@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-execution-environments@npm:1.0.1" + dependencies: + "@metamask/object-multiplex": ^1.2.0 + "@metamask/post-message-stream": ^6.1.1 + "@metamask/providers": ^10.2.0 + "@metamask/rpc-methods": ^1.0.1 + "@metamask/snaps-utils": ^1.0.1 + "@metamask/utils": ^6.0.1 + eth-rpc-errors: ^4.0.3 + json-rpc-engine: ^6.1.0 + pump: ^3.0.0 + ses: ^0.18.1 + stream-browserify: ^3.0.0 + superstruct: ^1.0.3 + checksum: 46645edaa0ca6d6188c6c795d1229fb50357d5d6b25d73155de6867d06b9b74b02be76bcd3a4a523b4c0c16a9210c9636fa0204bba347f53c88e4d09ae1f793f + languageName: node + linkType: hard + +"@metamask/snaps-registry@npm:^1.2.1": + version: 1.2.2 + resolution: "@metamask/snaps-registry@npm:1.2.2" + dependencies: + "@metamask/utils": ^7.1.0 + "@noble/secp256k1": ^1.7.1 + superstruct: ^1.0.3 + checksum: 02289b349390466158c4842c6398b56c0a6352258657eb186331636774a894782607aa0c4e4c772689c89248856c325d914a0d8c8f4b739e9324d60b0fb92da1 + languageName: node + linkType: hard + +"@metamask/snaps-ui@npm:^0.37.4-flask.1": + version: 0.37.4-flask.1 + resolution: "@metamask/snaps-ui@npm:0.37.4-flask.1" + dependencies: + "@metamask/utils": ^6.0.1 + superstruct: ^1.0.3 + checksum: d4e0a3c5f82a6edbf7bc82280fab364b93787e7d9995854208dd4200e188f37691216928967a5b75e6be5ebfeb360e638af39ac0043f1bdddb0c2ff7a5cdbce5 + languageName: node + linkType: hard + +"@metamask/snaps-ui@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-ui@npm:1.0.1" + dependencies: + "@metamask/utils": ^6.0.1 + superstruct: ^1.0.3 + checksum: 71719b47a79d2ff23f0e9f1d2a1a8afd42f4f94891437056241a49090d245ccd086eac5d8fdf7719aaaa3f6bbc16b30f5566ad7bcecaa7b70e00ea638387aaa0 + languageName: node + linkType: hard + +"@metamask/snaps-utils@npm:^0.38.2-flask.1": + version: 0.38.2-flask.1 + resolution: "@metamask/snaps-utils@npm:0.38.2-flask.1" + dependencies: + "@babel/core": ^7.20.12 + "@babel/types": ^7.18.7 + "@metamask/base-controller": ^3.2.0 + "@metamask/key-tree": ^9.0.0 + "@metamask/permission-controller": ^4.1.0 + "@metamask/snaps-registry": ^1.2.1 + "@metamask/snaps-ui": ^0.37.4-flask.1 + "@metamask/utils": ^6.0.1 + "@noble/hashes": ^1.3.1 + "@scure/base": ^1.1.1 + chalk: ^4.1.2 + cron-parser: ^4.5.0 + eth-rpc-errors: ^4.0.3 + fast-deep-equal: ^3.1.3 + fast-json-stable-stringify: ^2.1.0 + is-svg: ^4.4.0 + rfdc: ^1.3.0 + semver: ^7.5.4 + ses: ^0.18.7 + superstruct: ^1.0.3 + validate-npm-package-name: ^5.0.0 + checksum: cb95a67a1267299aacb5b85b25a35dbfd14f68060198974cc1a26b9a2e7eff5acd13700b9100e3546dfc7e78ed937b06aecb5c4884c32ff77b2d28c77e7f97b6 + languageName: node + linkType: hard + +"@metamask/snaps-utils@npm:^1.0.1": + version: 1.0.1 + resolution: "@metamask/snaps-utils@npm:1.0.1" + dependencies: + "@babel/core": ^7.18.6 + "@babel/types": ^7.18.7 + "@metamask/base-controller": ^3.0.0 + "@metamask/key-tree": ^7.1.1 + "@metamask/permission-controller": ^4.0.0 + "@metamask/providers": ^10.2.1 + "@metamask/snaps-registry": ^1.2.1 + "@metamask/snaps-ui": ^1.0.1 + "@metamask/utils": ^6.0.1 + "@noble/hashes": ^1.1.3 + "@scure/base": ^1.1.1 + cron-parser: ^4.5.0 + eth-rpc-errors: ^4.0.3 + fast-deep-equal: ^3.1.3 + fast-json-stable-stringify: ^2.1.0 + rfdc: ^1.3.0 + semver: ^7.5.4 + ses: ^0.18.7 + superstruct: ^1.0.3 + validate-npm-package-name: ^5.0.0 + checksum: 4abb2aabd5b6af314338d812f6029cbdecb0953bcaaf8df16e4b3ad65106d934d0c076c4587df032a1adb08409c8ea246f309aa8c2f94e4453e96f8afbb143f3 + languageName: node + linkType: hard + "@metamask/swappable-obj-proxy@npm:^2.1.0": version: 2.1.0 resolution: "@metamask/swappable-obj-proxy@npm:2.1.0" @@ -2169,6 +2587,13 @@ __metadata: languageName: unknown linkType: soft +"@metamask/types@npm:^1.1.0": + version: 1.1.0 + resolution: "@metamask/types@npm:1.1.0" + checksum: 500e8c076a2b0a6d688c8c465101256f090547d99c9a5585efe3d1db7a494b6b2712b127043fb316912caffc4fff78976b9a7780780abb68305e8a3bf812689c + languageName: node + linkType: hard + "@metamask/utils@npm:^4.0.0": version: 4.0.0 resolution: "@metamask/utils@npm:4.0.0" @@ -2194,7 +2619,7 @@ __metadata: languageName: node linkType: hard -"@metamask/utils@npm:^6.2.0": +"@metamask/utils@npm:^6.0.1, @metamask/utils@npm:^6.2.0": version: 6.2.0 resolution: "@metamask/utils@npm:6.2.0" dependencies: @@ -2208,6 +2633,20 @@ __metadata: languageName: node linkType: hard +"@metamask/utils@npm:^7.1.0": + version: 7.1.0 + resolution: "@metamask/utils@npm:7.1.0" + dependencies: + "@ethereumjs/tx": ^4.1.2 + "@noble/hashes": ^1.3.1 + "@types/debug": ^4.1.7 + debug: ^4.3.4 + semver: ^7.5.4 + superstruct: ^1.0.3 + checksum: 165ed378f4ac5ca42c241d32154e15b609f9e772a9dc069b870613c005dc0e7e4fa92204c30e98ec2317f1e38c77747057671a26fd0a5ba36a288e3c9ef03790 + languageName: node + linkType: hard + "@metamask/utils@npm:^8.0.0, @metamask/utils@npm:^8.1.0": version: 8.1.0 resolution: "@metamask/utils@npm:8.1.0" @@ -2246,6 +2685,13 @@ __metadata: languageName: node linkType: hard +"@noble/ed25519@npm:^1.6.0": + version: 1.7.3 + resolution: "@noble/ed25519@npm:1.7.3" + checksum: 45169927d51de513e47bbeebff3a603433c4ac7579e1b8c5034c380a0afedbe85e6959be3d69584a7a5ed6828d638f8f28879003b9bb2fb5f22d8aa2d88fd5fe + languageName: node + linkType: hard + "@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0": version: 1.2.0 resolution: "@noble/hashes@npm:1.2.0" @@ -2253,13 +2699,20 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:1.3.1, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1": +"@noble/hashes@npm:1.3.1": version: 1.3.1 resolution: "@noble/hashes@npm:1.3.1" checksum: 7fdefc0f7a0c1ec27acc6ff88841793e3f93ec4ce6b8a6a12bfc0dd70ae6b7c4c82fe305fdfeda1735d5ad4a9eebe761e6693b3d355689c559e91242f4bc95b1 languageName: node linkType: hard +"@noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.1.3, @noble/hashes@npm:^1.3.1, @noble/hashes@npm:~1.3.0, @noble/hashes@npm:~1.3.1": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 + languageName: node + linkType: hard + "@noble/hashes@npm:~1.1.1": version: 1.1.5 resolution: "@noble/hashes@npm:1.1.5" @@ -2267,7 +2720,7 @@ __metadata: languageName: node linkType: hard -"@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:~1.7.0": +"@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:^1.5.5, @noble/secp256k1@npm:^1.7.1, @noble/secp256k1@npm:~1.7.0": version: 1.7.1 resolution: "@noble/secp256k1@npm:1.7.1" checksum: d2301f1f7690368d8409a3152450458f27e54df47e3f917292de3de82c298770890c2de7c967d237eff9c95b70af485389a9695f73eb05a43e2bd562d18b18cb @@ -2360,10 +2813,10 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:~1.1.0": - version: 1.1.1 - resolution: "@scure/base@npm:1.1.1" - checksum: b4fc810b492693e7e8d0107313ac74c3646970c198bbe26d7332820886fa4f09441991023ec9aa3a2a51246b74409ab5ebae2e8ef148bbc253da79ac49130309 +"@scure/base@npm:^1.0.0, @scure/base@npm:^1.1.1, @scure/base@npm:~1.1.0": + version: 1.1.2 + resolution: "@scure/base@npm:1.1.2" + checksum: f666b09dbd62ecb5fe6d0e7a629c8a86a972a47dc4f4555ebbbd7b09782b10a5f894fed9c3b8c74fd683b1588c064df079a44e9f695c075ccd98c30a8d3e91f7 languageName: node linkType: hard @@ -2562,6 +3015,16 @@ __metadata: languageName: node linkType: hard +"@types/chrome@npm:^0.0.136": + version: 0.0.136 + resolution: "@types/chrome@npm:0.0.136" + dependencies: + "@types/filesystem": "*" + "@types/har-format": "*" + checksum: af96fdc79fb019d827fdb6269f831921f8f36215ee05a2624436dd2ad4d84d7be12333cc6f54912fb8bae0ca49cbfde5a78de94723bfbd20d309d2e71e274a1b + languageName: node + linkType: hard + "@types/debug@npm:^4.1.7": version: 4.1.8 resolution: "@types/debug@npm:4.1.8" @@ -2578,6 +3041,22 @@ __metadata: languageName: node linkType: hard +"@types/filesystem@npm:*": + version: 0.0.32 + resolution: "@types/filesystem@npm:0.0.32" + dependencies: + "@types/filewriter": "*" + checksum: 4b9079d200a3b241722b90e1c5806c4b32c4dac87d42a1c7ef76a2c0dafdbe7d5f1a379b873ad5de73622b44de6599e1522908f67b938d54e785bd1c36e302a0 + languageName: node + linkType: hard + +"@types/filewriter@npm:*": + version: 0.0.29 + resolution: "@types/filewriter@npm:0.0.29" + checksum: 0c58aa875c2c245be7dbc42b20212f3203e13d11ec013a4a5cd0febf0e8b87214be5882c05ff9d7bdf0398f145a4fdbc24b7e6cf7b094e134a3b4c7a0598502f + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.2": version: 4.1.6 resolution: "@types/graceful-fs@npm:4.1.6" @@ -2587,6 +3066,13 @@ __metadata: languageName: node linkType: hard +"@types/har-format@npm:*": + version: 1.2.12 + resolution: "@types/har-format@npm:1.2.12" + checksum: f52c1617859983437f4c06d02fda052500c0e5b2e8ffd5deb0172f1adc2222d4569db2df3ca2b99007c2572dbae01ba7a042b51e7b6ec6a4a44d53eea8230895 + languageName: node + linkType: hard + "@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": version: 2.0.4 resolution: "@types/istanbul-lib-coverage@npm:2.0.4" @@ -2706,6 +3192,16 @@ __metadata: languageName: node linkType: hard +"@types/readable-stream@npm:^2.3.0": + version: 2.3.15 + resolution: "@types/readable-stream@npm:2.3.15" + dependencies: + "@types/node": "*" + safe-buffer: ~5.1.1 + checksum: ec36f525cad09b6c65a1dafcb5ad99b9e2ed824ec49b7aa23180ac427e5d35b8a0706193ecd79ab4253a283ad485ba03d5917a98daaaa144f0ea34f4823e9d82 + languageName: node + linkType: hard + "@types/secp256k1@npm:^4.0.1": version: 4.0.3 resolution: "@types/secp256k1@npm:4.0.3" @@ -2752,6 +3248,13 @@ __metadata: languageName: node linkType: hard +"@types/uuid@npm:^9.0.1": + version: 9.0.2 + resolution: "@types/uuid@npm:9.0.2" + checksum: 1754bcf3444e1e3aeadd6e774fc328eb53bc956665e2e8fb6ec127aa8e1f43d9a224c3d22a9a6233dca8dd81a12dc7fed4d84b8876dd5ec82d40f574f7ff8b68 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -2898,6 +3401,13 @@ __metadata: languageName: node linkType: hard +"@xstate/fsm@npm:^2.0.0": + version: 2.1.0 + resolution: "@xstate/fsm@npm:2.1.0" + checksum: 593aa73866d89472f15727872f41cf305388f9b1a34e26e22bff0a062e4dedc8d8615a2c805d76114dc418a635ea0ccfc1d17dd44774e6b7ec1c6fb4a6d1723f + languageName: node + linkType: hard + "abab@npm:^2.0.3, abab@npm:^2.0.5": version: 2.0.6 resolution: "abab@npm:2.0.6" @@ -3396,6 +3906,17 @@ __metadata: languageName: node linkType: hard +"bl@npm:^4.0.3": + version: 4.1.0 + resolution: "bl@npm:4.1.0" + dependencies: + buffer: ^5.5.0 + inherits: ^2.0.4 + readable-stream: ^3.4.0 + checksum: 9e8521fa7e83aa9427c6f8ccdcba6e8167ef30cc9a22df26effcc5ab682ef91d2cbc23a239f945d099289e4bbcfae7a192e9c28c84c6202e710a0dfec3722662 + languageName: node + linkType: hard + "blakejs@npm:^1.1.0": version: 1.2.1 resolution: "blakejs@npm:1.2.1" @@ -3489,6 +4010,15 @@ __metadata: languageName: node linkType: hard +"browserify-zlib@npm:^0.1.4": + version: 0.1.4 + resolution: "browserify-zlib@npm:0.1.4" + dependencies: + pako: ~0.2.0 + checksum: abee4cb4349e8a21391fd874564f41b113fe691372913980e6fa06a777e4ea2aad4e942af14ab99bce190d5ac8f5328201432f4ef0eae48c6d02208bc212976f + languageName: node + linkType: hard + "browserslist@npm:^4.21.9": version: 4.21.9 resolution: "browserslist@npm:4.21.9" @@ -3555,7 +4085,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.1.0": +"buffer@npm:^5.1.0, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -3565,7 +4095,7 @@ __metadata: languageName: node linkType: hard -"builtins@npm:^5.0.1": +"builtins@npm:^5.0.0, builtins@npm:^5.0.1": version: 5.0.1 resolution: "builtins@npm:5.0.1" dependencies: @@ -3648,7 +4178,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^2.0.0": +"chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" dependencies: @@ -3659,7 +4189,7 @@ __metadata: languageName: node linkType: hard -"chalk@npm:^4.0.0": +"chalk@npm:^4.0.0, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" dependencies: @@ -3828,6 +4358,18 @@ __metadata: languageName: node linkType: hard +"concat-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "concat-stream@npm:2.0.0" + dependencies: + buffer-from: ^1.0.0 + inherits: ^2.0.3 + readable-stream: ^3.0.2 + typedarray: ^0.0.6 + checksum: d7f75d48f0ecd356c1545d87e22f57b488172811b1181d96021c7c4b14ab8855f5313280263dca44bb06e5222f274d047da3e290a38841ef87b59719bde967c7 + languageName: node + linkType: hard + "console-control-strings@npm:^1.1.0": version: 1.1.0 resolution: "console-control-strings@npm:1.1.0" @@ -3908,6 +4450,15 @@ __metadata: languageName: node linkType: hard +"cron-parser@npm:^4.5.0": + version: 4.9.0 + resolution: "cron-parser@npm:4.9.0" + dependencies: + luxon: ^3.2.1 + checksum: 3cf248fc5cae6c19ec7124962b1cd84b76f02b9bc4f58976b3bd07624db3ef10aaf1548efcc2d2dcdab0dad4f12029d640a55ecce05ea5e1596af9db585502cf + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3": version: 7.0.3 resolution: "cross-spawn@npm:7.0.3" @@ -4069,6 +4620,13 @@ __metadata: languageName: node linkType: hard +"detect-browser@npm:^5.2.0": + version: 5.3.0 + resolution: "detect-browser@npm:5.3.0" + checksum: dd6e08d55da1d9e0f22510ac79872078ae03d9dfa13c5e66c96baedc1c86567345a88f96949161f6be8f3e0fafa93bf179bdb1cd311b14f5f163112fcc70ab49 + languageName: node + linkType: hard + "detect-indent@npm:^7.0.1": version: 7.0.1 resolution: "detect-indent@npm:7.0.1" @@ -4154,6 +4712,18 @@ __metadata: languageName: node linkType: hard +"duplexify@npm:^3.5.0, duplexify@npm:^3.6.0": + version: 3.7.1 + resolution: "duplexify@npm:3.7.1" + dependencies: + end-of-stream: ^1.0.0 + inherits: ^2.0.1 + readable-stream: ^2.0.0 + stream-shift: ^1.0.0 + checksum: 3c2ed2223d956a5da713dae12ba8295acb61d9acd966ccbba938090d04f4574ca4dca75cca089b5077c2d7e66101f32e6ea9b36a78ca213eff574e7a8b8accf2 + languageName: node + linkType: hard + "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -4213,6 +4783,15 @@ __metadata: languageName: node linkType: hard +"end-of-stream@npm:^1.0.0, end-of-stream@npm:^1.1.0, end-of-stream@npm:^1.4.1, end-of-stream@npm:^1.4.4": + version: 1.4.4 + resolution: "end-of-stream@npm:1.4.4" + dependencies: + once: ^1.4.0 + checksum: 530a5a5a1e517e962854a31693dbb5c0b2fc40b46dad2a56a2deec656ca040631124f4795823acc68238147805f8b021abbe221f4afed5ef3c8e8efc2024908b + languageName: node + linkType: hard + "env-paths@npm:^2.2.0": version: 2.2.1 resolution: "env-paths@npm:2.2.1" @@ -5075,6 +5654,22 @@ __metadata: languageName: node linkType: hard +"extension-port-stream@npm:^2.0.1, extension-port-stream@npm:^2.1.1": + version: 2.1.1 + resolution: "extension-port-stream@npm:2.1.1" + dependencies: + webextension-polyfill: ">=0.10.0 <1.0" + checksum: aee8bbeb2ed6f69a62f58a89580e0e9002dadb11062edbaedb7bb04cfc5a5e0b0d3980bfeaa1c3ee7e08dec7e5fac26e25497fc2f82000db7653442bd5eca157 + languageName: node + linkType: hard + +"fast-deep-equal@npm:^2.0.1": + version: 2.0.1 + resolution: "fast-deep-equal@npm:2.0.1" + checksum: b701835a87985e0ec4925bdf1f0c1e7eb56309b5d12d534d5b4b69d95a54d65bb16861c081781ead55f73f12d6c60ba668713391ee7fbf6b0567026f579b7b0b + languageName: node + linkType: hard + "fast-deep-equal@npm:^3.1.1, fast-deep-equal@npm:^3.1.3": version: 3.1.3 resolution: "fast-deep-equal@npm:3.1.3" @@ -5109,7 +5704,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0, fast-json-stable-stringify@npm:^2.1.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb @@ -5130,6 +5725,17 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:^4.1.3": + version: 4.2.7 + resolution: "fast-xml-parser@npm:4.2.7" + dependencies: + strnum: ^1.0.5 + bin: + fxparser: src/cli/cli.js + checksum: d8b0c9e04756f6c43fa0399428f30149acadae21350e42e26e8fe98e24e6afa6b9b00aa554453795036b00e9fee974a1b556fe2ba18be391d51a9bf1ab790e7c + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.15.0 resolution: "fastq@npm:1.15.0" @@ -5233,6 +5839,13 @@ __metadata: languageName: node linkType: hard +"fs-constants@npm:^1.0.0": + version: 1.0.0 + resolution: "fs-constants@npm:1.0.0" + checksum: 18f5b718371816155849475ac36c7d0b24d39a11d91348cfcb308b4494824413e03572c403c86d3a260e049465518c4f0d5bd00f0371cdfcad6d4f30a85b350d + languageName: node + linkType: hard + "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -5518,6 +6131,22 @@ __metadata: languageName: node linkType: hard +"gunzip-maybe@npm:^1.4.2": + version: 1.4.2 + resolution: "gunzip-maybe@npm:1.4.2" + dependencies: + browserify-zlib: ^0.1.4 + is-deflate: ^1.0.0 + is-gzip: ^1.0.0 + peek-stream: ^1.1.0 + pumpify: ^1.3.3 + through2: ^2.0.3 + bin: + gunzip-maybe: bin.js + checksum: bc4d4977c24a2860238df271de75d53dd72a359d19f1248d1c613807dc221d3b8ae09624e3085c8106663e3e1b59db62a85b261d1138c2cc24efad9df577d4e1 + languageName: node + linkType: hard + "has-bigints@npm:^1.0.1, has-bigints@npm:^1.0.2": version: 1.0.2 resolution: "has-bigints@npm:1.0.2" @@ -5803,7 +6432,7 @@ __metadata: languageName: node linkType: hard -"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3": +"inherits@npm:2, inherits@npm:^2.0.1, inherits@npm:^2.0.3, inherits@npm:^2.0.4, inherits@npm:~2.0.3, inherits@npm:~2.0.4": version: 2.0.4 resolution: "inherits@npm:2.0.4" checksum: 4a48a733847879d6cf6691860a6b1e3f0f4754176e4d71494c41f3475553768b10f84b5ce1d40fbd0e34e6bfbb864ee35858ad4dd2cf31e02fc4a154b724d7f1 @@ -5907,6 +6536,13 @@ __metadata: languageName: node linkType: hard +"is-deflate@npm:^1.0.0": + version: 1.0.0 + resolution: "is-deflate@npm:1.0.0" + checksum: c2f9f2d3db79ac50c5586697d1e69a55282a2b0cc5e437b3c470dd47f24e40b6216dcd7e024511e21381607bf57afa019343e3bd0e08a119032818b596004262 + languageName: node + linkType: hard + "is-docker@npm:^2.0.0": version: 2.2.1 resolution: "is-docker@npm:2.2.1" @@ -5971,6 +6607,13 @@ __metadata: languageName: node linkType: hard +"is-gzip@npm:^1.0.0": + version: 1.0.0 + resolution: "is-gzip@npm:1.0.0" + checksum: 0d28931c1f445fa29c900cf9f48e06e9d1d477a3bf7bd7332e7ce68f1333ccd8cb381de2f0f62a9a262d9c0912608a9a71b4a40e788e201b3dbd67072bb20d86 + languageName: node + linkType: hard + "is-hex-prefixed@npm:1.0.0": version: 1.0.0 resolution: "is-hex-prefixed@npm:1.0.0" @@ -6092,6 +6735,15 @@ __metadata: languageName: node linkType: hard +"is-svg@npm:^4.4.0": + version: 4.4.0 + resolution: "is-svg@npm:4.4.0" + dependencies: + fast-xml-parser: ^4.1.3 + checksum: cd5a0ba1af653e4897721913b0b80de968fa5b19eb1a592412f4672d3a1203935d183c2a9dbf61d68023739ee43d3761ea795ae1a9f618c6098a9e89eacdd256 + languageName: node + linkType: hard + "is-symbol@npm:^1.0.2, is-symbol@npm:^1.0.3": version: 1.0.4 resolution: "is-symbol@npm:1.0.4" @@ -6101,20 +6753,7 @@ __metadata: languageName: node linkType: hard -"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3": - version: 1.1.10 - resolution: "is-typed-array@npm:1.1.10" - dependencies: - available-typed-arrays: ^1.0.5 - call-bind: ^1.0.2 - for-each: ^0.3.3 - gopd: ^1.0.1 - has-tostringtag: ^1.0.0 - checksum: aac6ecb59d4c56a1cdeb69b1f129154ef462bbffe434cb8a8235ca89b42f258b7ae94073c41b3cb7bce37f6a1733ad4499f07882d5d5093a7ba84dfc4ebb8017 - languageName: node - linkType: hard - -"is-typed-array@npm:^1.1.9": +"is-typed-array@npm:^1.1.10, is-typed-array@npm:^1.1.3, is-typed-array@npm:^1.1.9": version: 1.1.12 resolution: "is-typed-array@npm:1.1.12" dependencies: @@ -6949,6 +7588,16 @@ __metadata: languageName: node linkType: hard +"json-rpc-middleware-stream@npm:^4.2.0, json-rpc-middleware-stream@npm:^4.2.1": + version: 4.2.2 + resolution: "json-rpc-middleware-stream@npm:4.2.2" + dependencies: + "@metamask/safe-event-emitter": ^3.0.0 + readable-stream: ^2.3.3 + checksum: 01ff3a23b501fde5c2abb8c3b4d100c4fd430b41cf5e7750235f860a02d5823f8a43b0e81150c1d3bb196737f2273af1c7a50ff179e95e3d59fb7fe172249de3 + languageName: node + linkType: hard + "json-rpc-random-id@npm:^1.0.0, json-rpc-random-id@npm:^1.0.1": version: 1.0.1 resolution: "json-rpc-random-id@npm:1.0.1" @@ -6977,7 +7626,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:2.x, json5@npm:^2.2.2": +"json5@npm:2.x, json5@npm:^2.2.3": version: 2.2.3 resolution: "json5@npm:2.2.3" bin: @@ -7146,6 +7795,13 @@ __metadata: languageName: node linkType: hard +"luxon@npm:^3.2.1": + version: 3.4.2 + resolution: "luxon@npm:3.4.2" + checksum: efefdfaea90f4c8e502db8eb255385e6dac6733b6a9e1372eaf1a866cc1118363e235a3f6df505b837abc8bbcdfd8f0718a8de387b80cef9ee624d8791ca0844 + languageName: node + linkType: hard + "make-dir@npm:^3.0.0": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -7698,7 +8354,7 @@ __metadata: languageName: node linkType: hard -"once@npm:^1.3.0": +"once@npm:^1.3.0, once@npm:^1.3.1, once@npm:^1.4.0": version: 1.4.0 resolution: "once@npm:1.4.0" dependencies: @@ -7803,6 +8459,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:~0.2.0": + version: 0.2.9 + resolution: "pako@npm:0.2.9" + checksum: 055f9487cd57fbb78df84315873bbdd089ba286f3499daed47d2effdc6253e981f5db6898c23486de76d4a781559f890d643bd3a49f70f1b4a18019c98aa5125 + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -7905,6 +8568,17 @@ __metadata: languageName: node linkType: hard +"peek-stream@npm:^1.1.0": + version: 1.1.3 + resolution: "peek-stream@npm:1.1.3" + dependencies: + buffer-from: ^1.0.0 + duplexify: ^3.5.0 + through2: ^2.0.3 + checksum: a0e09d6d1a8a01158a3334f20d6b1cdd91747eba24eb06a1d742eefb620385593121a76d4378cc81f77cdce6a66df0575a41041b1189c510254aec91878afc99 + languageName: node + linkType: hard + "picocolors@npm:^1.0.0": version: 1.0.0 resolution: "picocolors@npm:1.0.0" @@ -8011,6 +8685,13 @@ __metadata: languageName: node linkType: hard +"process-nextick-args@npm:~1.0.6": + version: 1.0.7 + resolution: "process-nextick-args@npm:1.0.7" + checksum: 41224fbc803ac6c96907461d4dfc20942efa3ca75f2d521bcf7cf0e89f8dec127fb3fb5d76746b8fb468a232ea02d84824fae08e027aec185fd29049c66d49f8 + languageName: node + linkType: hard + "process-nextick-args@npm:~2.0.0": version: 2.0.1 resolution: "process-nextick-args@npm:2.0.1" @@ -8062,6 +8743,37 @@ __metadata: languageName: node linkType: hard +"pump@npm:^2.0.0": + version: 2.0.1 + resolution: "pump@npm:2.0.1" + dependencies: + end-of-stream: ^1.1.0 + once: ^1.3.1 + checksum: e9f26a17be00810bff37ad0171edb35f58b242487b0444f92fb7d78bc7d61442fa9b9c5bd93a43fd8fd8ddd3cc75f1221f5e04c790f42907e5baab7cf5e2b931 + languageName: node + linkType: hard + +"pump@npm:^3.0.0": + version: 3.0.0 + resolution: "pump@npm:3.0.0" + dependencies: + end-of-stream: ^1.1.0 + once: ^1.3.1 + checksum: e42e9229fba14732593a718b04cb5e1cfef8254544870997e0ecd9732b189a48e1256e4e5478148ecb47c8511dca2b09eae56b4d0aad8009e6fac8072923cfc9 + languageName: node + linkType: hard + +"pumpify@npm:^1.3.3": + version: 1.5.1 + resolution: "pumpify@npm:1.5.1" + dependencies: + duplexify: ^3.6.0 + inherits: ^2.0.3 + pump: ^2.0.0 + checksum: 26ca412ec8d665bd0d5e185c1b8f627728eff603440d75d22a58e421e3c66eaf86ec6fc6a6efc54808ecef65979279fa8e99b109a23ec1fa8d79f37e6978c9bd + languageName: node + linkType: hard + "punycode@npm:2.1.0": version: 2.1.0 resolution: "punycode@npm:2.1.0" @@ -8130,18 +8842,22 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:^3.6.0": - version: 3.6.2 - resolution: "readable-stream@npm:3.6.2" +"readable-stream@npm:2.3.3": + version: 2.3.3 + resolution: "readable-stream@npm:2.3.3" dependencies: - inherits: ^2.0.3 - string_decoder: ^1.1.1 - util-deprecate: ^1.0.1 - checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + core-util-is: ~1.0.0 + inherits: ~2.0.3 + isarray: ~1.0.0 + process-nextick-args: ~1.0.6 + safe-buffer: ~5.1.1 + string_decoder: ~1.0.3 + util-deprecate: ~1.0.1 + checksum: 76f9863065d7edc14abd78e68784048487e83a4b6908336ba3eacb5e9544d642ad60836f91fab16e1dc6ad9e493dfe6c2e5b65f370ec65454d415efa50361a76 languageName: node linkType: hard -"readable-stream@npm:~2.3.6": +"readable-stream@npm:^2.0.0, readable-stream@npm:^2.3.3, readable-stream@npm:~2.3.6": version: 2.3.8 resolution: "readable-stream@npm:2.3.8" dependencies: @@ -8156,6 +8872,26 @@ __metadata: languageName: node linkType: hard +"readable-stream@npm:^3.0.2, readable-stream@npm:^3.1.1, readable-stream@npm:^3.4.0, readable-stream@npm:^3.5.0, readable-stream@npm:^3.6.0": + version: 3.6.2 + resolution: "readable-stream@npm:3.6.2" + dependencies: + inherits: ^2.0.3 + string_decoder: ^1.1.1 + util-deprecate: ^1.0.1 + checksum: bdcbe6c22e846b6af075e32cf8f4751c2576238c5043169a1c221c92ee2878458a816a4ea33f4c67623c0b6827c8a400409bfb3cf0bf3381392d0b1dfb52ac8d + languageName: node + linkType: hard + +"readable-web-to-node-stream@npm:^3.0.2": + version: 3.0.2 + resolution: "readable-web-to-node-stream@npm:3.0.2" + dependencies: + readable-stream: ^3.6.0 + checksum: 8c56cc62c68513425ddfa721954875b382768f83fa20e6b31e365ee00cbe7a3d6296f66f7f1107b16cd3416d33aa9f1680475376400d62a081a88f81f0ea7f9c + languageName: node + linkType: hard + "regenerator-runtime@npm:^0.11.0": version: 0.11.1 resolution: "regenerator-runtime@npm:0.11.1" @@ -8265,6 +9001,13 @@ __metadata: languageName: node linkType: hard +"rfdc@npm:^1.3.0": + version: 1.3.0 + resolution: "rfdc@npm:1.3.0" + checksum: fb2ba8512e43519983b4c61bd3fa77c0f410eff6bae68b08614437bc3f35f91362215f7b4a73cbda6f67330b5746ce07db5dd9850ad3edc91271ad6deea0df32 + languageName: node + linkType: hard + "rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" @@ -8423,6 +9166,15 @@ __metadata: languageName: node linkType: hard +"ses@npm:^0.18.1, ses@npm:^0.18.7": + version: 0.18.7 + resolution: "ses@npm:0.18.7" + dependencies: + "@endo/env-options": ^0.1.3 + checksum: 75ac014771d9bc1f747193c6d0f9e7d2d7700a10311ba8d805d9bc78d4c20d4ef40537f0535b1ea6abf06babf67e70f8bd37b2ad68ad54992a0c5ce842181c87 + languageName: node + linkType: hard + "set-blocking@npm:^2.0.0": version: 2.0.0 resolution: "set-blocking@npm:2.0.0" @@ -8684,6 +9436,23 @@ __metadata: languageName: node linkType: hard +"stream-browserify@npm:^3.0.0": + version: 3.0.0 + resolution: "stream-browserify@npm:3.0.0" + dependencies: + inherits: ~2.0.4 + readable-stream: ^3.5.0 + checksum: 4c47ef64d6f03815a9ca3874e2319805e8e8a85f3550776c47ce523b6f4c6cd57f40e46ec6a9ab8ad260fde61863c2718f250d3bedb3fe9052444eb9abfd9921 + languageName: node + linkType: hard + +"stream-shift@npm:^1.0.0": + version: 1.0.1 + resolution: "stream-shift@npm:1.0.1" + checksum: 59b82b44b29ec3699b5519a49b3cedcc6db58c72fb40c04e005525dfdcab1c75c4e0c180b923c380f204bed78211b9bad8faecc7b93dece4d004c3f6ec75737b + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -8758,6 +9527,15 @@ __metadata: languageName: node linkType: hard +"string_decoder@npm:~1.0.3": + version: 1.0.3 + resolution: "string_decoder@npm:1.0.3" + dependencies: + safe-buffer: ~5.1.0 + checksum: 57ef02a148fd1ff2f20fe1accd944505ed3703e78bb28d302d940b2ad3dfb469508f79dcd0275ba1960d9675aa206452f76b2416059a6d0b0200bd7e9f552cdb + languageName: node + linkType: hard + "string_decoder@npm:~1.1.1": version: 1.1.1 resolution: "string_decoder@npm:1.1.1" @@ -8829,6 +9607,13 @@ __metadata: languageName: node linkType: hard +"strnum@npm:^1.0.5": + version: 1.0.5 + resolution: "strnum@npm:1.0.5" + checksum: 651b2031db5da1bf4a77fdd2f116a8ac8055157c5420f5569f64879133825915ad461513e7202a16d7fec63c54fd822410d0962f8ca12385c4334891b9ae6dd2 + languageName: node + linkType: hard + "superstruct@npm:^1.0.3": version: 1.0.3 resolution: "superstruct@npm:1.0.3" @@ -8897,6 +9682,19 @@ __metadata: languageName: node linkType: hard +"tar-stream@npm:^2.2.0": + version: 2.2.0 + resolution: "tar-stream@npm:2.2.0" + dependencies: + bl: ^4.0.3 + end-of-stream: ^1.4.1 + fs-constants: ^1.0.0 + inherits: ^2.0.3 + readable-stream: ^3.1.1 + checksum: 699831a8b97666ef50021c767f84924cfee21c142c2eb0e79c63254e140e6408d6d55a065a2992548e72b06de39237ef2b802b99e3ece93ca3904a37622a66f3 + languageName: node + linkType: hard + "tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.1.15 resolution: "tar@npm:6.1.15" @@ -9222,6 +10020,13 @@ __metadata: languageName: node linkType: hard +"typedarray@npm:^0.0.6": + version: 0.0.6 + resolution: "typedarray@npm:0.0.6" + checksum: 33b39f3d0e8463985eeaeeacc3cb2e28bc3dfaf2a5ed219628c0b629d5d7b810b0eb2165f9f607c34871d5daa92ba1dc69f49051cf7d578b4cbd26c340b9d1b1 + languageName: node + linkType: hard + "typedoc-plugin-missing-exports@npm:^0.22.6": version: 0.22.6 resolution: "typedoc-plugin-missing-exports@npm:0.22.6" @@ -9390,6 +10195,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.0 + resolution: "uuid@npm:9.0.0" + bin: + uuid: dist/bin/uuid + checksum: 8dd2c83c43ddc7e1c71e36b60aea40030a6505139af6bee0f382ebcd1a56f6cd3028f7f06ffb07f8cf6ced320b76aea275284b224b002b289f89fe89c389b028 + languageName: node + linkType: hard + "v8-compile-cache-lib@npm:^3.0.1": version: 3.0.1 resolution: "v8-compile-cache-lib@npm:3.0.1" @@ -9408,6 +10222,15 @@ __metadata: languageName: node linkType: hard +"validate-npm-package-name@npm:^5.0.0": + version: 5.0.0 + resolution: "validate-npm-package-name@npm:5.0.0" + dependencies: + builtins: ^5.0.0 + checksum: 5342a994986199b3c28e53a8452a14b2bb5085727691ea7aa0d284a6606b127c371e0925ae99b3f1ef7cc7d2c9de75f52eb61a3d1cc45e39bca1e3a9444cbb4e + languageName: node + linkType: hard + "vscode-oniguruma@npm:^1.6.1": version: 1.7.0 resolution: "vscode-oniguruma@npm:1.7.0" @@ -9449,6 +10272,29 @@ __metadata: languageName: node linkType: hard +"webextension-polyfill-ts@npm:^0.25.0": + version: 0.25.0 + resolution: "webextension-polyfill-ts@npm:0.25.0" + dependencies: + webextension-polyfill: ^0.7.0 + checksum: c4dc82c86e34cea717a26af549f2822d63e92af52632f5e909ea13b5e7bd9d6110781f10313e36ada2b54c770ebca018bc3784756d12ba3b0b623d285f1a14a7 + languageName: node + linkType: hard + +"webextension-polyfill@npm:>=0.10.0 <1.0, webextension-polyfill@npm:^0.10.0": + version: 0.10.0 + resolution: "webextension-polyfill@npm:0.10.0" + checksum: 4a59036bda571360c2c0b2fb03fe1dc244f233946bcf9a6766f677956c40fd14d270aaa69cdba95e4ac521014afbe4008bfa5959d0ac39f91c990eb206587f91 + languageName: node + linkType: hard + +"webextension-polyfill@npm:^0.7.0": + version: 0.7.0 + resolution: "webextension-polyfill@npm:0.7.0" + checksum: fb738a5de07feb593875e02f25c3ab4276c8736118929556c8d4bdf965bb0f11c96ea263cd397b9b21259e8faf2dce2eaaa42ce08c922d96de7adb5896ec7d10 + languageName: node + linkType: hard + "webidl-conversions@npm:^3.0.0": version: 3.0.1 resolution: "webidl-conversions@npm:3.0.1" @@ -9527,7 +10373,7 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.11": +"which-typed-array@npm:^1.1.10, which-typed-array@npm:^1.1.11, which-typed-array@npm:^1.1.2": version: 1.1.11 resolution: "which-typed-array@npm:1.1.11" dependencies: @@ -9540,20 +10386,6 @@ __metadata: languageName: node linkType: hard -"which-typed-array@npm:^1.1.2": - version: 1.1.10 - resolution: "which-typed-array@npm:1.1.10" - dependencies: - available-typed-arrays: ^1.0.5 - call-bind: ^1.0.2 - for-each: ^0.3.3 - gopd: ^1.0.1 - has-tostringtag: ^1.0.0 - is-typed-array: ^1.1.10 - checksum: 149f54f5d11773ce938c60a2c36306720a1824eccb62bda0620170932c2783fa50ad0226254c5741a962e35c7ccba5f4e4c402b8618cb3b4f2cf47bf5e6ade31 - languageName: node - linkType: hard - "which@npm:^2.0.1, which@npm:^2.0.2": version: 2.0.2 resolution: "which@npm:2.0.2"