diff --git a/README.md b/README.md index e0a425c8b86..50b5fd67982 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,8 @@ This repository contains the following packages [^fn1]: - [`@metamask/selected-network-controller`](packages/selected-network-controller) - [`@metamask/signature-controller`](packages/signature-controller) - [`@metamask/transaction-controller`](packages/transaction-controller) +- [`@metamask/user-operation-controller`](packages/user-operation-controller) +- [`@metamask/wallet`](packages/wallet) @@ -79,19 +81,24 @@ linkStyle default opacity:0.5 selected_network_controller(["@metamask/selected-network-controller"]); signature_controller(["@metamask/signature-controller"]); transaction_controller(["@metamask/transaction-controller"]); + user_operation_controller(["@metamask/user-operation-controller"]); + wallet(["@metamask/wallet"]); accounts_controller --> base_controller; accounts_controller --> keyring_controller; address_book_controller --> base_controller; address_book_controller --> controller_utils; announcement_controller --> base_controller; approval_controller --> base_controller; + assets_controllers --> accounts_controller; assets_controllers --> approval_controller; assets_controllers --> base_controller; assets_controllers --> controller_utils; + assets_controllers --> keyring_controller; assets_controllers --> network_controller; assets_controllers --> polling_controller; assets_controllers --> preferences_controller; composable_controller --> base_controller; + composable_controller --> json_rpc_engine; ens_controller --> base_controller; ens_controller --> controller_utils; ens_controller --> network_controller; @@ -103,21 +110,21 @@ linkStyle default opacity:0.5 json_rpc_middleware_stream --> json_rpc_engine; keyring_controller --> base_controller; keyring_controller --> message_manager; - keyring_controller --> preferences_controller; logging_controller --> base_controller; logging_controller --> controller_utils; message_manager --> base_controller; message_manager --> controller_utils; name_controller --> base_controller; + name_controller --> controller_utils; network_controller --> base_controller; network_controller --> controller_utils; network_controller --> eth_json_rpc_provider; network_controller --> json_rpc_engine; notification_controller --> base_controller; - permission_controller --> approval_controller; permission_controller --> base_controller; permission_controller --> controller_utils; permission_controller --> json_rpc_engine; + permission_controller --> approval_controller; permission_log_controller --> base_controller; permission_log_controller --> json_rpc_engine; phishing_controller --> base_controller; @@ -127,7 +134,7 @@ linkStyle default opacity:0.5 polling_controller --> network_controller; preferences_controller --> base_controller; preferences_controller --> controller_utils; - queued_request_controller --> approval_controller; + preferences_controller --> keyring_controller; queued_request_controller --> base_controller; queued_request_controller --> controller_utils; queued_request_controller --> json_rpc_engine; @@ -137,6 +144,7 @@ linkStyle default opacity:0.5 selected_network_controller --> base_controller; selected_network_controller --> json_rpc_engine; selected_network_controller --> network_controller; + selected_network_controller --> permission_controller; signature_controller --> approval_controller; signature_controller --> base_controller; signature_controller --> controller_utils; @@ -148,6 +156,25 @@ linkStyle default opacity:0.5 transaction_controller --> controller_utils; transaction_controller --> gas_fee_controller; transaction_controller --> network_controller; + user_operation_controller --> approval_controller; + user_operation_controller --> base_controller; + user_operation_controller --> controller_utils; + user_operation_controller --> gas_fee_controller; + user_operation_controller --> keyring_controller; + user_operation_controller --> network_controller; + user_operation_controller --> polling_controller; + user_operation_controller --> transaction_controller; + wallet --> accounts_controller; + wallet --> approval_controller; + wallet --> assets_controllers; + wallet --> base_controller; + wallet --> gas_fee_controller; + wallet --> json_rpc_engine; + wallet --> keyring_controller; + wallet --> network_controller; + wallet --> preferences_controller; + wallet --> transaction_controller; + wallet --> controller_utils; ``` diff --git a/packages/wallet/CHANGELOG.md b/packages/wallet/CHANGELOG.md new file mode 100644 index 00000000000..b518709c7b8 --- /dev/null +++ b/packages/wallet/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +[Unreleased]: https://github.com/MetaMask/core/ diff --git a/packages/wallet/LICENSE b/packages/wallet/LICENSE new file mode 100644 index 00000000000..6f8bff03fc4 --- /dev/null +++ b/packages/wallet/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright (c) 2024 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/wallet/README.md b/packages/wallet/README.md new file mode 100644 index 00000000000..7e788b21dd4 --- /dev/null +++ b/packages/wallet/README.md @@ -0,0 +1,15 @@ +# `@metamask/wallet` + +MetaMask wallet library + +## Installation + +`yarn add @metamask/wallet` + +or + +`npm install @metamask/wallet` + +## 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/wallet/jest.config.js b/packages/wallet/jest.config.js new file mode 100644 index 00000000000..ca084133399 --- /dev/null +++ b/packages/wallet/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/wallet/package.json b/packages/wallet/package.json new file mode 100644 index 00000000000..e50d97c10b0 --- /dev/null +++ b/packages/wallet/package.json @@ -0,0 +1,73 @@ +{ + "name": "@metamask/wallet", + "version": "0.0.0", + "description": "MetaMask wallet library", + "keywords": [ + "MetaMask", + "Ethereum" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/wallet#readme", + "bugs": { + "url": "https://github.com/MetaMask/core/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/MetaMask/core.git" + }, + "license": "MIT", + "sideEffects": false, + "exports": { + ".": { + "import": "./dist/index.mjs", + "require": "./dist/index.js", + "types": "./dist/types/index.d.ts" + }, + "./package.json": "./package.json" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/" + ], + "scripts": { + "build:docs": "typedoc", + "changelog:validate": "../../scripts/validate-changelog.sh @metamask/wallet", + "publish:preview": "yarn npm publish --tag preview", + "test": "jest --reporters=jest-silent-reporter", + "test:clean": "jest --clearCache", + "test:verbose": "jest --verbose", + "test:watch": "jest --watch" + }, + "dependencies": { + "@metamask/accounts-controller": "^12.0.1", + "@metamask/approval-controller": "^6.0.1", + "@metamask/assets-controllers": "^27.2.0", + "@metamask/base-controller": "^5.0.1", + "@metamask/gas-fee-controller": "^14.0.1", + "@metamask/json-rpc-engine": "^8.0.1", + "@metamask/keyring-api": "^5.1.0", + "@metamask/keyring-controller": "^14.0.1", + "@metamask/network-controller": "^18.0.1", + "@metamask/preferences-controller": "^9.0.1", + "@metamask/transaction-controller": "^25.3.0", + "@metamask/utils": "^8.4.0" + }, + "devDependencies": { + "@metamask/auto-changelog": "^3.4.4", + "@metamask/controller-utils": "^9.0.2", + "@types/jest": "^27.4.1", + "deepmerge": "^4.2.2", + "jest": "^27.5.1", + "ts-jest": "^27.1.4", + "typedoc": "^0.24.8", + "typedoc-plugin-missing-exports": "^2.0.0", + "typescript": "~4.8.4" + }, + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/wallet/src/Wallet.d.ts b/packages/wallet/src/Wallet.d.ts new file mode 100644 index 00000000000..2dcd17d0a8a --- /dev/null +++ b/packages/wallet/src/Wallet.d.ts @@ -0,0 +1,308 @@ +import type { + AccountsController, + AccountsControllerActions, + AccountsControllerEvents, + AccountsControllerState, +} from '@metamask/accounts-controller'; +import type { + ApprovalController, + ApprovalControllerActions, + ApprovalControllerEvents, + ApprovalControllerState, +} from '@metamask/approval-controller'; +import type { + CurrencyRateController, + CurrencyRateControllerActions, + CurrencyRateControllerEvents, + CurrencyRateState, +} from '@metamask/assets-controllers'; +import type { + ControllerGetStateAction, + ControllerMessenger, + ControllerStateChangeEvent, +} from '@metamask/base-controller'; +import type { + GasFeeController, + GasFeeControllerActions, + GasFeeControllerEvents, + GasFeeState, +} from '@metamask/gas-fee-controller'; +import type { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import type { + KeyringController, + KeyringControllerActions, + KeyringControllerEvents, + KeyringControllerState, +} from '@metamask/keyring-controller'; +import type { + NetworkController, + NetworkControllerActions, + NetworkControllerEvents, + NetworkState, +} from '@metamask/network-controller'; +import type { + CaveatSpecificationConstraint, + ExtractPermission, + PermissionController, + PermissionControllerActions, + PermissionControllerEvents, + PermissionControllerState, + PermissionSpecificationConstraint, + SubjectType, +} from '@metamask/permission-controller'; +import type { + PreferencesController, + PreferencesControllerActions, + PreferencesControllerEvents, + PreferencesState, +} from '@metamask/preferences-controller'; +import type { + TransactionController, + TransactionControllerActions, + TransactionControllerEvents, + TransactionControllerState, +} from '@metamask/transaction-controller'; + +import type { + InternalCaveatSpecification, + InternalPermissionSpecification, +} from './permissions'; + +/** + * MetaMask wallet state. + */ +export type MetamaskState< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> = { + accountsController: AccountsControllerState; + approvalController: ApprovalControllerState; + currencyRateController: CurrencyRateState; + gasFeeController: GasFeeState; + keyringController: KeyringControllerState; + networkController: NetworkState; + permissionController: PermissionControllerState< + ExtractPermission< + ControllerPermissionSpecification, + ControllerCaveatSpecification & InternalCaveatSpecification + > + >; + preferencesController: PreferencesState; + transactionController: TransactionControllerState; +}; + +export type WalletGetState< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> = ControllerGetStateAction< + 'Wallet', + MetamaskState< + ControllerPermissionSpecification, + ControllerCaveatSpecification + > +>; + +export type WalletActions< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> = WalletGetState< + ControllerPermissionSpecification, + ControllerCaveatSpecification +>; + +/** + * All wallet actions. + */ +export type AllWalletActions< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> = + | AccountsControllerActions + | ApprovalControllerActions + | CurrencyRateControllerActions + | GasFeeControllerActions + | KeyringControllerActions + | NetworkControllerActions + | PermissionControllerActions + | PreferencesControllerActions + | TransactionControllerActions + | WalletActions< + ControllerPermissionSpecification, + ControllerCaveatSpecification + >; + +export type WalletStateChange< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> = ControllerStateChangeEvent< + 'Wallet', + MetamaskState< + ControllerPermissionSpecification, + ControllerCaveatSpecification + > +>; + +export type WalletEvents< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> = WalletStateChange< + ControllerPermissionSpecification, + ControllerCaveatSpecification +>; + +/** + * All wallet events. + */ +export type AllWalletEvents< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> = + | AccountsControllerEvents + | ApprovalControllerEvents + | CurrencyRateControllerEvents + | GasFeeControllerEvents + | KeyringControllerEvents + | NetworkControllerEvents + | PermissionControllerEvents + | PreferencesControllerEvents + | TransactionControllerEvents + | WalletEvents< + ControllerPermissionSpecification, + ControllerCaveatSpecification + >; + +/** + * A MetaMask wallet. + * + * @template ControllerPermissionSpecification - A union of the types of all + * permission specifications available to the controller. Any referenced caveats + * must be included in the controller's caveat specifications. + * @template ControllerCaveatSpecification - A union of the types of all + * caveat specifications available to the controller. + */ +export class MetamaskWallet< + ControllerPermissionSpecification extends PermissionSpecificationConstraint & + InternalPermissionSpecification = InternalPermissionSpecification, + ControllerCaveatSpecification extends CaveatSpecificationConstraint & + InternalCaveatSpecification = InternalCaveatSpecification, +> { + #controllerMessenger: ControllerMessenger; + + #controllers: { + accountsController: AccountsController; + approvalController: ApprovalController; + currencyRateController: CurrencyRateController; + gasFeeController: GasFeeController; + keyringController: KeyringController; + networkController: NetworkController; + permissionController: PermissionController< + ControllerPermissionSpecification, + ControllerCaveatSpecification + >; + preferencesController: PreferencesController; + transactionController: TransactionController; + }; + + #services: { + // TODO: Create EVM RPC request service, extracting from fetch middleware + evmRpcRequest: () => void; + // TODO: Create Etherscan service, extracting from transaction controller + }; + + /** + * Construct a MetaMask wallet. + * + * @param options - Options. + * @param options.controllerMessenger - An unrestricted global messenger, used as the primary + * message broker for the wallet. + * @param options.state - The initial wallet state, broken down by controller. + */ + constructor({ + controllerMessenger, + state = {}, + }: { + controllerMessenger: ControllerMessenger; + state?: Partial< + MetamaskState< + ControllerPermissionSpecification, + ControllerCaveatSpecification + > + >; + }); + + /** + * Initialize the wallet. + * + * This step is for asynchronous operations that should be performed after the wallet is + * initially constructed. For example, status checks, remote configuration updates, or preemptive + * caching. + * + * Note that this initialziation may not occur right away after wallet construction. For new + * wallet installations, this initialization will not be run until after onboarding. + */ + initialize(): Promise; + + // TODO: Consider adding state reset to base controller + + /** + * Reset all transient wallet state. + * + * This method is meant for applications that expect to automatically restart during typical + * operation (e.g. a wallet running in a service worker). Such applications can persist all + * wallet state, including transient state, to ensure caches are not cleared during these routine + * restarts. But after a fresh application start, we still want to have the ability to clear + * transient data that is not intended to be persisted. + * + * This method will erase all transient wallet state, leaving only persistent state. It should be + * called before initialization during a new application session. + */ + resetState(): void; + + /** + * Create a "provider engine" for the given subject. + * + * A provider engine is a JSON-RPC request handler for provider JSON-RPC requests. It can be + * used to construct a provider, or to implement a wallet API. + * + * @param options - Options + * @param options.origin - The origin of the subject. + * @param options.subjectType - The type of the subject. + */ + createProviderEngine({ + origin, + subjectType, + }: { + origin: string; + subjectType: SubjectType; + }): JsonRpcEngine; + + // TODO: Add start/resume, pause, stop methods to control all polling/services + + // Additional jotnotes: + // + // Step0: Rename controller messenger, write ADR about services and selectors, refactor guts of ComposableController into compose function + // Step1: Create wallet package + // Step2: Update options to take the root controller messenger, no controllers and no restricted controller + // Step3: Add controllers and services one-by-one, starting with keyring + // Step4: After adding the network controller, add `createProviderEngine` method for RPC pipeline + // Step5: Handle state reset + // + // The wallet API is called through the messenger, same as for a controller + // Actions and events + // We would also expose actions and events from internal controllers/services + // Leave the controller API to the clients +} diff --git a/packages/wallet/src/Wallet.test.ts b/packages/wallet/src/Wallet.test.ts new file mode 100644 index 00000000000..fb48cc927b0 --- /dev/null +++ b/packages/wallet/src/Wallet.test.ts @@ -0,0 +1,191 @@ +import { ControllerMessenger } from '@metamask/base-controller'; +import { NetworkType } from '@metamask/controller-utils'; +import { + defaultState as defaultNetworkState, + NetworkStatus, +} from '@metamask/network-controller'; + +import { MetamaskWallet, type WalletActions, type WalletEvents } from '.'; +import { SubjectType } from '../../permission-controller/src/SubjectMetadataController'; + +describe('Wallet', () => { + describe('constructor', () => { + it('initializes with default state', () => { + const controllerMessenger = new ControllerMessenger< + WalletActions, + WalletEvents + >(); + new MetamaskWallet({ controllerMessenger }); + + // This snapshot is too large for inline + // eslint-disable-next-line jest/no-restricted-matchers + expect(controllerMessenger.call('Wallet:getState')).toMatchSnapshot(); + }); + + it('initializes with persistent state', () => { + const controllerMessenger = new ControllerMessenger< + WalletActions, + WalletEvents + >(); + new MetamaskWallet({ + controllerMessenger, + state: { + networkController: { + ...defaultNetworkState, + // This state is persisted + selectedNetworkClientId: NetworkType.sepolia, + }, + }, + }); + + // This snapshot is too large for inline + // eslint-disable-next-line jest/no-restricted-matchers + expect(controllerMessenger.call('Wallet:getState')).toMatchSnapshot(); + }); + + it('initializes with persistent and transient state', () => { + const controllerMessenger = new ControllerMessenger< + WalletActions, + WalletEvents + >(); + new MetamaskWallet({ + controllerMessenger, + state: { + // The ApprovalController state is transient + approvalController: { + approvalFlows: [], + pendingApprovalCount: 1, + pendingApprovals: { + '123': { + id: '123', + origin: 'metamask.test', + time: 1, + type: 'Example', + requestData: {}, + requestState: null, + expectsResult: false, + }, + }, + }, + networkController: { + ...defaultNetworkState, + // This state is persisted + selectedNetworkClientId: NetworkType.sepolia, + }, + }, + }); + + // This snapshot is too large for inline + // eslint-disable-next-line jest/no-restricted-matchers + expect(controllerMessenger.call('Wallet:getState')).toMatchSnapshot(); + }); + }); + + describe('initialize', () => { + it('initializes network status', async () => { + const controllerMessenger = new ControllerMessenger< + WalletActions, + WalletEvents + >(); + const wallet = new MetamaskWallet({ controllerMessenger }); + + await wallet.initialize(); + + expect( + controllerMessenger.call('Wallet:getState').networkController + .networksMetadata[NetworkType.mainnet].status, + ).toBe(NetworkStatus.Available); + }); + }); + + describe('resetState', () => { + it('clears transient state', () => { + const controllerMessenger = new ControllerMessenger< + WalletActions, + WalletEvents + >(); + const wallet = new MetamaskWallet({ + controllerMessenger, + state: { + // The ApprovalController state is transient + approvalController: { + approvalFlows: [], + pendingApprovalCount: 1, + pendingApprovals: { + '123': { + id: '123', + origin: 'metamask.test', + time: 1, + type: 'Example', + requestData: {}, + requestState: null, + expectsResult: false, + }, + }, + }, + }, + }); + + wallet.resetState(); + + // This snapshot is too large for inline + // eslint-disable-next-line jest/no-restricted-matchers + expect( + controllerMessenger.call('Wallet:getState').approvalController + .pendingApprovalCount, + ).toBe(0); + }); + + it('does not clear persistent state', () => { + const controllerMessenger = new ControllerMessenger< + WalletActions, + WalletEvents + >(); + const wallet = new MetamaskWallet({ + controllerMessenger, + state: { + networkController: { + ...defaultNetworkState, + // This state is persisted + selectedNetworkClientId: NetworkType.sepolia, + }, + }, + }); + + wallet.resetState(); + + // This snapshot is too large for inline + // eslint-disable-next-line jest/no-restricted-matchers + expect( + controllerMessenger.call('Wallet:getState').networkController + .selectedNetworkClientId, + ).toBe(NetworkType.sepolia); + }); + }); + + describe('createProviderEngine', () => { + it('creates working provider engine', async () => { + const controllerMessenger = new ControllerMessenger< + WalletActions, + WalletEvents + >(); + const wallet = new MetamaskWallet({ + controllerMessenger, + state: {}, + }); + await wallet.initialize(); + + const providerEngine = wallet.createProviderEngine({ + origin: 'metamask', + subjectType: SubjectType.Internal, + }); + + const accounts = await providerEngine.handle({ + id: 1, + jsonrpc: '2.0', + method: 'eth_accounts', + }); + expect(accounts).toBe(['0x1']); + }); + }); +}); diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts new file mode 100644 index 00000000000..96379bb7f73 --- /dev/null +++ b/packages/wallet/src/index.ts @@ -0,0 +1,11 @@ +export { MetamaskWallet } from './Wallet'; +export type { + AllWalletActions, + AllWalletEvents, + WalletActions, + WalletEvents, +} from './Wallet'; +export type { + InternalCaveatSpecification, + InternalPermissionSpecification, +} from './permissions'; diff --git a/packages/wallet/src/permissions.d.ts b/packages/wallet/src/permissions.d.ts new file mode 100644 index 00000000000..fc8941bd7aa --- /dev/null +++ b/packages/wallet/src/permissions.d.ts @@ -0,0 +1,64 @@ +import type { + CaveatValidator, + PermissionConstraint, + PermissionFactory, + PermissionType, + RestrictedMethod, +} from '@metamask/permission-controller'; +import type { Hex } from '@metamask/utils'; + +declare const CaveatTypes: { + readonly restrictReturnedAccounts: 'restrictReturnedAccounts'; +}; + +declare const RestrictedMethods: { + // These properties match RPC method names, which follow a different naming convention + /* eslint-disable @typescript-eslint/naming-convention */ + readonly eth_accounts: 'eth_accounts'; + /* eslint-enable @typescript-eslint/naming-convention */ +}; + +type InternalCaveats = { + restrictReturnedAccounts: { + type: typeof CaveatTypes.restrictReturnedAccounts; + + value: Hex[]; + }; +}; + +export type InternalCaveatSpecification = { + type: typeof CaveatTypes.restrictReturnedAccounts; + + decorator: ( + method: string, + caveat: InternalCaveats['restrictReturnedAccounts'], + ) => Hex[]; + + validator: CaveatValidator; +}; + +type InternalPermissions = { + [RestrictedMethods.eth_accounts]: { + caveats: [InternalCaveats['restrictReturnedAccounts']]; + date: number; + id: string; + invoker: string; + parentCapability: typeof RestrictedMethods.eth_accounts; + }; +}; + +export type InternalPermissionSpecification = { + permissionType: PermissionType.RestrictedMethod; + targetName: typeof RestrictedMethods.eth_accounts; + allowedCaveats: [typeof CaveatTypes.restrictReturnedAccounts]; + factory: PermissionFactory< + InternalPermissions[typeof RestrictedMethods.eth_accounts], + Record + >; + methodImplementation: RestrictedMethod<[], Hex[]>; + validator: ( + permission: PermissionConstraint, + origin?: string, + target?: string, + ) => void; +}; diff --git a/packages/wallet/tsconfig.build.json b/packages/wallet/tsconfig.build.json new file mode 100644 index 00000000000..6d8541b98ef --- /dev/null +++ b/packages/wallet/tsconfig.build.json @@ -0,0 +1,22 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist/types", + "rootDir": "./src" + }, + "references": [ + { "path": "../accounts-controller/tsconfig.build.json" }, + { "path": "../approval-controller/tsconfig.build.json" }, + { "path": "../assets-controllers/tsconfig.build.json" }, + { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../controller-utils/tsconfig.build.json" }, + { "path": "../json-rpc-engine/tsconfig.build.json" }, + { "path": "../keyring-controller/tsconfig.build.json" }, + { "path": "../network-controller/tsconfig.build.json" }, + { "path": "../permission-controller/tsconfig.build.json" }, + { "path": "../preferences-controller/tsconfig.build.json" }, + { "path": "../transaction-controller/tsconfig.build.json" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/wallet/tsconfig.json b/packages/wallet/tsconfig.json new file mode 100644 index 00000000000..7f566d7fc7a --- /dev/null +++ b/packages/wallet/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "references": [ + { "path": "../accounts-controller" }, + { "path": "../approval-controller" }, + { "path": "../assets-controllers" }, + { "path": "../base-controller" }, + { "path": "../controller-utils" }, + { "path": "../json-rpc-engine" }, + { "path": "../keyring-controller" }, + { "path": "../network-controller" }, + { "path": "../permission-controller" }, + { "path": "../preferences-controller" }, + { "path": "../transaction-controller" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/wallet/typedoc.json b/packages/wallet/typedoc.json new file mode 100644 index 00000000000..c9da015dbf8 --- /dev/null +++ b/packages/wallet/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 96be50e5b38..9932ecacf73 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -29,7 +29,8 @@ { "path": "./packages/selected-network-controller/tsconfig.build.json" }, { "path": "./packages/signature-controller/tsconfig.build.json" }, { "path": "./packages/transaction-controller/tsconfig.build.json" }, - { "path": "./packages/user-operation-controller/tsconfig.build.json" } + { "path": "./packages/user-operation-controller/tsconfig.build.json" }, + { "path": "./packages/wallet" } ], "files": [], "include": [] diff --git a/tsconfig.json b/tsconfig.json index d32a4c26ff2..6ad93902134 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -29,7 +29,8 @@ { "path": "./packages/selected-network-controller" }, { "path": "./packages/signature-controller" }, { "path": "./packages/transaction-controller" }, - { "path": "./packages/user-operation-controller" } + { "path": "./packages/user-operation-controller" }, + { "path": "./packages/wallet" } ], "files": [], "include": ["./types"] diff --git a/yarn.lock b/yarn.lock index b94ce729691..d49361fd073 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1746,7 +1746,7 @@ __metadata: languageName: node linkType: hard -"@metamask/assets-controllers@workspace:packages/assets-controllers": +"@metamask/assets-controllers@^27.2.0, @metamask/assets-controllers@workspace:packages/assets-controllers": version: 0.0.0-use.local resolution: "@metamask/assets-controllers@workspace:packages/assets-controllers" dependencies: @@ -2384,6 +2384,18 @@ __metadata: languageName: node linkType: hard +"@metamask/json-rpc-middleware-stream@npm:^6.0.2": + version: 6.0.2 + resolution: "@metamask/json-rpc-middleware-stream@npm:6.0.2" + dependencies: + "@metamask/json-rpc-engine": ^7.3.2 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^8.3.0 + readable-stream: ^3.6.2 + checksum: e831041b03e9f48f584f4425188f72b58974f95b60429c9fe8b5561da69c6bbfad2f2b2199acdff06ee718967214b65c05604d4f85f3287186619683487f1060 + languageName: node + linkType: hard + "@metamask/json-rpc-middleware-stream@workspace:packages/json-rpc-middleware-stream": version: 0.0.0-use.local resolution: "@metamask/json-rpc-middleware-stream@workspace:packages/json-rpc-middleware-stream" @@ -2451,6 +2463,21 @@ __metadata: languageName: node linkType: hard +"@metamask/keyring-api@npm:^5.1.0": + version: 5.1.0 + resolution: "@metamask/keyring-api@npm:5.1.0" + dependencies: + "@metamask/snaps-sdk": ^3.1.1 + "@metamask/utils": ^8.3.0 + "@types/uuid": ^9.0.1 + superstruct: ^1.0.3 + uuid: ^9.0.0 + peerDependencies: + "@metamask/providers": ">=15 <17" + checksum: 37cc4d35116285762be7167f294a31add9a978eca7652af014355ce691c9b90cfeb52ab8732ebc59d413aeb7ac87e785e4a2a49fd44d3c1563f3796e4ac1bb8f + languageName: node + linkType: hard + "@metamask/keyring-controller@^14.0.1, @metamask/keyring-controller@workspace:packages/keyring-controller": version: 0.0.0-use.local resolution: "@metamask/keyring-controller@workspace:packages/keyring-controller" @@ -2815,6 +2842,26 @@ __metadata: languageName: node linkType: hard +"@metamask/providers@npm:^15.0.0": + version: 15.0.0 + resolution: "@metamask/providers@npm:15.0.0" + dependencies: + "@metamask/json-rpc-engine": ^7.3.2 + "@metamask/json-rpc-middleware-stream": ^6.0.2 + "@metamask/object-multiplex": ^2.0.0 + "@metamask/rpc-errors": ^6.2.1 + "@metamask/safe-event-emitter": ^3.0.0 + "@metamask/utils": ^8.3.0 + detect-browser: ^5.2.0 + extension-port-stream: ^3.0.0 + fast-deep-equal: ^3.1.3 + is-stream: ^2.0.0 + readable-stream: ^3.6.2 + webextension-polyfill: ^0.10.0 + checksum: 42571450e79d69d943384f557f6a61e0f73101d49804fb6e8075d791959f76c42b8ff626f711d434674792d77aead6cb8a32b04a3dcd53598c8aff24cbb1ad25 + languageName: node + linkType: hard + "@metamask/queued-request-controller@workspace:packages/queued-request-controller": version: 0.0.0-use.local resolution: "@metamask/queued-request-controller@workspace:packages/queued-request-controller" @@ -3090,6 +3137,20 @@ __metadata: languageName: node linkType: hard +"@metamask/snaps-sdk@npm:^3.1.1": + version: 3.1.1 + resolution: "@metamask/snaps-sdk@npm:3.1.1" + dependencies: + "@metamask/key-tree": ^9.0.0 + "@metamask/providers": ^15.0.0 + "@metamask/rpc-errors": ^6.2.1 + "@metamask/utils": ^8.3.0 + fast-xml-parser: ^4.3.4 + superstruct: ^1.0.3 + checksum: dbedd7c331bbe7900f7fec96a43bf90ec8637db8ae30b181d6a9f53ed5b14e8b48d7ffe83f5821d97a93530e91f0e0262731e48938c872cb000eb5ad45382d68 + languageName: node + linkType: hard + "@metamask/snaps-utils@npm:^5.0.0, @metamask/snaps-utils@npm:^5.1.1, @metamask/snaps-utils@npm:^5.1.2, @metamask/snaps-utils@npm:^5.2.0": version: 5.2.0 resolution: "@metamask/snaps-utils@npm:5.2.0" @@ -3254,6 +3315,51 @@ __metadata: languageName: node linkType: hard +"@metamask/utils@npm:^8.4.0": + version: 8.4.0 + resolution: "@metamask/utils@npm:8.4.0" + dependencies: + "@ethereumjs/tx": ^4.2.0 + "@noble/hashes": ^1.3.1 + "@scure/base": ^1.1.3 + "@types/debug": ^4.1.7 + debug: ^4.3.4 + pony-cause: ^2.1.10 + semver: ^7.5.4 + superstruct: ^1.0.3 + uuid: ^9.0.1 + checksum: b0397e97bac7192f6189a8625a2dfcb56d3c2cf4dd2cb3d4e012a7e9786f04f59f6917805544bc131a6dacd2c8344e237ae43ad47429bb5eb35c6cf1248440b4 + languageName: node + linkType: hard + +"@metamask/wallet@workspace:packages/wallet": + version: 0.0.0-use.local + resolution: "@metamask/wallet@workspace:packages/wallet" + dependencies: + "@metamask/accounts-controller": ^12.0.1 + "@metamask/approval-controller": ^6.0.1 + "@metamask/assets-controllers": ^27.2.0 + "@metamask/auto-changelog": ^3.4.4 + "@metamask/base-controller": ^5.0.1 + "@metamask/controller-utils": ^9.0.2 + "@metamask/gas-fee-controller": ^14.0.1 + "@metamask/json-rpc-engine": ^8.0.1 + "@metamask/keyring-api": ^5.1.0 + "@metamask/keyring-controller": ^14.0.1 + "@metamask/network-controller": ^18.0.1 + "@metamask/preferences-controller": ^9.0.1 + "@metamask/transaction-controller": ^25.3.0 + "@metamask/utils": ^8.4.0 + "@types/jest": ^27.4.1 + deepmerge: ^4.2.2 + jest: ^27.5.1 + ts-jest: ^27.1.4 + typedoc: ^0.24.8 + typedoc-plugin-missing-exports: ^2.0.0 + typescript: ~4.8.4 + languageName: unknown + linkType: soft + "@ngraveio/bc-ur@npm:^1.1.5": version: 1.1.6 resolution: "@ngraveio/bc-ur@npm:1.1.6" @@ -6746,6 +6852,17 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:^4.3.4": + version: 4.3.6 + resolution: "fast-xml-parser@npm:4.3.6" + dependencies: + strnum: ^1.0.5 + bin: + fxparser: src/cli/cli.js + checksum: 12795c55f4564699c3cee13f7e892423244ac1125775e9b85bf948a1d4b65352da8f688d334bad530972288bb7ee0cf3d2605088d475123fce40d95003f045fa + languageName: node + linkType: hard + "fastq@npm:^1.6.0": version: 1.15.0 resolution: "fastq@npm:1.15.0" @@ -11738,6 +11855,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:~4.8.4": + version: 4.8.4 + resolution: "typescript@npm:4.8.4" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 3e4f061658e0c8f36c820802fa809e0fd812b85687a9a2f5430bc3d0368e37d1c9605c3ce9b39df9a05af2ece67b1d844f9f6ea8ff42819f13bcb80f85629af0 + languageName: node + linkType: hard + "typescript@npm:~4.9.5": version: 4.9.5 resolution: "typescript@npm:4.9.5" @@ -11748,6 +11875,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@~4.8.4#~builtin": + version: 4.8.4 + resolution: "typescript@patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=0102e9" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 301459fc3eb3b1a38fe91bf96d98eb55da88a9cb17b4ef80b4d105d620f4d547ba776cc27b44cc2ef58b66eda23fe0a74142feb5e79a6fb99f54fc018a696afa + languageName: node + linkType: hard + "typescript@patch:typescript@~4.9.5#~builtin": version: 4.9.5 resolution: "typescript@patch:typescript@npm%3A4.9.5#~builtin::version=4.9.5&hash=d73830" @@ -11871,7 +12008,7 @@ __metadata: languageName: node linkType: hard -"uuid@npm:^9.0.0": +"uuid@npm:^9.0.0, uuid@npm:^9.0.1": version: 9.0.1 resolution: "uuid@npm:9.0.1" bin: