From 55b6636b078ac381cc096b4f6eba08702159788d Mon Sep 17 00:00:00 2001 From: OGPoyraz Date: Tue, 26 Sep 2023 14:20:51 +0200 Subject: [PATCH 01/14] Release 78.0.0 (#1708) See change logs --------- Co-authored-by: Alex Donesky Co-authored-by: Jiexi Luan Co-authored-by: Mark Stacey --- package.json | 2 +- packages/accounts-controller/CHANGELOG.md | 7 +- packages/accounts-controller/package.json | 6 +- packages/address-book-controller/CHANGELOG.md | 9 ++- packages/address-book-controller/package.json | 4 +- packages/assets-controllers/CHANGELOG.md | 30 ++++++- packages/assets-controllers/package.json | 12 +-- packages/controller-utils/CHANGELOG.md | 11 ++- packages/controller-utils/package.json | 2 +- packages/ens-controller/CHANGELOG.md | 9 ++- packages/ens-controller/package.json | 8 +- packages/gas-fee-controller/CHANGELOG.md | 8 +- packages/gas-fee-controller/package.json | 8 +- packages/keyring-controller/CHANGELOG.md | 20 ++++- packages/keyring-controller/package.json | 8 +- packages/logging-controller/CHANGELOG.md | 7 +- packages/logging-controller/package.json | 4 +- packages/message-manager/CHANGELOG.md | 7 +- packages/message-manager/package.json | 4 +- packages/network-controller/CHANGELOG.md | 11 ++- packages/network-controller/package.json | 4 +- packages/permission-controller/CHANGELOG.md | 7 +- packages/permission-controller/package.json | 2 +- packages/phishing-controller/CHANGELOG.md | 9 ++- packages/phishing-controller/package.json | 4 +- packages/preferences-controller/CHANGELOG.md | 9 ++- packages/preferences-controller/package.json | 4 +- .../selected-network-controller/CHANGELOG.md | 7 +- .../selected-network-controller/package.json | 6 +- packages/signature-controller/CHANGELOG.md | 11 ++- packages/signature-controller/package.json | 12 +-- packages/transaction-controller/CHANGELOG.md | 28 ++++++- packages/transaction-controller/package.json | 8 +- yarn.lock | 78 +++++++++---------- 34 files changed, 253 insertions(+), 113 deletions(-) diff --git a/package.json b/package.json index 5cb8011f7cb..27d57c51f3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/core-monorepo", - "version": "77.0.0", + "version": "78.0.0", "private": true, "description": "Monorepo for packages shared between MetaMask clients", "repository": { diff --git a/packages/accounts-controller/CHANGELOG.md b/packages/accounts-controller/CHANGELOG.md index d88d084412f..504571a4496 100644 --- a/packages/accounts-controller/CHANGELOG.md +++ b/packages/accounts-controller/CHANGELOG.md @@ -6,9 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.0] +### Changed +- **BREAKING:** Bump peer dependency on `@metamask/keyring-controller` to ^8.0.0 + ## [1.0.0] ### Added - Initial release ([#1637](https://github.com/MetaMask/core/pull/1637)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@1.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@2.0.0...HEAD +[2.0.0]: https://github.com/MetaMask/core/compare/@metamask/accounts-controller@1.0.0...@metamask/accounts-controller@2.0.0 [1.0.0]: https://github.com/MetaMask/core/releases/tag/@metamask/accounts-controller@1.0.0 diff --git a/packages/accounts-controller/package.json b/packages/accounts-controller/package.json index 8a624305921..087f75c198c 100644 --- a/packages/accounts-controller/package.json +++ b/packages/accounts-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/accounts-controller", - "version": "1.0.0", + "version": "2.0.0", "description": "Manages internal accounts", "keywords": [ "MetaMask", @@ -42,7 +42,7 @@ }, "devDependencies": { "@metamask/auto-changelog": "^3.1.0", - "@metamask/keyring-controller": "^7.5.0", + "@metamask/keyring-controller": "^8.0.0", "@metamask/snaps-controllers": "^1.0.1", "@types/jest": "^27.4.1", "@types/readable-stream": "^2.3.0", @@ -53,7 +53,7 @@ "typescript": "~4.6.3" }, "peerDependencies": { - "@metamask/keyring-controller": "^7.5.0" + "@metamask/keyring-controller": "^8.0.0" }, "engines": { "node": ">=16.0.0" diff --git a/packages/address-book-controller/CHANGELOG.md b/packages/address-book-controller/CHANGELOG.md index afa2822fec6..6fa4af9722b 100644 --- a/packages/address-book-controller/CHANGELOG.md +++ b/packages/address-book-controller/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.1.2] +### Changed +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 + ## [3.1.1] ### Changed - Bump dependency on `@metamask/base-controller` to ^3.2.1 @@ -32,7 +36,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Add optional `addressType` property to address book entries ([#828](https://github.com/MetaMask/controllers/pull/828), [#1068](https://github.com/MetaMask/core/pull/1068)) - Rename this repository to `core` ([#1031](https://github.com/MetaMask/controllers/pull/1031)) -- Update `@metamask/controller-utils` package ([#1041](https://github.com/MetaMask/controllers/pull/1041)) +- Update `@metamask/controller-utils` package ([#1041](https://github.com/MetaMask/controllers/pull/1041)) ## [1.0.1] ### Changed @@ -47,7 +51,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/address-book-controller@3.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/address-book-controller@3.1.2...HEAD +[3.1.2]: https://github.com/MetaMask/core/compare/@metamask/address-book-controller@3.1.1...@metamask/address-book-controller@3.1.2 [3.1.1]: https://github.com/MetaMask/core/compare/@metamask/address-book-controller@3.1.0...@metamask/address-book-controller@3.1.1 [3.1.0]: https://github.com/MetaMask/core/compare/@metamask/address-book-controller@3.0.0...@metamask/address-book-controller@3.1.0 [3.0.0]: https://github.com/MetaMask/core/compare/@metamask/address-book-controller@2.0.0...@metamask/address-book-controller@3.0.0 diff --git a/packages/address-book-controller/package.json b/packages/address-book-controller/package.json index 396d11810a5..bf1987aef18 100644 --- a/packages/address-book-controller/package.json +++ b/packages/address-book-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/address-book-controller", - "version": "3.1.1", + "version": "3.1.2", "description": "Manages a list of recipient addresses associated with nicknames", "keywords": [ "MetaMask", @@ -29,7 +29,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@metamask/utils": "^6.2.0" }, "devDependencies": { diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 4dcb8bf9f4f..103fa7ed375 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -6,6 +6,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [13.0.0] +### Changed +- **BREAKING**: `TokensController` now expects `getNetworkClientById` in constructor options ([#1676](https://github.com/MetaMask/core/pull/1676)) +- **BREAKING**: `TokensController.addToken` now accepts a single options object ([#1676](https://github.com/MetaMask/core/pull/1676)) + ``` + { + address: string; + symbol: string; + decimals: number; + name?: string; + image?: string; + interactingAddress?: string; + networkClientId?: NetworkClientId; + } + ``` +- **BREAKING**: Bump peer dependency on `@metamask/network-controller` to ^13.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) +- **CHANGED**: `TokensController.addToken` will use the chain ID value derived from state for `networkClientId` if provided ([#1676](https://github.com/MetaMask/core/pull/1676)) +- **CHANGED**: `TokensController.addTokens` now accepts an optional `networkClientId` as the last parameter ([#1676](https://github.com/MetaMask/core/pull/1676)) +- **CHANGED**: `TokensController.addTokens` will use the chain ID value derived from state for `networkClientId` if provided ([#1676](https://github.com/MetaMask/core/pull/1676)) +- **CHANGED**: `TokensController.watchAsset` options now accepts optional `networkClientId` which is used to get the ERC-20 token name if provided ([#1676](https://github.com/MetaMask/core/pull/1676)) +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Bump dependency on `@metamask/preferences-controller` to ^4.4.1 ([#1676](https://github.com/MetaMask/core/pull/1676)) + ## [12.0.0] ### Added - Add `AssetsContractController` methods `getProvider`, `getChainId`, `getERC721Standard`, and `getERC1155Standard` ([#1638](https://github.com/MetaMask/core/pull/1638)) @@ -78,7 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - The `getERC20TokenName` method is used to get the token name for tokens added via `wallet_watchAsset` - The `onTokenListStateChange` method is used to trigger a name update when the token list changes. On each change, token names are copied from the token list if they're missing from token controller state. - **BREAKING:** The signature of the tokens controller method `addToken` has changed - - The fourth and fifth positional parameters (`image` and `interactingAddress`) have been replaced by an `options` object + - The fourth and fifth positional parameters (`image` and `interactingAddress`) have been replaced by an `options` object - The new options parameter includes the `image` and `interactingAddress` properties, and a new `name` property - The token detection controller now sets the token name when new tokens are detected ([#1127](https://github.com/MetaMask/core/pull/1127)) - The `Token` type now includes an optional `name` field ([#1127](https://github.com/MetaMask/core/pull/1127)) @@ -157,7 +180,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Remove the `networkType` configuration option from the NFT detection controller, NFT controller, and tokens controller ([#1360](https://github.com/MetaMask/core/pull/1360), [#1359](https://github.com/MetaMask/core/pull/1359)) - **BREAKING:** Remove the `SuggestedAssetMeta` and `SuggestedAssetMetaBase` types from the token controller ([#1268](https://github.com/MetaMask/core/pull/1268)) - **BREAKING:** Remove the `acceptWatchAsset` and `rejectWatchAsset` methods from the token controller ([#1268](https://github.com/MetaMask/core/pull/1268)) - - Suggested assets can be accepted or rejected using the approval controller instead + - Suggested assets can be accepted or rejected using the approval controller instead ## [7.0.0] ### Changed @@ -248,7 +271,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Use Ethers for AssetsContractController ([#845](https://github.com/MetaMask/core/pull/845)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@12.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@13.0.0...HEAD +[13.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@12.0.0...@metamask/assets-controllers@13.0.0 [12.0.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@11.1.0...@metamask/assets-controllers@12.0.0 [11.1.0]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@11.0.1...@metamask/assets-controllers@11.1.0 [11.0.1]: https://github.com/MetaMask/core/compare/@metamask/assets-controllers@11.0.0...@metamask/assets-controllers@11.0.1 diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 27f37b2f781..f8290b56b5c 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/assets-controllers", - "version": "12.0.0", + "version": "13.0.0", "description": "Controllers which manage interactions involving ERC-20, ERC-721, and ERC-1155 tokens (including NFTs)", "keywords": [ "MetaMask", @@ -36,11 +36,11 @@ "@metamask/approval-controller": "^3.5.1", "@metamask/base-controller": "^3.2.1", "@metamask/contract-metadata": "^2.3.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@metamask/eth-query": "^3.0.1", "@metamask/metamask-eth-abis": "3.0.0", - "@metamask/network-controller": "^12.2.0", - "@metamask/preferences-controller": "^4.4.0", + "@metamask/network-controller": "^13.0.0", + "@metamask/preferences-controller": "^4.4.1", "@metamask/rpc-errors": "^5.1.1", "@metamask/utils": "^6.2.0", "@types/uuid": "^8.3.0", @@ -69,8 +69,8 @@ }, "peerDependencies": { "@metamask/approval-controller": "^3.5.1", - "@metamask/network-controller": "^12.2.0", - "@metamask/preferences-controller": "^4.4.0" + "@metamask/network-controller": "^13.0.0", + "@metamask/preferences-controller": "^4.4.1" }, "engines": { "node": ">=16.0.0" diff --git a/packages/controller-utils/CHANGELOG.md b/packages/controller-utils/CHANGELOG.md index 865a9f96e0e..cbc4b9d7a6c 100644 --- a/packages/controller-utils/CHANGELOG.md +++ b/packages/controller-utils/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.0.0] +### Changed +- **BREAKING**: Rename `NETWORK_ID_TO_ETHERS_NETWORK_NAME_MAP` to `CHAIN_ID_TO_ETHERS_NETWORK_NAME_MAP` ([#1633](https://github.com/MetaMask/core/pull/1633)) + - Change it to a map of `Hex` chain ID to `BuiltInNetworkName` + +### Removed +- **BREAKING**: Remove `NetworkId` constant and type ([#1633](https://github.com/MetaMask/core/pull/1633)) + ## [4.3.2] ### Changed - There are no consumer-facing changes to this package. This version is a part of a synchronized release across all packages in our monorepo. @@ -147,7 +155,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/controller-utils@4.3.2...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/controller-utils@5.0.0...HEAD +[5.0.0]: https://github.com/MetaMask/core/compare/@metamask/controller-utils@4.3.2...@metamask/controller-utils@5.0.0 [4.3.2]: https://github.com/MetaMask/core/compare/@metamask/controller-utils@4.3.1...@metamask/controller-utils@4.3.2 [4.3.1]: https://github.com/MetaMask/core/compare/@metamask/controller-utils@4.3.0...@metamask/controller-utils@4.3.1 [4.3.0]: https://github.com/MetaMask/core/compare/@metamask/controller-utils@4.2.0...@metamask/controller-utils@4.3.0 diff --git a/packages/controller-utils/package.json b/packages/controller-utils/package.json index 7470b81709c..b64384bcf77 100644 --- a/packages/controller-utils/package.json +++ b/packages/controller-utils/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/controller-utils", - "version": "4.3.2", + "version": "5.0.0", "description": "Data and convenience functions shared by multiple packages", "keywords": [ "MetaMask", diff --git a/packages/ens-controller/CHANGELOG.md b/packages/ens-controller/CHANGELOG.md index e11a3136e03..f152f18b351 100644 --- a/packages/ens-controller/CHANGELOG.md +++ b/packages/ens-controller/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.0.0] +### Changed +- **BREAKING**: Bump peer dependency on `@metamask/network-controller` to ^13.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Use `providerConfig.chainId` instead of `providerConfig.networkId` to determine ENS compatability ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) + ## [4.1.1] ### Changed - Bump dependency on `@metamask/base-controller` to ^3.2.1 @@ -60,7 +66,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/ens-controller@4.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/ens-controller@5.0.0...HEAD +[5.0.0]: https://github.com/MetaMask/core/compare/@metamask/ens-controller@4.1.1...@metamask/ens-controller@5.0.0 [4.1.1]: https://github.com/MetaMask/core/compare/@metamask/ens-controller@4.1.0...@metamask/ens-controller@4.1.1 [4.1.0]: https://github.com/MetaMask/core/compare/@metamask/ens-controller@4.0.0...@metamask/ens-controller@4.1.0 [4.0.0]: https://github.com/MetaMask/core/compare/@metamask/ens-controller@3.1.0...@metamask/ens-controller@4.0.0 diff --git a/packages/ens-controller/package.json b/packages/ens-controller/package.json index eabd50b4553..28d9f4a986f 100644 --- a/packages/ens-controller/package.json +++ b/packages/ens-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/ens-controller", - "version": "4.1.1", + "version": "5.0.0", "description": "Maps ENS names to their resolved addresses by chain id", "keywords": [ "MetaMask", @@ -30,8 +30,8 @@ "dependencies": { "@ethersproject/providers": "^5.7.0", "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", - "@metamask/network-controller": "^12.2.0", + "@metamask/controller-utils": "^5.0.0", + "@metamask/network-controller": "^13.0.0", "@metamask/utils": "^6.2.0", "ethereum-ens-network-map": "^1.0.2", "punycode": "^2.1.1" @@ -47,7 +47,7 @@ "typescript": "~4.6.3" }, "peerDependencies": { - "@metamask/network-controller": "^12.2.0" + "@metamask/network-controller": "^13.0.0" }, "engines": { "node": ">=16.0.0" diff --git a/packages/gas-fee-controller/CHANGELOG.md b/packages/gas-fee-controller/CHANGELOG.md index 259e259304c..3a3708f1de6 100644 --- a/packages/gas-fee-controller/CHANGELOG.md +++ b/packages/gas-fee-controller/CHANGELOG.md @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [7.0.0] +### Changed +- **BREAKING**: Bump peer dependency on `@metamask/network-controller` to ^13.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) + ## [6.1.2] ### Changed - Bump dependency on `@metamask/base-controller` to ^3.2.1 @@ -77,7 +82,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller@6.1.2...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller@7.0.0...HEAD +[7.0.0]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller@6.1.2...@metamask/gas-fee-controller@7.0.0 [6.1.2]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller@6.1.1...@metamask/gas-fee-controller@6.1.2 [6.1.1]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller@6.1.0...@metamask/gas-fee-controller@6.1.1 [6.1.0]: https://github.com/MetaMask/core/compare/@metamask/gas-fee-controller@6.0.1...@metamask/gas-fee-controller@6.1.0 diff --git a/packages/gas-fee-controller/package.json b/packages/gas-fee-controller/package.json index fece04e1214..ebc364e88f6 100644 --- a/packages/gas-fee-controller/package.json +++ b/packages/gas-fee-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/gas-fee-controller", - "version": "6.1.2", + "version": "7.0.0", "description": "Periodically calculates gas fee estimates based on various gas limits as well as other data displayed on transaction confirm screens", "keywords": [ "MetaMask", @@ -29,9 +29,9 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@metamask/eth-query": "^3.0.1", - "@metamask/network-controller": "^12.2.0", + "@metamask/network-controller": "^13.0.0", "@metamask/utils": "^6.2.0", "@types/uuid": "^8.3.0", "ethereumjs-util": "^7.0.10", @@ -54,7 +54,7 @@ "typescript": "~4.6.3" }, "peerDependencies": { - "@metamask/network-controller": "^12.2.0" + "@metamask/network-controller": "^13.0.0" }, "engines": { "node": ">=16.0.0" diff --git a/packages/keyring-controller/CHANGELOG.md b/packages/keyring-controller/CHANGELOG.md index 0ea041ab2ac..5b7992cde3b 100644 --- a/packages/keyring-controller/CHANGELOG.md +++ b/packages/keyring-controller/CHANGELOG.md @@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [8.0.0] +### Added +- Add `getQRKeyring(): QRKeyring | undefined` method +- Add `KeyringController:qrKeyringStateChange` messenger event +- The event emits updates from the internal `QRKeyring` instance, if there's one + +### Changed +- **BREAKING:** addNewKeyring(type) return type changed from Promise> to Promise + - When calling with QRKeyring type the keyring instance is retrieved or created (no multiple QRKeyring instances possible) +- Bump dependency on `@metamask/message-manager` to ^7.3.3 +- Bump dependency on `@metamask/preferences-controller` to ^4.4.1 + +### Fixed +- Fix `addNewAccountForKeyring` for `CustodyKeyring` ([#1694](https://github.com/MetaMask/core/pull/1694)) + ## [7.5.0] ### Added - Add `KeyringController` messenger actions ([#1691](https://github.com/MetaMask/core/pull/1691)) @@ -20,7 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `KeyringController` messenger actions ([#1654](https://github.com/MetaMask/core/pull/1654)) - `KeyringController:signMessage` - - `KeyringController:signPersonalMessage` + - `KeyringController:signPersonalMessage` - `KeyringController:signTypedMessage` - `KeyringController:decryptMessage` - `KeyringController:getEncryptionPublicKey` @@ -181,7 +196,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@7.5.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@8.0.0...HEAD +[8.0.0]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@7.5.0...@metamask/keyring-controller@8.0.0 [7.5.0]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@7.4.0...@metamask/keyring-controller@7.5.0 [7.4.0]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@7.3.0...@metamask/keyring-controller@7.4.0 [7.3.0]: https://github.com/MetaMask/core/compare/@metamask/keyring-controller@7.2.0...@metamask/keyring-controller@7.3.0 diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 89131eae0e4..0db24435864 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/keyring-controller", - "version": "7.5.0", + "version": "8.0.0", "description": "Stores identities seen in the wallet and manages interactions such as signing", "keywords": [ "MetaMask", @@ -31,8 +31,8 @@ "@keystonehq/metamask-airgapped-keyring": "^0.13.1", "@metamask/base-controller": "^3.2.1", "@metamask/eth-keyring-controller": "^13.0.1", - "@metamask/message-manager": "^7.3.2", - "@metamask/preferences-controller": "^4.4.0", + "@metamask/message-manager": "^7.3.3", + "@metamask/preferences-controller": "^4.4.1", "@metamask/utils": "^6.2.0", "async-mutex": "^0.2.6", "ethereumjs-util": "^7.0.10", @@ -57,7 +57,7 @@ "uuid": "^8.3.2" }, "peerDependencies": { - "@metamask/preferences-controller": "^4.4.0" + "@metamask/preferences-controller": "^4.4.1" }, "engines": { "node": ">=16.0.0" diff --git a/packages/logging-controller/CHANGELOG.md b/packages/logging-controller/CHANGELOG.md index cde21f3cf17..fa98f02a60d 100644 --- a/packages/logging-controller/CHANGELOG.md +++ b/packages/logging-controller/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.2] +### Changed +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 + ## [1.0.1] ### Changed - Bump dependency on `@metamask/base-controller` to ^3.2.1 @@ -16,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial Release - Add logging controller ([#1089](https://github.com/MetaMask/core.git/pull/1089)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/logging-controller@1.0.1...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/logging-controller@1.0.2...HEAD +[1.0.2]: https://github.com/MetaMask/core/compare/@metamask/logging-controller@1.0.1...@metamask/logging-controller@1.0.2 [1.0.1]: https://github.com/MetaMask/core/compare/@metamask/logging-controller@1.0.0...@metamask/logging-controller@1.0.1 [1.0.0]: https://github.com/MetaMask/core/releases/tag/@metamask/logging-controller@1.0.0 diff --git a/packages/logging-controller/package.json b/packages/logging-controller/package.json index 4371043418e..b300c7fb4a0 100644 --- a/packages/logging-controller/package.json +++ b/packages/logging-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/logging-controller", - "version": "1.0.1", + "version": "1.0.2", "description": "Manages logging data to assist users and support staff", "keywords": [ "MetaMask", @@ -29,7 +29,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/packages/message-manager/CHANGELOG.md b/packages/message-manager/CHANGELOG.md index dc3dd7dc8c8..9d626514e6c 100644 --- a/packages/message-manager/CHANGELOG.md +++ b/packages/message-manager/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [7.3.3] +### Changed +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 + ## [7.3.2] ### Changed - Bump @metamask/eth-sig-util from 6.0.0 to 7.0.0 ([#1669](https://github.com/MetaMask/core/pull/1669)) @@ -109,7 +113,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/message-manager@7.3.2...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/message-manager@7.3.3...HEAD +[7.3.3]: https://github.com/MetaMask/core/compare/@metamask/message-manager@7.3.2...@metamask/message-manager@7.3.3 [7.3.2]: https://github.com/MetaMask/core/compare/@metamask/message-manager@7.3.1...@metamask/message-manager@7.3.2 [7.3.1]: https://github.com/MetaMask/core/compare/@metamask/message-manager@7.3.0...@metamask/message-manager@7.3.1 [7.3.0]: https://github.com/MetaMask/core/compare/@metamask/message-manager@7.2.0...@metamask/message-manager@7.3.0 diff --git a/packages/message-manager/package.json b/packages/message-manager/package.json index 52579a026aa..f854a4cf922 100644 --- a/packages/message-manager/package.json +++ b/packages/message-manager/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/message-manager", - "version": "7.3.2", + "version": "7.3.3", "description": "Stores and manages interactions with signing requests", "keywords": [ "MetaMask", @@ -29,7 +29,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@metamask/eth-sig-util": "^7.0.0", "@metamask/utils": "^6.2.0", "@types/uuid": "^8.3.0", diff --git a/packages/network-controller/CHANGELOG.md b/packages/network-controller/CHANGELOG.md index 70e8b2b5867..a244a7ff6e1 100644 --- a/packages/network-controller/CHANGELOG.md +++ b/packages/network-controller/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [13.0.0] +### Changed +- **BREAKING**: Remove `NetworkId` type ([#1633](https://github.com/MetaMask/core/pull/1633)) +- **BREAKING**: Remove `networkId` property from `NetworkState` type ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Update scaffold RPC middleware for built-in Infura networks to no longer resolve `net_version` locally ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Stop making `net_version` request to determine network status ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 + ## [12.2.0] ### Added - Add `NetworkController:getNetworkClientById` action ([#1638](https://github.com/MetaMask/core/pull/1638)) @@ -253,7 +261,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/network-controller@12.2.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/network-controller@13.0.0...HEAD +[13.0.0]: https://github.com/MetaMask/core/compare/@metamask/network-controller@12.2.0...@metamask/network-controller@13.0.0 [12.2.0]: https://github.com/MetaMask/core/compare/@metamask/network-controller@12.1.2...@metamask/network-controller@12.2.0 [12.1.2]: https://github.com/MetaMask/core/compare/@metamask/network-controller@12.1.1...@metamask/network-controller@12.1.2 [12.1.1]: https://github.com/MetaMask/core/compare/@metamask/network-controller@12.1.0...@metamask/network-controller@12.1.1 diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index 384647d46c0..1190ff6a789 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/network-controller", - "version": "12.2.0", + "version": "13.0.0", "description": "Provides an interface to the currently selected network via a MetaMask-compatible provider object", "keywords": [ "MetaMask", @@ -29,7 +29,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@metamask/eth-json-rpc-infura": "^8.1.1", "@metamask/eth-json-rpc-middleware": "^11.0.2", "@metamask/eth-json-rpc-provider": "^1.0.0", diff --git a/packages/permission-controller/CHANGELOG.md b/packages/permission-controller/CHANGELOG.md index d56114e70fa..7e11e7f4316 100644 --- a/packages/permission-controller/CHANGELOG.md +++ b/packages/permission-controller/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.1.2] +### Changed +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 + ## [4.1.1] ### Changed - Bump dependency and peer dependency on `@metamask/approval-controller` to ^3.5.1 @@ -79,7 +83,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@4.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@4.1.2...HEAD +[4.1.2]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@4.1.1...@metamask/permission-controller@4.1.2 [4.1.1]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@4.1.0...@metamask/permission-controller@4.1.1 [4.1.0]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@4.0.1...@metamask/permission-controller@4.1.0 [4.0.1]: https://github.com/MetaMask/core/compare/@metamask/permission-controller@4.0.0...@metamask/permission-controller@4.0.1 diff --git a/packages/permission-controller/package.json b/packages/permission-controller/package.json index 7225f0ea527..58a8e9ea99f 100644 --- a/packages/permission-controller/package.json +++ b/packages/permission-controller/package.json @@ -30,7 +30,7 @@ "dependencies": { "@metamask/approval-controller": "^3.5.1", "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@metamask/utils": "^6.2.0", "@types/deep-freeze-strict": "^1.1.0", "deep-freeze-strict": "^1.1.1", diff --git a/packages/phishing-controller/CHANGELOG.md b/packages/phishing-controller/CHANGELOG.md index 8fe039a422e..6af40c68856 100644 --- a/packages/phishing-controller/CHANGELOG.md +++ b/packages/phishing-controller/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.0.2] +### Changed +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 + ## [6.0.1] ### Changed - Bump dependency on `@metamask/base-controller` to ^3.2.1 @@ -61,7 +65,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.1.1] ### Changed - Rename this repository to `core` ([#1031](https://github.com/MetaMask/controllers/pull/1031)) -- Update `@metamask/controller-utils` package ([#1041](https://github.com/MetaMask/controllers/pull/1041)) +- Update `@metamask/controller-utils` package ([#1041](https://github.com/MetaMask/controllers/pull/1041)) ## [1.1.0] ### Added @@ -80,7 +84,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@6.0.1...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@6.0.2...HEAD +[6.0.2]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@6.0.1...@metamask/phishing-controller@6.0.2 [6.0.1]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@6.0.0...@metamask/phishing-controller@6.0.1 [6.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@5.0.0...@metamask/phishing-controller@6.0.0 [5.0.0]: https://github.com/MetaMask/core/compare/@metamask/phishing-controller@4.0.0...@metamask/phishing-controller@5.0.0 diff --git a/packages/phishing-controller/package.json b/packages/phishing-controller/package.json index 96bc5e5ffe1..0561fe8d4db 100644 --- a/packages/phishing-controller/package.json +++ b/packages/phishing-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/phishing-controller", - "version": "6.0.1", + "version": "6.0.2", "description": "Maintains a periodically updated list of approved and unapproved website origins", "keywords": [ "MetaMask", @@ -29,7 +29,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@types/punycode": "^2.1.0", "eth-phishing-detect": "^1.2.0", "punycode": "^2.1.1" diff --git a/packages/preferences-controller/CHANGELOG.md b/packages/preferences-controller/CHANGELOG.md index a8cad049d94..2f246bf4bea 100644 --- a/packages/preferences-controller/CHANGELOG.md +++ b/packages/preferences-controller/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.4.1] +### Changed +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 + ## [4.4.0] ### Added - Add `isIpfsGatewayEnabled` property to PreferencesController state ([#1577](https://github.com/MetaMask/core/pull/1577)) @@ -48,7 +52,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.2] ### Changed - Rename this repository to `core` ([#1031](https://github.com/MetaMask/controllers/pull/1031)) -- Update `@metamask/controller-utils` package ([#1041](https://github.com/MetaMask/controllers/pull/1041)) +- Update `@metamask/controller-utils` package ([#1041](https://github.com/MetaMask/controllers/pull/1041)) ## [1.0.1] ### Changed @@ -63,7 +67,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/preferences-controller@4.4.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/preferences-controller@4.4.1...HEAD +[4.4.1]: https://github.com/MetaMask/core/compare/@metamask/preferences-controller@4.4.0...@metamask/preferences-controller@4.4.1 [4.4.0]: https://github.com/MetaMask/core/compare/@metamask/preferences-controller@4.3.0...@metamask/preferences-controller@4.4.0 [4.3.0]: https://github.com/MetaMask/core/compare/@metamask/preferences-controller@4.2.0...@metamask/preferences-controller@4.3.0 [4.2.0]: https://github.com/MetaMask/core/compare/@metamask/preferences-controller@4.1.0...@metamask/preferences-controller@4.2.0 diff --git a/packages/preferences-controller/package.json b/packages/preferences-controller/package.json index 578a923c10d..59a01865080 100644 --- a/packages/preferences-controller/package.json +++ b/packages/preferences-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/preferences-controller", - "version": "4.4.0", + "version": "4.4.1", "description": "Manages user-configurable settings for MetaMask", "keywords": [ "MetaMask", @@ -29,7 +29,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2" + "@metamask/controller-utils": "^5.0.0" }, "devDependencies": { "@metamask/auto-changelog": "^3.1.0", diff --git a/packages/selected-network-controller/CHANGELOG.md b/packages/selected-network-controller/CHANGELOG.md index dc454350299..1e8b0225edd 100644 --- a/packages/selected-network-controller/CHANGELOG.md +++ b/packages/selected-network-controller/CHANGELOG.md @@ -6,9 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.0.0] +### Changed +- **BREAKING**: Bump peer dependency on `@metamask/network-controller` to ^13.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) + ## [1.0.0] ### Added - Initial Release ([#1643](https://github.com/MetaMask/core/pull/1643)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/selected-network-controller@1.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/selected-network-controller@2.0.0...HEAD +[2.0.0]: https://github.com/MetaMask/core/compare/@metamask/selected-network-controller@1.0.0...@metamask/selected-network-controller@2.0.0 [1.0.0]: https://github.com/MetaMask/core/releases/tag/@metamask/selected-network-controller@1.0.0 diff --git a/packages/selected-network-controller/package.json b/packages/selected-network-controller/package.json index e46b9dd7250..b902bbdd0fa 100644 --- a/packages/selected-network-controller/package.json +++ b/packages/selected-network-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/selected-network-controller", - "version": "1.0.0", + "version": "2.0.0", "description": "Provides an interface to the currently selected networkClientId for a given domain", "keywords": [ "MetaMask", @@ -29,7 +29,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/network-controller": "^12.2.0", + "@metamask/network-controller": "^13.0.0", "json-rpc-engine": "^6.1.0" }, "devDependencies": { @@ -47,7 +47,7 @@ "typescript": "~4.6.3" }, "peerDependencies": { - "@metamask/network-controller": "^12.2.0" + "@metamask/network-controller": "^13.0.0" }, "engines": { "node": ">=16.0.0" diff --git a/packages/signature-controller/CHANGELOG.md b/packages/signature-controller/CHANGELOG.md index 3ae7f84e7de..9879eb8cc09 100644 --- a/packages/signature-controller/CHANGELOG.md +++ b/packages/signature-controller/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [6.1.0] +### Changed +- Add `LoggingController` logs on signature operation stages ([#1692](https://github.com/MetaMask/core/pull/1692)) +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 +- Bump dependency on `@metamask/keyring-controller` to ^8.0.0 +- Bump dependency on `@metamask/logging-controller` to ^1.0.2 +- Bump dependency on `@metamask/message-manager` to ^7.3.3 + ## [6.0.0] ### Changed - **BREAKING**: Removed `keyringController` property from constructor option ([#1593](https://github.com/MetaMask/core/pull/1593)) @@ -70,7 +78,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release ([#1214](https://github.com/MetaMask/core/pull/1214)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/signature-controller@6.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/signature-controller@6.1.0...HEAD +[6.1.0]: https://github.com/MetaMask/core/compare/@metamask/signature-controller@6.0.0...@metamask/signature-controller@6.1.0 [6.0.0]: https://github.com/MetaMask/core/compare/@metamask/signature-controller@5.3.1...@metamask/signature-controller@6.0.0 [5.3.1]: https://github.com/MetaMask/core/compare/@metamask/signature-controller@5.3.0...@metamask/signature-controller@5.3.1 [5.3.0]: https://github.com/MetaMask/core/compare/@metamask/signature-controller@5.2.0...@metamask/signature-controller@5.3.0 diff --git a/packages/signature-controller/package.json b/packages/signature-controller/package.json index 4104ae7cc55..3f070fe4a3a 100644 --- a/packages/signature-controller/package.json +++ b/packages/signature-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/signature-controller", - "version": "6.0.0", + "version": "6.1.0", "description": "Processes signing requests in order to sign arbitrary and typed data", "keywords": [ "MetaMask", @@ -30,9 +30,9 @@ "dependencies": { "@metamask/approval-controller": "^3.5.1", "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", - "@metamask/logging-controller": "^1.0.1", - "@metamask/message-manager": "^7.3.2", + "@metamask/controller-utils": "^5.0.0", + "@metamask/logging-controller": "^1.0.2", + "@metamask/message-manager": "^7.3.3", "@metamask/utils": "^6.2.0", "eth-rpc-errors": "^4.0.2", "ethereumjs-util": "^7.0.10", @@ -41,7 +41,7 @@ }, "devDependencies": { "@metamask/auto-changelog": "^3.1.0", - "@metamask/keyring-controller": "^7.5.0", + "@metamask/keyring-controller": "^8.0.0", "@types/jest": "^27.4.1", "deepmerge": "^4.2.2", "jest": "^27.5.1", @@ -52,7 +52,7 @@ }, "peerDependencies": { "@metamask/approval-controller": "^3.5.1", - "@metamask/logging-controller": "^1.0.1" + "@metamask/logging-controller": "^1.0.2" }, "engines": { "node": ">=16.0.0" diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 9b59c07c434..0f63f6265ba 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -6,6 +6,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [12.0.0] +### Changed +- **BREAKING**: Use only `chainId` to determine if a transaction belongs to the current network ([#1633](https://github.com/MetaMask/core/pull/1633)) + - No longer uses `networkID` as a fallback if `chainId` is missing +- **BREAKING**: Change `TransactionMeta.chainId` to be required ([#1633](https://github.com/MetaMask/core/pull/1633)) +- **BREAKING**: Bump peer dependency on `@metamask/network-controller` to ^13.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Update `TransactionMeta.networkID` as deprecated ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Change `TransactionMeta.networkID` to be readonly ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Bump dependency on `@metamask/controller-utils` to ^5.0.0 ([#1633](https://github.com/MetaMask/core/pull/1633)) + +### Removed +- Remove `networkId` param from `RemoteTransactionSource.isSupportedNetwork()` interface ([#1633](https://github.com/MetaMask/core/pull/1633)) +- Remove `currentNetworkId` property from `RemoteTransactionSourceRequest` ([#1633](https://github.com/MetaMask/core/pull/1633)) + +## [11.1.0] +### Added +- Add `type` property to the transaction metadata ([#1670](https://github.com/MetaMask/core/pull/1670)) + ## [11.0.0] ### Added - Add optional `getLastBlockVariations` method to `RemoteTransactionSource` type ([#1668](https://github.com/MetaMask/core/pull/1668)) @@ -70,7 +88,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add incoming transaction methods ([#1579](https://github.com/MetaMask/core/pull/1579)) - `startIncomingTransactionPolling` - `stopIncomingTransactionPolling` - - `updateIncomingTransactions` + - `updateIncomingTransactions` - Add `requireApproval` option to `addTransaction` method options ([#1580](https://github.com/MetaMask/core/pull/1580)) - Add `address` argument to `wipeTransactions` method ([#1573](https://github.com/MetaMask/core/pull/1573)) @@ -110,7 +128,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [7.0.0] ### Changed - **BREAKING**: Change the approveTransaction and cancelTransaction methods to private ([#1435](https://github.com/MetaMask/core/pull/1435)) - - Consumers should migrate from use of these methods to use of `processApproval`. + - Consumers should migrate from use of these methods to use of `processApproval`. - Update the TransactionController to await the approval request promise before automatically performing the relevant logic, either signing and submitting the transaction, or cancelling it ([#1435](https://github.com/MetaMask/core/pull/1435)) ## [6.1.0] @@ -123,7 +141,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Update transaction controller to automatically initiate, finalize, and cancel approval requests as transactions move through states ([#1241](https://github.com/MetaMask/core/pull/1241)) - The `ApprovalController:addRequest` action will be called when a new transaction is initiated - The `ApprovalController:rejectRequest` action will be called if a transaction fails - - The `ApprovalController:acceptRequest` action will be called when a transaction is approved + - The `ApprovalController:acceptRequest` action will be called when a transaction is approved ### Changed - **BREAKING:** Bump to Node 16 ([#1262](https://github.com/MetaMask/core/pull/1262)) @@ -185,7 +203,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 All changes listed after this point were applied to this package following the monorepo conversion. -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-controller@11.0.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-controller@12.0.0...HEAD +[12.0.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-controller@11.1.0...@metamask/transaction-controller@12.0.0 +[11.1.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-controller@11.0.0...@metamask/transaction-controller@11.1.0 [11.0.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-controller@10.0.0...@metamask/transaction-controller@11.0.0 [10.0.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-controller@9.2.0...@metamask/transaction-controller@10.0.0 [9.2.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-controller@9.1.0...@metamask/transaction-controller@9.2.0 diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 9e49ebdba5d..376984c26df 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/transaction-controller", - "version": "11.0.0", + "version": "12.0.0", "description": "Stores transactions alongside their periodically updated statuses and manages interactions such as approval and cancellation", "keywords": [ "MetaMask", @@ -33,10 +33,10 @@ "@ethersproject/abi": "^5.7.0", "@metamask/approval-controller": "^3.5.1", "@metamask/base-controller": "^3.2.1", - "@metamask/controller-utils": "^4.3.2", + "@metamask/controller-utils": "^5.0.0", "@metamask/eth-query": "^3.0.1", "@metamask/metamask-eth-abis": "^3.0.0", - "@metamask/network-controller": "^12.2.0", + "@metamask/network-controller": "^13.0.0", "@metamask/utils": "^6.2.0", "async-mutex": "^0.2.6", "eth-method-registry": "1.1.0", @@ -63,7 +63,7 @@ }, "peerDependencies": { "@metamask/approval-controller": "^3.5.1", - "@metamask/network-controller": "^12.2.0", + "@metamask/network-controller": "^13.0.0", "babel-runtime": "^6.26.0" }, "engines": { diff --git a/yarn.lock b/yarn.lock index f303cca1af0..f3dd9ca3583 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1295,7 +1295,7 @@ __metadata: "@metamask/base-controller": ^3.2.1 "@metamask/eth-snap-keyring": ^0.2.2 "@metamask/keyring-api": ^0.2.5 - "@metamask/keyring-controller": ^7.5.0 + "@metamask/keyring-controller": ^8.0.0 "@metamask/snaps-controllers": ^1.0.1 "@metamask/snaps-utils": ^1.0.1 "@metamask/utils": ^6.2.0 @@ -1313,7 +1313,7 @@ __metadata: typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: - "@metamask/keyring-controller": ^7.5.0 + "@metamask/keyring-controller": ^8.0.0 languageName: unknown linkType: soft @@ -1334,7 +1334,7 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 deepmerge: ^4.2.2 @@ -1397,11 +1397,11 @@ __metadata: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 "@metamask/contract-metadata": ^2.3.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@metamask/eth-query": ^3.0.1 "@metamask/metamask-eth-abis": 3.0.0 - "@metamask/network-controller": ^12.2.0 - "@metamask/preferences-controller": ^4.4.0 + "@metamask/network-controller": ^13.0.0 + "@metamask/preferences-controller": ^4.4.1 "@metamask/rpc-errors": ^5.1.1 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 @@ -1426,8 +1426,8 @@ __metadata: uuid: ^8.3.2 peerDependencies: "@metamask/approval-controller": ^3.5.1 - "@metamask/network-controller": ^12.2.0 - "@metamask/preferences-controller": ^4.4.0 + "@metamask/network-controller": ^13.0.0 + "@metamask/preferences-controller": ^4.4.1 languageName: unknown linkType: soft @@ -1496,7 +1496,7 @@ __metadata: languageName: node linkType: hard -"@metamask/controller-utils@^4.3.2, @metamask/controller-utils@workspace:packages/controller-utils": +"@metamask/controller-utils@^5.0.0, @metamask/controller-utils@workspace:packages/controller-utils": version: 0.0.0-use.local resolution: "@metamask/controller-utils@workspace:packages/controller-utils" dependencies: @@ -1586,8 +1586,8 @@ __metadata: "@ethersproject/providers": ^5.7.0 "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 - "@metamask/network-controller": ^12.2.0 + "@metamask/controller-utils": ^5.0.0 + "@metamask/network-controller": ^13.0.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 deepmerge: ^4.2.2 @@ -1599,7 +1599,7 @@ __metadata: typedoc-plugin-missing-exports: ^0.22.6 typescript: ~4.6.3 peerDependencies: - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 languageName: unknown linkType: soft @@ -1807,9 +1807,9 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@metamask/eth-query": ^3.0.1 - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 "@types/jest-when": ^2.7.3 @@ -1828,7 +1828,7 @@ __metadata: typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 languageName: unknown linkType: soft @@ -1887,7 +1887,7 @@ __metadata: languageName: node linkType: hard -"@metamask/keyring-controller@^7.5.0, @metamask/keyring-controller@workspace:packages/keyring-controller": +"@metamask/keyring-controller@^8.0.0, @metamask/keyring-controller@workspace:packages/keyring-controller": version: 0.0.0-use.local resolution: "@metamask/keyring-controller@workspace:packages/keyring-controller" dependencies: @@ -1899,8 +1899,8 @@ __metadata: "@metamask/base-controller": ^3.2.1 "@metamask/eth-keyring-controller": ^13.0.1 "@metamask/eth-sig-util": ^7.0.0 - "@metamask/message-manager": ^7.3.2 - "@metamask/preferences-controller": ^4.4.0 + "@metamask/message-manager": ^7.3.3 + "@metamask/preferences-controller": ^4.4.1 "@metamask/scure-bip39": ^2.1.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 @@ -1917,17 +1917,17 @@ __metadata: typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: - "@metamask/preferences-controller": ^4.4.0 + "@metamask/preferences-controller": ^4.4.1 languageName: unknown linkType: soft -"@metamask/logging-controller@^1.0.1, @metamask/logging-controller@workspace:packages/logging-controller": +"@metamask/logging-controller@^1.0.2, @metamask/logging-controller@workspace:packages/logging-controller": version: 0.0.0-use.local resolution: "@metamask/logging-controller@workspace:packages/logging-controller" dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@types/jest": ^27.4.1 deepmerge: ^4.2.2 jest: ^27.5.1 @@ -1939,13 +1939,13 @@ __metadata: languageName: unknown linkType: soft -"@metamask/message-manager@^7.3.2, @metamask/message-manager@workspace:packages/message-manager": +"@metamask/message-manager@^7.3.3, @metamask/message-manager@workspace:packages/message-manager": version: 0.0.0-use.local resolution: "@metamask/message-manager@workspace:packages/message-manager" dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@metamask/eth-sig-util": ^7.0.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 @@ -1986,14 +1986,14 @@ __metadata: languageName: unknown linkType: soft -"@metamask/network-controller@^12.2.0, @metamask/network-controller@workspace:packages/network-controller": +"@metamask/network-controller@^13.0.0, @metamask/network-controller@workspace:packages/network-controller": version: 0.0.0-use.local resolution: "@metamask/network-controller@workspace:packages/network-controller" dependencies: "@json-rpc-specification/meta-schema": ^1.0.6 "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@metamask/eth-json-rpc-infura": ^8.1.1 "@metamask/eth-json-rpc-middleware": ^11.0.2 "@metamask/eth-json-rpc-provider": ^1.0.0 @@ -2079,7 +2079,7 @@ __metadata: "@metamask/approval-controller": ^3.5.1 "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@metamask/utils": ^6.2.0 "@types/deep-freeze-strict": ^1.1.0 "@types/jest": ^27.4.1 @@ -2105,7 +2105,7 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@types/jest": ^27.4.1 "@types/punycode": ^2.1.0 deepmerge: ^4.2.2 @@ -2131,13 +2131,13 @@ __metadata: languageName: node linkType: hard -"@metamask/preferences-controller@^4.4.0, @metamask/preferences-controller@workspace:packages/preferences-controller": +"@metamask/preferences-controller@^4.4.1, @metamask/preferences-controller@workspace:packages/preferences-controller": version: 0.0.0-use.local resolution: "@metamask/preferences-controller@workspace:packages/preferences-controller" dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@types/jest": ^27.4.1 deepmerge: ^4.2.2 jest: ^27.5.1 @@ -2310,7 +2310,7 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 "@types/jest": ^27.4.1 deepmerge: ^4.2.2 immer: ^9.0.6 @@ -2324,7 +2324,7 @@ __metadata: typedoc-plugin-missing-exports: ^0.22.6 typescript: ~4.6.3 peerDependencies: - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 languageName: unknown linkType: soft @@ -2335,10 +2335,10 @@ __metadata: "@metamask/approval-controller": ^3.5.1 "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 - "@metamask/keyring-controller": ^7.5.0 - "@metamask/logging-controller": ^1.0.1 - "@metamask/message-manager": ^7.3.2 + "@metamask/controller-utils": ^5.0.0 + "@metamask/keyring-controller": ^8.0.0 + "@metamask/logging-controller": ^1.0.2 + "@metamask/message-manager": ^7.3.3 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 deepmerge: ^4.2.2 @@ -2353,7 +2353,7 @@ __metadata: typescript: ~4.6.3 peerDependencies: "@metamask/approval-controller": ^3.5.1 - "@metamask/logging-controller": ^1.0.1 + "@metamask/logging-controller": ^1.0.2 languageName: unknown linkType: soft @@ -2560,10 +2560,10 @@ __metadata: "@metamask/approval-controller": ^3.5.1 "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/controller-utils": ^4.3.2 + "@metamask/controller-utils": ^5.0.0 "@metamask/eth-query": ^3.0.1 "@metamask/metamask-eth-abis": ^3.0.0 - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 "@types/node": ^16.18.24 @@ -2586,7 +2586,7 @@ __metadata: uuid: ^8.3.2 peerDependencies: "@metamask/approval-controller": ^3.5.1 - "@metamask/network-controller": ^12.2.0 + "@metamask/network-controller": ^13.0.0 babel-runtime: ^6.26.0 languageName: unknown linkType: soft From d2a79aafb4f73b58e4c8efd83cb1a7a89967eecc Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 26 Sep 2023 10:02:43 -0230 Subject: [PATCH 02/14] chore: Update `typedoc` and related packages (#1717) ## Explanation The `typedoc` package has been updated to match the version used by the module template. This was done in preparation for updating the TypeScript version used by the core monorepo as well. The current version of `typedoc` we use doesn't support TypeScript v4.8. The `typedoc-plugin-missing-exports` package required an update to maintain compatibility with `typedoc`. In both cases, none of the breaking changes affect our usage of the package. Mostly they include improvements to the generated documentation, and dropping support for older Node.js and TypeScript versions. `typedoc` changelog: https://github.com/TypeStrong/typedoc/releases/tag/v0.23.0 `typedoc-plugin-missing-exports` changelog: https://github.com/Gerrit0/typedoc-plugin-missing-exports/blob/main/CHANGELOG.md#0230-2022-06-26 ## References None ## Changelog N/A ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- packages/accounts-controller/package.json | 4 +- packages/address-book-controller/package.json | 4 +- packages/announcement-controller/package.json | 4 +- packages/approval-controller/package.json | 4 +- packages/assets-controllers/package.json | 4 +- packages/base-controller/package.json | 4 +- packages/composable-controller/package.json | 4 +- packages/controller-utils/package.json | 4 +- packages/ens-controller/package.json | 4 +- packages/gas-fee-controller/package.json | 4 +- packages/keyring-controller/package.json | 4 +- packages/logging-controller/package.json | 4 +- packages/message-manager/package.json | 4 +- packages/name-controller/package.json | 4 +- packages/network-controller/package.json | 4 +- packages/notification-controller/package.json | 4 +- packages/permission-controller/package.json | 4 +- packages/phishing-controller/package.json | 4 +- packages/preferences-controller/package.json | 4 +- packages/rate-limit-controller/package.json | 4 +- .../selected-network-controller/package.json | 4 +- packages/signature-controller/package.json | 4 +- packages/transaction-controller/package.json | 4 +- yarn.lock | 176 +++++++++--------- 24 files changed, 131 insertions(+), 137 deletions(-) diff --git a/packages/accounts-controller/package.json b/packages/accounts-controller/package.json index 087f75c198c..24191d9ba33 100644 --- a/packages/accounts-controller/package.json +++ b/packages/accounts-controller/package.json @@ -48,8 +48,8 @@ "@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", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/packages/address-book-controller/package.json b/packages/address-book-controller/package.json index bf1987aef18..a3e4afe7d22 100644 --- a/packages/address-book-controller/package.json +++ b/packages/address-book-controller/package.json @@ -38,8 +38,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/announcement-controller/package.json b/packages/announcement-controller/package.json index a8f3a456a5e..8ad9778e1d5 100644 --- a/packages/announcement-controller/package.json +++ b/packages/announcement-controller/package.json @@ -37,8 +37,8 @@ "immer": "^9.0.6", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/approval-controller/package.json b/packages/approval-controller/package.json index bfcde9c024e..27657d4ed44 100644 --- a/packages/approval-controller/package.json +++ b/packages/approval-controller/package.json @@ -41,8 +41,8 @@ "jest": "^27.5.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index f8290b56b5c..031ba85cf5d 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -63,8 +63,8 @@ "nock": "^13.3.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/packages/base-controller/package.json b/packages/base-controller/package.json index e6ac76f22af..9d67bb3ff51 100644 --- a/packages/base-controller/package.json +++ b/packages/base-controller/package.json @@ -39,8 +39,8 @@ "jest": "^27.5.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/composable-controller/package.json b/packages/composable-controller/package.json index ae25fcc010b..c62d5fb7799 100644 --- a/packages/composable-controller/package.json +++ b/packages/composable-controller/package.json @@ -38,8 +38,8 @@ "jest": "^27.5.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/controller-utils/package.json b/packages/controller-utils/package.json index b64384bcf77..491ad0301db 100644 --- a/packages/controller-utils/package.json +++ b/packages/controller-utils/package.json @@ -45,8 +45,8 @@ "jest": "^27.5.1", "nock": "^13.3.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/ens-controller/package.json b/packages/ens-controller/package.json index 28d9f4a986f..3edbe15d70d 100644 --- a/packages/ens-controller/package.json +++ b/packages/ens-controller/package.json @@ -42,8 +42,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/packages/gas-fee-controller/package.json b/packages/gas-fee-controller/package.json index ebc364e88f6..e5d2de0a0c9 100644 --- a/packages/gas-fee-controller/package.json +++ b/packages/gas-fee-controller/package.json @@ -49,8 +49,8 @@ "nock": "^13.3.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/packages/keyring-controller/package.json b/packages/keyring-controller/package.json index 0db24435864..d2b33d2a8b4 100644 --- a/packages/keyring-controller/package.json +++ b/packages/keyring-controller/package.json @@ -51,8 +51,8 @@ "jest": "^27.5.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3", "uuid": "^8.3.2" }, diff --git a/packages/logging-controller/package.json b/packages/logging-controller/package.json index b300c7fb4a0..a02635b1680 100644 --- a/packages/logging-controller/package.json +++ b/packages/logging-controller/package.json @@ -38,8 +38,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/message-manager/package.json b/packages/message-manager/package.json index f854a4cf922..ac55fe711b7 100644 --- a/packages/message-manager/package.json +++ b/packages/message-manager/package.json @@ -43,8 +43,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/name-controller/package.json b/packages/name-controller/package.json index 84d793d9289..f2e4f28c228 100644 --- a/packages/name-controller/package.json +++ b/packages/name-controller/package.json @@ -38,8 +38,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/network-controller/package.json b/packages/network-controller/package.json index 1190ff6a789..2ad3570d8b3 100644 --- a/packages/network-controller/package.json +++ b/packages/network-controller/package.json @@ -56,8 +56,8 @@ "nock": "^13.3.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/notification-controller/package.json b/packages/notification-controller/package.json index 0cb8b5966db..fea9e4f35c2 100644 --- a/packages/notification-controller/package.json +++ b/packages/notification-controller/package.json @@ -39,8 +39,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/permission-controller/package.json b/packages/permission-controller/package.json index 58a8e9ea99f..28ac47accae 100644 --- a/packages/permission-controller/package.json +++ b/packages/permission-controller/package.json @@ -45,8 +45,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/packages/phishing-controller/package.json b/packages/phishing-controller/package.json index 0561fe8d4db..73db7a26336 100644 --- a/packages/phishing-controller/package.json +++ b/packages/phishing-controller/package.json @@ -42,8 +42,8 @@ "nock": "^13.3.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/preferences-controller/package.json b/packages/preferences-controller/package.json index 59a01865080..e72014e400f 100644 --- a/packages/preferences-controller/package.json +++ b/packages/preferences-controller/package.json @@ -37,8 +37,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/rate-limit-controller/package.json b/packages/rate-limit-controller/package.json index a666b383fc2..d929f6f53d4 100644 --- a/packages/rate-limit-controller/package.json +++ b/packages/rate-limit-controller/package.json @@ -38,8 +38,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "engines": { diff --git a/packages/selected-network-controller/package.json b/packages/selected-network-controller/package.json index b902bbdd0fa..e49bc8a9b05 100644 --- a/packages/selected-network-controller/package.json +++ b/packages/selected-network-controller/package.json @@ -42,8 +42,8 @@ "nock": "^13.3.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/packages/signature-controller/package.json b/packages/signature-controller/package.json index 3f070fe4a3a..dbd7553ad11 100644 --- a/packages/signature-controller/package.json +++ b/packages/signature-controller/package.json @@ -46,8 +46,8 @@ "deepmerge": "^4.2.2", "jest": "^27.5.1", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/packages/transaction-controller/package.json b/packages/transaction-controller/package.json index 376984c26df..252e8975504 100644 --- a/packages/transaction-controller/package.json +++ b/packages/transaction-controller/package.json @@ -57,8 +57,8 @@ "jest": "^27.5.1", "sinon": "^9.2.4", "ts-jest": "^27.1.4", - "typedoc": "^0.22.15", - "typedoc-plugin-missing-exports": "^0.22.6", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", "typescript": "~4.6.3" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index f3dd9ca3583..c51530c5a7e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,8 +1308,8 @@ __metadata: jest: ^27.5.1 nanoid: ^3.1.31 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: @@ -1340,8 +1340,8 @@ __metadata: deepmerge: ^4.2.2 jest: ^27.5.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -1357,8 +1357,8 @@ __metadata: immer: ^9.0.6 jest: ^27.5.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -1378,8 +1378,8 @@ __metadata: nanoid: ^3.1.31 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -1420,8 +1420,8 @@ __metadata: single-call-balance-checker-abi: ^1.0.0 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: @@ -1458,8 +1458,8 @@ __metadata: jest: ^27.5.1 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -1483,8 +1483,8 @@ __metadata: jest: ^27.5.1 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -1515,8 +1515,8 @@ __metadata: jest: ^27.5.1 nock: ^13.3.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -1595,8 +1595,8 @@ __metadata: jest: ^27.5.1 punycode: ^2.1.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 peerDependencies: "@metamask/network-controller": ^13.0.0 @@ -1823,8 +1823,8 @@ __metadata: nock: ^13.3.1 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: @@ -1912,8 +1912,8 @@ __metadata: jest: ^27.5.1 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: @@ -1932,8 +1932,8 @@ __metadata: deepmerge: ^4.2.2 jest: ^27.5.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 languageName: unknown @@ -1955,8 +1955,8 @@ __metadata: jest: ^27.5.1 jsonschema: ^1.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 languageName: unknown @@ -1980,8 +1980,8 @@ __metadata: immer: ^9.0.6 jest: ^27.5.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -2015,8 +2015,8 @@ __metadata: nock: ^13.3.1 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 languageName: unknown @@ -2035,8 +2035,8 @@ __metadata: jest: ^27.5.1 nanoid: ^3.1.31 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -2091,8 +2091,8 @@ __metadata: json-rpc-engine: ^6.1.0 nanoid: ^3.1.31 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 peerDependencies: "@metamask/approval-controller": ^3.5.1 @@ -2115,8 +2115,8 @@ __metadata: punycode: ^2.1.1 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -2142,8 +2142,8 @@ __metadata: deepmerge: ^4.2.2 jest: ^27.5.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -2218,8 +2218,8 @@ __metadata: immer: ^9.0.6 jest: ^27.5.1 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 languageName: unknown linkType: soft @@ -2320,8 +2320,8 @@ __metadata: nock: ^13.3.1 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 peerDependencies: "@metamask/network-controller": ^13.0.0 @@ -2348,8 +2348,8 @@ __metadata: jest: ^27.5.1 lodash: ^4.17.21 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 peerDependencies: "@metamask/approval-controller": ^3.5.1 @@ -2580,8 +2580,8 @@ __metadata: nonce-tracker: ^1.1.0 sinon: ^9.2.4 ts-jest: ^27.1.4 - typedoc: ^0.22.15 - typedoc-plugin-missing-exports: ^0.22.6 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 typescript: ~4.6.3 uuid: ^8.3.2 peerDependencies: @@ -3558,6 +3558,13 @@ __metadata: languageName: node linkType: hard +"ansi-sequence-parser@npm:^1.1.0": + version: 1.1.1 + resolution: "ansi-sequence-parser@npm:1.1.1" + checksum: ead5b15c596e8e85ca02951a844366c6776769dcc9fd1bd3a0db11bb21364554822c6a439877fb599e7e1ffa0b5f039f1e5501423950457f3dcb2f480c30b188 + languageName: node + linkType: hard + "ansi-styles@npm:^3.2.1": version: 3.2.1 resolution: "ansi-styles@npm:3.2.1" @@ -6047,19 +6054,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.3": - version: 8.1.0 - resolution: "glob@npm:8.1.0" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^5.0.1 - once: ^1.3.0 - checksum: 92fbea3221a7d12075f26f0227abac435de868dd0736a17170663783296d0dd8d3d532a5672b4488a439bf5d7fb85cdd07c11185d6cd39184f0385cbdfb86a47 - languageName: node - linkType: hard - "globals@npm:^11.1.0": version: 11.12.0 resolution: "globals@npm:11.12.0" @@ -7650,7 +7644,7 @@ __metadata: languageName: node linkType: hard -"jsonc-parser@npm:^3.0.0": +"jsonc-parser@npm:^3.2.0": version: 3.2.0 resolution: "jsonc-parser@npm:3.2.0" checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 @@ -7854,7 +7848,7 @@ __metadata: languageName: node linkType: hard -"marked@npm:^4.0.16": +"marked@npm:^4.2.12": version: 4.3.0 resolution: "marked@npm:4.3.0" bin: @@ -7958,12 +7952,12 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^5.0.1, minimatch@npm:^5.1.0": - version: 5.1.6 - resolution: "minimatch@npm:5.1.6" +"minimatch@npm:^7.1.3": + version: 7.4.6 + resolution: "minimatch@npm:7.4.6" dependencies: brace-expansion: ^2.0.1 - checksum: 7564208ef81d7065a370f788d337cd80a689e981042cb9a1d0e6580b6c6a8c9279eba80010516e258835a988363f99f54a6f711a315089b8b42694f5da9d0d77 + checksum: 1a6c8d22618df9d2a88aabeef1de5622eb7b558e9f8010be791cb6b0fa6e102d39b11c28d75b855a1e377b12edc7db8ff12a99c20353441caa6a05e78deb5da9 languageName: node linkType: hard @@ -9228,14 +9222,15 @@ __metadata: languageName: node linkType: hard -"shiki@npm:^0.10.1": - version: 0.10.1 - resolution: "shiki@npm:0.10.1" +"shiki@npm:^0.14.1": + version: 0.14.4 + resolution: "shiki@npm:0.14.4" dependencies: - jsonc-parser: ^3.0.0 - vscode-oniguruma: ^1.6.1 - vscode-textmate: 5.2.0 - checksum: fb746f3cb3de7e545e3b10a6cb658d3938f840e4ccc9a3c90ceb7e69a8f89dbb432171faac1e9f02a03f103684dad88ee5e54b5c4964fa6b579fca6e8e26424d + ansi-sequence-parser: ^1.1.0 + jsonc-parser: ^3.2.0 + vscode-oniguruma: ^1.7.0 + vscode-textmate: ^8.0.0 + checksum: 1173f6fa9531690a8cd4bf1d8e28c9eb9295af38a4c150cba6546e95f6e32bc96c7dd98826e39e688f1ca9d36b683a9a02ef77d51ce6495900b3a46ada64f828 languageName: node linkType: hard @@ -10031,29 +10026,28 @@ __metadata: 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" +"typedoc-plugin-missing-exports@npm:^0.23.0": + version: 0.23.0 + resolution: "typedoc-plugin-missing-exports@npm:0.23.0" peerDependencies: - typedoc: 0.22.x - checksum: 012f44beaac05731b4d37c26bfba2d972ac48344eab6be793a95982712a207416e7dc3451279be4e4cebc8b6b99a16764d2d4aaa06e041fac60ce21cc22cf796 + typedoc: 0.22.x || 0.23.x + checksum: b3fc9eccca88a9ffb686d1e9ba923178c54b4bb7e496823b7b971b6f6baa957263f7ccff058f5b0e579fee49c93da09dbdc3a4dafd713960d93b2832de8094e1 languageName: node linkType: hard -"typedoc@npm:^0.22.15": - version: 0.22.18 - resolution: "typedoc@npm:0.22.18" +"typedoc@npm:^0.23.15": + version: 0.23.28 + resolution: "typedoc@npm:0.23.28" dependencies: - glob: ^8.0.3 lunr: ^2.3.9 - marked: ^4.0.16 - minimatch: ^5.1.0 - shiki: ^0.10.1 + marked: ^4.2.12 + minimatch: ^7.1.3 + shiki: ^0.14.1 peerDependencies: - typescript: 4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x || 4.7.x + typescript: 4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x bin: typedoc: bin/typedoc - checksum: b813d8129682f6ed5a4e96bacaf019e4da1d2744ca89fef850d6bb4c034616567ce67e6a7f5cfc5f00aac573f0b45d44b1427aafa262ab88dce6b460cb9e744c + checksum: 40eb4e207aac1b734e09400cf03f543642cc7b11000895198dd5a0d3166315759ccf4ac30a2915153597c5c186101c72bac2f1fc12b428184a9274d3a0e44c5e languageName: node linkType: hard @@ -10235,17 +10229,17 @@ __metadata: languageName: node linkType: hard -"vscode-oniguruma@npm:^1.6.1": +"vscode-oniguruma@npm:^1.7.0": version: 1.7.0 resolution: "vscode-oniguruma@npm:1.7.0" checksum: 53519d91d90593e6fb080260892e87d447e9b200c4964d766772b5053f5699066539d92100f77f1302c91e8fc5d9c772fbe40fe4c90f3d411a96d5a9b1e63f42 languageName: node linkType: hard -"vscode-textmate@npm:5.2.0": - version: 5.2.0 - resolution: "vscode-textmate@npm:5.2.0" - checksum: 5449b42d451080f6f3649b66948f4b5ee4643c4e88cfe3558a3b31c84c78060cfdd288c4958c1690eaa5cd65d09992fa6b7c3bef9d4aa72b3651054a04624d20 +"vscode-textmate@npm:^8.0.0": + version: 8.0.0 + resolution: "vscode-textmate@npm:8.0.0" + checksum: 127780dfea89559d70b8326df6ec344cfd701312dd7f3f591a718693812b7852c30b6715e3cfc8b3200a4e2515b4c96f0843c0eacc0a3020969b5de262c2a4bb languageName: node linkType: hard From 22786ef84cdd4db41f58f33340e291e8951d63ab Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Tue, 26 Sep 2023 15:11:05 +0200 Subject: [PATCH 03/14] Migrate `PhishingController` to BaseControllerV2 (#1705) --- .../src/PhishingController.test.ts | 292 ++++++++---------- .../src/PhishingController.ts | 282 ++++++++++------- 2 files changed, 303 insertions(+), 271 deletions(-) diff --git a/packages/phishing-controller/src/PhishingController.test.ts b/packages/phishing-controller/src/PhishingController.test.ts index 0950c77381b..be3888ad208 100644 --- a/packages/phishing-controller/src/PhishingController.test.ts +++ b/packages/phishing-controller/src/PhishingController.test.ts @@ -1,3 +1,4 @@ +import { ControllerMessenger } from '@metamask/base-controller'; import { strict as assert } from 'assert'; import nock from 'nock'; import * as sinon from 'sinon'; @@ -8,10 +9,45 @@ import { METAMASK_STALELIST_FILE, PhishingController, PHISHING_CONFIG_BASE_URL, + type PhishingControllerActions, + type PhishingControllerOptions, } from './PhishingController'; -const defaultHotlistRefreshInterval = 30 * 60; -const defaultStalelistRefreshInterval = 4 * 24 * 60 * 60; +const controllerName = 'PhishingController'; + +/** + * Constructs a restricted controller messenger. + * + * @returns A restricted controller messenger. + */ +function getRestrictedMessenger() { + const controllerMessenger = new ControllerMessenger< + PhishingControllerActions, + never + >(); + + const messenger = controllerMessenger.getRestricted< + typeof controllerName, + never, + never + >({ + name: 'PhishingController', + }); + + return messenger; +} + +/** + * Contruct a Phishing Controller with the given options if any. + * @param options - The Phishing Controller options. + * @returns The contstructed Phishing Controller. + */ +function getPhishingController(options?: Partial) { + return new PhishingController({ + messenger: getRestrictedMessenger(), + ...options, + }); +} describe('PhishingController', () => { afterEach(() => { @@ -19,23 +55,15 @@ describe('PhishingController', () => { }); it('should have no default phishing lists', () => { - const controller = new PhishingController(); + const controller = getPhishingController(); expect(controller.state.phishingLists).toStrictEqual([]); }); it('should default to an empty whitelist', () => { - const controller = new PhishingController(); + const controller = getPhishingController(); expect(controller.state.whitelist).toStrictEqual([]); }); - it('should use default stalelist & hotlist refresh intervals', () => { - const controller = new PhishingController(); - expect(controller.config).toStrictEqual({ - stalelistRefreshInterval: defaultStalelistRefreshInterval, - hotlistRefreshInterval: defaultHotlistRefreshInterval, - }); - }); - it('does not call update stalelist or hotlist upon construction', async () => { const nockScope = nock(PHISHING_CONFIG_BASE_URL) .get(METAMASK_STALELIST_FILE) @@ -56,7 +84,7 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - new PhishingController({}); + getPhishingController(); expect(nockScope.isDone()).toBe(false); }); @@ -82,7 +110,7 @@ describe('PhishingController', () => { ], }); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -137,7 +165,7 @@ describe('PhishingController', () => { it('should not have stalelist be out of date immediately after maybeUpdateState is called', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -149,7 +177,7 @@ describe('PhishingController', () => { it('should not be out of date after maybeUpdateStalelist is called but before refresh interval has passed', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -162,7 +190,7 @@ describe('PhishingController', () => { it('should still be out of date while update is in progress', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -178,7 +206,7 @@ describe('PhishingController', () => { it('should call update only if it is out of date, otherwise it should not call update', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); expect(controller.isStalelistOutOfDate()).toBe(false); @@ -236,7 +264,7 @@ describe('PhishingController', () => { ], }); const clock = sinon.useFakeTimers(50); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, stalelistRefreshInterval: 50, }); @@ -250,7 +278,7 @@ describe('PhishingController', () => { describe('isStalelistOutOfDate', () => { it('should not be out of date upon construction', () => { sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); @@ -259,7 +287,7 @@ describe('PhishingController', () => { it('should not be out of date after some of the refresh interval has passed', () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); clock.tick(1000 * 5); @@ -269,7 +297,7 @@ describe('PhishingController', () => { it('should be out of date after the refresh interval has passed', () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -279,7 +307,7 @@ describe('PhishingController', () => { it('should be out of date if the refresh interval has passed and an update is in progress', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -293,7 +321,7 @@ describe('PhishingController', () => { it('should not be out of date if the phishing lists were just updated', async () => { sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); await controller.updateStalelist(); @@ -303,18 +331,18 @@ describe('PhishingController', () => { it('should not be out of date if the phishing lists were recently updated', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); await controller.updateStalelist(); - await clock.tick(1000 * 5); + clock.tick(1000 * 5); expect(controller.isStalelistOutOfDate()).toBe(false); }); it('should be out of date if the time elapsed since the last update equals the refresh interval', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ stalelistRefreshInterval: 10, }); await controller.updateStalelist(); @@ -327,7 +355,7 @@ describe('PhishingController', () => { describe('isHotlistOutOfDate', () => { it('should not be out of date upon construction', () => { sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); @@ -336,7 +364,7 @@ describe('PhishingController', () => { it('should not be out of date after some of the refresh interval has passed', () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); clock.tick(1000 * 5); @@ -346,7 +374,7 @@ describe('PhishingController', () => { it('should be out of date after the refresh interval has passed', () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -356,7 +384,7 @@ describe('PhishingController', () => { it('should be out of date if the refresh interval has passed and an update is in progress', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); clock.tick(1000 * 10); @@ -370,7 +398,7 @@ describe('PhishingController', () => { it('should not be out of date if the phishing lists were just updated', async () => { sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); await controller.updateHotlist(); @@ -380,18 +408,18 @@ describe('PhishingController', () => { it('should not be out of date if the phishing lists were recently updated', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); await controller.updateHotlist(); - await clock.tick(1000 * 5); + clock.tick(1000 * 5); expect(controller.isHotlistOutOfDate()).toBe(false); }); it('should be out of date if the time elapsed since the last update equals the refresh interval', async () => { const clock = sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); await controller.updateHotlist(); @@ -403,7 +431,7 @@ describe('PhishingController', () => { it('should be able to change the stalelistRefreshInterval', async () => { sinon.useFakeTimers(); - const controller = new PhishingController({ stalelistRefreshInterval: 10 }); + const controller = getPhishingController({ stalelistRefreshInterval: 10 }); controller.setStalelistRefreshInterval(0); expect(controller.isStalelistOutOfDate()).toBe(true); @@ -411,7 +439,7 @@ describe('PhishingController', () => { it('should be able to change the hotlistRefreshInterval', async () => { sinon.useFakeTimers(); - const controller = new PhishingController({ + const controller = getPhishingController({ hotlistRefreshInterval: 10, }); controller.setHotlistRefreshInterval(0); @@ -439,7 +467,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('metamask.io')).toMatchObject({ result: false, @@ -469,7 +497,7 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('i❤.ws')).toMatchObject({ result: false, @@ -498,7 +526,7 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('xn--i-7iq.ws')).toMatchObject({ result: false, @@ -527,7 +555,7 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('etnerscan.io')).toMatchObject({ result: true, @@ -556,7 +584,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('myetherẉalletṭ.com')).toMatchObject({ result: true, @@ -586,7 +614,7 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('xn--myetherallet-4k5fwn.com')).toMatchObject({ result: true, @@ -624,7 +652,7 @@ describe('PhishingController', () => { ], }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect( controller.test('e4d600ab9141b7a9859511c77e63b9b3.com'), @@ -656,7 +684,7 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(500); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect( controller.test('e4d600ab9141b7a9859511c77e63b9b3.com'), @@ -686,7 +714,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('opensea.io')).toMatchObject({ result: false, @@ -715,7 +743,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.test('ohpensea.io')).toMatchObject({ result: true, @@ -744,7 +772,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect( controller.test('this-is-the-official-website-of-opensea.io'), @@ -774,7 +802,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); const unsafeDomain = 'electrum.mx'; assert.equal( @@ -809,7 +837,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); const unsafeDomain = 'electrum.mx'; assert.equal( @@ -845,7 +873,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); const unsafeDomain = 'myetherẉalletṭ.com'; assert.equal( @@ -880,7 +908,7 @@ describe('PhishingController', () => { }) .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); const unsafeDomain = 'xn--myetherallet-4k5fwn.com'; assert.equal( @@ -930,7 +958,7 @@ describe('PhishingController', () => { ], }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.state.phishingLists).toStrictEqual([ @@ -993,7 +1021,7 @@ describe('PhishingController', () => { ], }); - const controller = new PhishingController(); + const controller = getPhishingController(); await controller.updateStalelist(); expect(controller.state.phishingLists).toStrictEqual([ @@ -1018,10 +1046,15 @@ describe('PhishingController', () => { ]); }); - it('should not update stale list if disabled', async () => { - const controller = new PhishingController( - { disabled: true }, - { + it('should not update phishing lists if fetch returns 304', async () => { + nock(PHISHING_CONFIG_BASE_URL) + .get(METAMASK_STALELIST_FILE) + .reply(304) + .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) + .reply(304); + + const controller = getPhishingController({ + state: { phishingLists: [ { allowlist: [], @@ -1034,7 +1067,7 @@ describe('PhishingController', () => { }, ], }, - ); + }); await controller.updateStalelist(); expect(controller.state.phishingLists).toStrictEqual([ @@ -1050,10 +1083,15 @@ describe('PhishingController', () => { ]); }); - it('should not update hotlist lists if disabled', async () => { - const controller = new PhishingController( - { disabled: true }, - { + it('should not update phishing lists if fetch returns 500', async () => { + nock(PHISHING_CONFIG_BASE_URL) + .get(METAMASK_STALELIST_FILE) + .reply(500) + .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) + .reply(500); + + const controller = getPhishingController({ + state: { phishingLists: [ { allowlist: [], @@ -1066,76 +1104,6 @@ describe('PhishingController', () => { }, ], }, - ); - await controller.updateHotlist(); - - expect(controller.state.phishingLists).toStrictEqual([ - { - allowlist: [], - blocklist: [], - fuzzylist: [], - tolerance: 3, - version: 1, - name: ListNames.MetaMask, - lastUpdated: 0, - }, - ]); - }); - - it('should not update phishing lists if fetch returns 304', async () => { - nock(PHISHING_CONFIG_BASE_URL) - .get(METAMASK_STALELIST_FILE) - .reply(304) - .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) - .reply(304); - - const controller = new PhishingController(undefined, { - phishingLists: [ - { - allowlist: [], - blocklist: [], - fuzzylist: [], - tolerance: 3, - version: 1, - name: ListNames.MetaMask, - lastUpdated: 0, - }, - ], - }); - await controller.updateStalelist(); - - expect(controller.state.phishingLists).toStrictEqual([ - { - allowlist: [], - blocklist: [], - fuzzylist: [], - tolerance: 3, - version: 1, - name: ListNames.MetaMask, - lastUpdated: 0, - }, - ]); - }); - - it('should not update phishing lists if fetch returns 500', async () => { - nock(PHISHING_CONFIG_BASE_URL) - .get(METAMASK_STALELIST_FILE) - .reply(500) - .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) - .reply(500); - - const controller = new PhishingController(undefined, { - phishingLists: [ - { - allowlist: [], - blocklist: [], - fuzzylist: [], - tolerance: 3, - version: 1, - name: ListNames.MetaMask, - lastUpdated: 0, - }, - ], }); await controller.updateStalelist(); @@ -1159,7 +1127,7 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${1}`) .replyWithError('network error'); - const controller = new PhishingController(); + const controller = getPhishingController(); expect(await controller.updateStalelist()).toBeUndefined(); }); @@ -1189,7 +1157,7 @@ describe('PhishingController', () => { .delay(100) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); const firstPromise = controller.updateStalelist(); const secondPromise = controller.updateStalelist(); @@ -1227,7 +1195,7 @@ describe('PhishingController', () => { .delay(100) .reply(200, { data: [] }); - const controller = new PhishingController(); + const controller = getPhishingController(); const firstPromise = controller.updateStalelist(); const secondPromise = controller.updateStalelist(); clock.tick(1000 * 99); @@ -1255,18 +1223,20 @@ describe('PhishingController', () => { ], }); - const controller = new PhishingController(undefined, { - phishingLists: [ - { - allowlist: [], - blocklist: [], - fuzzylist: [], - tolerance: 3, - version: 1, - name: ListNames.MetaMask, - lastUpdated: 0, - }, - ], + const controller = getPhishingController({ + state: { + phishingLists: [ + { + allowlist: [], + blocklist: [], + fuzzylist: [], + tolerance: 3, + version: 1, + name: ListNames.MetaMask, + lastUpdated: 0, + }, + ], + }, }); await controller.updateHotlist(); @@ -1287,18 +1257,20 @@ describe('PhishingController', () => { .get(`${METAMASK_HOTLIST_DIFF_FILE}/${0}`) .reply(404); - const controller = new PhishingController(undefined, { - phishingLists: [ - { - allowlist: [], - blocklist: [], - fuzzylist: [], - tolerance: 3, - version: 1, - name: ListNames.MetaMask, - lastUpdated: 0, - }, - ], + const controller = getPhishingController({ + state: { + phishingLists: [ + { + allowlist: [], + blocklist: [], + fuzzylist: [], + tolerance: 3, + version: 1, + name: ListNames.MetaMask, + lastUpdated: 0, + }, + ], + }, }); await controller.updateHotlist(); diff --git a/packages/phishing-controller/src/PhishingController.ts b/packages/phishing-controller/src/PhishingController.ts index 80ae1cd16c6..2c95e86587d 100644 --- a/packages/phishing-controller/src/PhishingController.ts +++ b/packages/phishing-controller/src/PhishingController.ts @@ -1,11 +1,24 @@ -import type { BaseConfig, BaseState } from '@metamask/base-controller'; -import { BaseController } from '@metamask/base-controller'; +import type { RestrictedControllerMessenger } from '@metamask/base-controller'; +import { BaseControllerV2 as BaseController } from '@metamask/base-controller'; import { safelyExecute } from '@metamask/controller-utils'; import PhishingDetector from 'eth-phishing-detect/src/detector'; import { toASCII } from 'punycode/'; import { applyDiffs, fetchTimeNow } from './utils'; +export const PHISHING_CONFIG_BASE_URL = + 'https://phishing-detection.metafi.codefi.network'; + +export const METAMASK_STALELIST_FILE = '/v1/stalelist'; + +export const METAMASK_HOTLIST_DIFF_FILE = '/v1/diffsSince'; + +export const HOTLIST_REFRESH_INTERVAL = 30 * 60; // 30 mins in seconds +export const STALELIST_REFRESH_INTERVAL = 4 * 24 * 60 * 60; // 4 days in seconds + +export const METAMASK_STALELIST_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_STALELIST_FILE}`; +export const METAMASK_HOTLIST_DIFF_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_HOTLIST_DIFF_FILE}`; + /** * @type ListTypes * @@ -24,36 +37,36 @@ export type ListTypes = 'fuzzylist' | 'blocklist' | 'allowlist'; * @property version - Version number of this configuration * @property whitelist - List of approved origins */ -export interface EthPhishingResponse { +export type EthPhishingResponse = { blacklist: string[]; fuzzylist: string[]; tolerance: number; version: number; whitelist: string[]; -} +}; /** * @type PhishingStalelist * - * Interface defining expected type of the stalelist.json file. + * type defining expected type of the stalelist.json file. * @property eth_phishing_detect_config - Stale list sourced from eth-phishing-detect's config.json. * @property phishfort_hotlist - Stale list sourced from phishfort's hotlist.json. Only includes blocklist. Deduplicated entries from eth_phishing_detect_config. * @property tolerance - Fuzzy match tolerance level * @property lastUpdated - Timestamp of last update. * @property version - Stalelist data structure iteration. */ -export interface PhishingStalelist { +export type PhishingStalelist = { eth_phishing_detect_config: Record; phishfort_hotlist: Record; tolerance: number; version: number; lastUpdated: number; -} +}; /** * @type PhishingListState * - * Interface defining the persisted list state. This is the persisted state that is updated frequently with `this.maybeUpdateState()`. + * type defining the persisted list state. This is the persisted state that is updated frequently with `this.maybeUpdateState()`. * @property allowlist - List of approved origins (legacy naming "whitelist") * @property blocklist - List of unapproved origins (legacy naming "blacklist") * @property fuzzylist - List of fuzzy-matched unapproved origins @@ -62,7 +75,7 @@ export interface PhishingStalelist { * @property version - Version of the phishing list state. * @property name - Name of the list. Used for attribution. */ -export interface PhishingListState { +export type PhishingListState = { allowlist: string[]; blocklist: string[]; fuzzylist: string[]; @@ -70,45 +83,45 @@ export interface PhishingListState { version: number; lastUpdated: number; name: ListNames; -} +}; /** * @type EthPhishingDetectResult * - * Interface that describes the result of the `test` method. + * type that describes the result of the `test` method. * @property name - Name of the config on which a match was found. * @property version - Version of the config on which a match was found. * @property result - Whether a domain was detected as a phishing domain. True means an unsafe domain. * @property match - The matching fuzzylist origin when a fuzzylist match is found. Returned as undefined for non-fuzzy true results. * @property type - The field of the config on which a match was found. */ -export interface EthPhishingDetectResult { +export type EthPhishingDetectResult = { name?: string; version?: string; result: boolean; match?: string; // Returned as undefined for non-fuzzy true results. type: 'all' | 'fuzzy' | 'blocklist' | 'allowlist'; -} +}; /** * @type HotlistDiff * - * Interface defining the expected type of the diffs in hotlist.json file. + * type defining the expected type of the diffs in hotlist.json file. * @property url - Url of the diff entry. * @property timestamp - Timestamp at which the diff was identified. * @property targetList - The list name where the diff was identified. * @property isRemoval - Was the diff identified a removal type. */ -export interface HotlistDiff { +export type HotlistDiff = { url: string; timestamp: number; targetList: `${ListKeys}.${ListTypes}`; isRemoval?: boolean; -} +}; -export interface DataResultWrapper { +export type DataResultWrapper = { data: T; -} +}; /** * @type Hotlist @@ -121,45 +134,6 @@ export interface DataResultWrapper { */ export type Hotlist = HotlistDiff[]; -/** - * @type PhishingConfig - * - * Phishing controller configuration - * @property stalelistRefreshInterval - Polling interval used to fetch stale list. - * @property hotlistRefreshInterval - Polling interval used to fetch hotlist diff list. - */ -export interface PhishingConfig extends BaseConfig { - stalelistRefreshInterval: number; - hotlistRefreshInterval: number; -} - -/** - * @type PhishingState - * - * Phishing controller state - * @property phishing - eth-phishing-detect configuration - * @property whitelist - array of temporarily-approved origins - */ -export interface PhishingState extends BaseState { - phishingLists: PhishingListState[]; - whitelist: string[]; - hotlistLastFetched: number; - stalelistLastFetched: number; -} - -export const PHISHING_CONFIG_BASE_URL = - 'https://phishing-detection.metafi.codefi.network'; - -export const METAMASK_STALELIST_FILE = '/v1/stalelist'; - -export const METAMASK_HOTLIST_DIFF_FILE = '/v1/diffsSince'; - -export const HOTLIST_REFRESH_INTERVAL = 30 * 60; // 30 mins in seconds -export const STALELIST_REFRESH_INTERVAL = 4 * 24 * 60 * 60; // 4 days in seconds - -export const METAMASK_STALELIST_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_STALELIST_FILE}`; -export const METAMASK_HOTLIST_DIFF_URL = `${PHISHING_CONFIG_BASE_URL}${METAMASK_HOTLIST_DIFF_FILE}`; - /** * Enum containing upstream data provider source list keys. * These are the keys denoting lists consumed by the upstream data provider. @@ -195,56 +169,147 @@ export const phishingListKeyNameMap = { [ListKeys.PhishfortHotlist]: ListNames.Phishfort, }; +const controllerName = 'PhishingController'; + +const metadata = { + phishingLists: { persist: true, anonymous: false }, + whitelist: { persist: true, anonymous: false }, + hotlistLastFetched: { persist: true, anonymous: false }, + stalelistLastFetched: { persist: true, anonymous: false }, +}; + +/** + * Get a default empty state for the controller. + * @returns The default empty state. + */ +const getDefaultState = (): PhishingControllerState => { + return { + phishingLists: [], + whitelist: [], + hotlistLastFetched: 0, + stalelistLastFetched: 0, + }; +}; + +/** + * @type PhishingControllerState + * + * Phishing controller state + * @property phishing - eth-phishing-detect configuration + * @property whitelist - array of temporarily-approved origins + */ +export type PhishingControllerState = { + phishingLists: PhishingListState[]; + whitelist: string[]; + hotlistLastFetched: number; + stalelistLastFetched: number; +}; + +/** + * @type PhishingControllerOptions + * + * Phishing controller options + * @property stalelistRefreshInterval - Polling interval used to fetch stale list. + * @property hotlistRefreshInterval - Polling interval used to fetch hotlist diff list. + */ +export type PhishingControllerOptions = { + stalelistRefreshInterval?: number; + hotlistRefreshInterval?: number; + messenger: PhishingControllerMessenger; + state?: Partial; +}; + +export type MaybeUpdateState = { + type: `${typeof controllerName}:maybeUpdateState`; + handler: PhishingController['maybeUpdateState']; +}; + +export type TestOrigin = { + type: `${typeof controllerName}:testOrigin`; + handler: PhishingController['test']; +}; + +export type PhishingControllerActions = MaybeUpdateState | TestOrigin; + +export type PhishingControllerMessenger = RestrictedControllerMessenger< + typeof controllerName, + PhishingControllerActions, + never, + never, + never +>; + /** * Controller that manages community-maintained lists of approved and unapproved website origins. */ export class PhishingController extends BaseController< - PhishingConfig, - PhishingState + typeof controllerName, + PhishingControllerState, + PhishingControllerMessenger > { - private detector: any; + #detector: any; - #inProgressHotlistUpdate: Promise | undefined; + #stalelistRefreshInterval: number; - #inProgressStalelistUpdate: Promise | undefined; + #hotlistRefreshInterval: number; - /** - * Name of this controller used during composition - */ - override name = 'PhishingController'; + #inProgressHotlistUpdate?: Promise; + + #inProgressStalelistUpdate?: Promise; /** - * Creates a PhishingController instance. + * Construct a Phishing Controller. * * @param config - Initial options used to configure this controller. - * @param state - Initial state to set on this controller. + * @param config.stalelistRefreshInterval - Polling interval used to fetch stale list. + * @param config.hotlistRefreshInterval - Polling interval used to fetch hotlist diff list. + * @param config.messenger - The controller restricted messenger. + * @param config.state - Initial state to set on this controller. */ - constructor( - config?: Partial, - state?: Partial, - ) { - super(config, state); - this.defaultConfig = { - stalelistRefreshInterval: STALELIST_REFRESH_INTERVAL, - hotlistRefreshInterval: HOTLIST_REFRESH_INTERVAL, - }; + constructor({ + stalelistRefreshInterval = STALELIST_REFRESH_INTERVAL, + hotlistRefreshInterval = HOTLIST_REFRESH_INTERVAL, + messenger, + state = {}, + }: PhishingControllerOptions) { + super({ + name: controllerName, + metadata, + messenger, + state: { + ...getDefaultState(), + ...state, + }, + }); - this.defaultState = { - phishingLists: [], - whitelist: [], - hotlistLastFetched: 0, - stalelistLastFetched: 0, - }; + this.#stalelistRefreshInterval = stalelistRefreshInterval; + this.#hotlistRefreshInterval = hotlistRefreshInterval; + this.#registerMessageHandlers(); - this.initialize(); this.updatePhishingDetector(); } + /** + * Constructor helper for registering this controller's messaging system + * actions. + */ + #registerMessageHandlers(): void { + this.messagingSystem.registerActionHandler( + `${controllerName}:maybeUpdateState` as const, + this.maybeUpdateState.bind(this), + ); + + this.messagingSystem.registerActionHandler( + `${controllerName}:testOrigin` as const, + this.test.bind(this), + ); + } + /** * Updates this.detector with an instance of PhishingDetector using the current state. */ updatePhishingDetector() { - this.detector = new PhishingDetector(this.state.phishingLists); + this.#detector = new PhishingDetector(this.state.phishingLists); } /** @@ -255,7 +320,7 @@ export class PhishingController extends BaseController< * @param interval - the new interval, in ms. */ setStalelistRefreshInterval(interval: number) { - this.configure({ stalelistRefreshInterval: interval }, false, false); + this.#stalelistRefreshInterval = interval; } /** @@ -266,7 +331,7 @@ export class PhishingController extends BaseController< * @param interval - the new interval, in ms. */ setHotlistRefreshInterval(interval: number) { - this.configure({ hotlistRefreshInterval: interval }, false, false); + this.#hotlistRefreshInterval = interval; } /** @@ -277,7 +342,7 @@ export class PhishingController extends BaseController< isStalelistOutOfDate() { return ( fetchTimeNow() - this.state.stalelistLastFetched >= - this.config.stalelistRefreshInterval + this.#stalelistRefreshInterval ); } @@ -289,7 +354,7 @@ export class PhishingController extends BaseController< isHotlistOutOfDate() { return ( fetchTimeNow() - this.state.hotlistLastFetched >= - this.config.hotlistRefreshInterval + this.#hotlistRefreshInterval ); } @@ -328,7 +393,7 @@ export class PhishingController extends BaseController< if (this.state.whitelist.includes(punycodeOrigin)) { return { result: false, type: 'all' }; // Same as whitelisted match returned by detector.check(...). } - return this.detector.check(punycodeOrigin); + return this.#detector.check(punycodeOrigin); } /** @@ -342,7 +407,9 @@ export class PhishingController extends BaseController< if (whitelist.includes(punycodeOrigin)) { return; } - this.update({ whitelist: [...whitelist, punycodeOrigin] }); + this.update((draftState) => { + draftState.whitelist.push(punycodeOrigin); + }); } /** @@ -392,21 +459,17 @@ export class PhishingController extends BaseController< * this function that prevents redundant configuration updates. */ async #updateStalelist() { - if (this.disabled) { - return; - } - let stalelistResponse; let hotlistDiffsResponse; try { - stalelistResponse = await this.queryConfig< + stalelistResponse = await this.#queryConfig< DataResultWrapper >(METAMASK_STALELIST_URL).then((d) => d); // Fetching hotlist diffs relies on having a lastUpdated timestamp to do `GET /v1/diffsSince/:timestamp`, // so it doesn't make sense to call if there is not a timestamp to begin with. if (stalelistResponse?.data && stalelistResponse.data.lastUpdated > 0) { - hotlistDiffsResponse = await this.queryConfig< + hotlistDiffsResponse = await this.#queryConfig< DataResultWrapper >(`${METAMASK_HOTLIST_DIFF_URL}/${stalelistResponse.data.lastUpdated}`); } @@ -414,9 +477,9 @@ export class PhishingController extends BaseController< // Set `stalelistLastFetched` and `hotlistLastFetched` even for failed requests to prevent server // from being overwhelmed with traffic after a network disruption. const timeNow = fetchTimeNow(); - this.update({ - stalelistLastFetched: timeNow, - hotlistLastFetched: timeNow, + this.update((draftState) => { + draftState.stalelistLastFetched = timeNow; + draftState.hotlistLastFetched = timeNow; }); } @@ -451,8 +514,8 @@ export class PhishingController extends BaseController< ListKeys.EthPhishingDetectConfig, ); - this.update({ - phishingLists: [newMetaMaskListState, newPhishfortListState], + this.update((draftState) => { + draftState.phishingLists = [newMetaMaskListState, newPhishfortListState]; }); this.updatePhishingDetector(); } @@ -464,23 +527,20 @@ export class PhishingController extends BaseController< * this function that prevents redundant configuration updates. */ async #updateHotlist() { - if (this.disabled) { - return; - } const lastDiffTimestamp = Math.max( ...this.state.phishingLists.map(({ lastUpdated }) => lastUpdated), ); let hotlistResponse: DataResultWrapper | null; try { - hotlistResponse = await this.queryConfig>( + hotlistResponse = await this.#queryConfig>( `${METAMASK_HOTLIST_DIFF_URL}/${lastDiffTimestamp}`, ); } finally { // Set `hotlistLastFetched` even for failed requests to prevent server from being overwhelmed with // traffic after a network disruption. - this.update({ - hotlistLastFetched: fetchTimeNow(), + this.update((draftState) => { + draftState.hotlistLastFetched = fetchTimeNow(); }); } @@ -496,13 +556,13 @@ export class PhishingController extends BaseController< ), ); - this.update({ - phishingLists: newPhishingLists, + this.update((draftState) => { + draftState.phishingLists = newPhishingLists; }); this.updatePhishingDetector(); } - private async queryConfig( + async #queryConfig( input: RequestInfo, ): Promise { const response = await safelyExecute( From e84aa471cd4169be081221168b9b88d0c930bfc4 Mon Sep 17 00:00:00 2001 From: Sylva Elendu Date: Tue, 26 Sep 2023 14:56:33 +0100 Subject: [PATCH 04/14] Return type of transaction if legacy (#1713) ## Explanation Including [1672](https://github.com/MetaMask/core/pull/1672) to core main repo. ## References ## Changelog ### `@metamask/transaction-controller` - **ADDED**: Includ `transaction?.type` when normalising transaction. ## Checklist - [ ] I've updated the test suite for new or updated code as appropriate - [ ] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [ ] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- packages/transaction-controller/src/types.ts | 6 +++ .../transaction-controller/src/utils.test.ts | 51 ++++++++++++++----- packages/transaction-controller/src/utils.ts | 1 + 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index 5cf4544d57c..e6a29685607 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -430,6 +430,12 @@ export interface TransactionParams { * Value associated with this transaction. */ value?: string; + + /** + * Type of transaction. + * 0x0 indicates a legacy transaction. + */ + type?: string; } /** diff --git a/packages/transaction-controller/src/utils.test.ts b/packages/transaction-controller/src/utils.test.ts index d0932826a18..d54d47ddc8d 100644 --- a/packages/transaction-controller/src/utils.test.ts +++ b/packages/transaction-controller/src/utils.test.ts @@ -19,8 +19,8 @@ describe('utils', () => { jest.clearAllMocks(); }); - it('normalizeTxParams', () => { - const normalized = util.normalizeTxParams({ + describe('normalizeTxParams', () => { + const commonInput = { data: 'data', from: 'FROM', gas: 'gas', @@ -31,18 +31,43 @@ describe('utils', () => { maxFeePerGas: 'maxFeePerGas', maxPriorityFeePerGas: 'maxPriorityFeePerGas', estimatedBaseFee: 'estimatedBaseFee', + }; + + it('normalizeTransaction', () => { + const normalized = util.normalizeTxParams({ + ...commonInput, + }); + expect(normalized).toStrictEqual({ + data: '0xdata', + from: '0xfrom', + gas: '0xgas', + gasPrice: '0xgasPrice', + nonce: '0xnonce', + to: '0xto', + value: '0xvalue', + maxFeePerGas: '0xmaxFeePerGas', + maxPriorityFeePerGas: '0xmaxPriorityFeePerGas', + estimatedBaseFee: '0xestimatedBaseFee', + }); }); - expect(normalized).toStrictEqual({ - data: '0xdata', - from: '0xfrom', - gas: '0xgas', - gasPrice: '0xgasPrice', - nonce: '0xnonce', - to: '0xto', - value: '0xvalue', - maxFeePerGas: '0xmaxFeePerGas', - maxPriorityFeePerGas: '0xmaxPriorityFeePerGas', - estimatedBaseFee: '0xestimatedBaseFee', + it('normalizeTransaction if type is zero', () => { + const normalized = util.normalizeTxParams({ + ...commonInput, + type: '0x0', + }); + expect(normalized).toStrictEqual({ + data: '0xdata', + from: '0xfrom', + gas: '0xgas', + gasPrice: '0xgasPrice', + nonce: '0xnonce', + to: '0xto', + value: '0xvalue', + maxFeePerGas: '0xmaxFeePerGas', + maxPriorityFeePerGas: '0xmaxPriorityFeePerGas', + estimatedBaseFee: '0xestimatedBaseFee', + type: '0x0', + }); }); }); diff --git a/packages/transaction-controller/src/utils.ts b/packages/transaction-controller/src/utils.ts index 7bab7d91e44..13dd009818d 100644 --- a/packages/transaction-controller/src/utils.ts +++ b/packages/transaction-controller/src/utils.ts @@ -28,6 +28,7 @@ const NORMALIZERS: { [param in keyof TransactionParams]: any } = { addHexPrefix(maxPriorityFeePerGas), estimatedBaseFee: (maxPriorityFeePerGas: string) => addHexPrefix(maxPriorityFeePerGas), + type: (type: string) => (type === '0x0' ? '0x0' : undefined), }; /** From 9698b0a882d52058b2664e1ab0dc1d7a1b6dcdbe Mon Sep 17 00:00:00 2001 From: Shane Date: Tue, 26 Sep 2023 07:41:26 -0700 Subject: [PATCH 05/14] Added polling-controller (#1703) ## Explanation > Originally was in https://github.com/MetaMask/core/pull/1673 but pulled out to get this in on its own. Adds an abstract class (currently named `PollingController`. The start and stop methods are parameterized by `networkClientId`'s and `pollingToken`'s and polling intervals are stored in class variables by `chainId` so that multiple `networkClients`/`chainIds` can poll simultaneously. `executePoll` is an abstract method to be implemented by the controller itself so that this pattern could be generalized and be agnostic to what is being executed on the polling interval. ## References Related to https://github.com/MetaMask/core/pull/1673 Related to https://github.com/MetaMask/MetaMask-planning/issues/1314 --------- Co-authored-by: Alex Donesky --- packages/polling-controller/CHANGELOG.md | 9 + packages/polling-controller/LICENSE | 20 ++ packages/polling-controller/README.md | 15 + packages/polling-controller/jest.config.js | 26 ++ packages/polling-controller/package.json | 58 ++++ .../src/PollingController.test.ts | 308 ++++++++++++++++++ .../src/PollingController.ts | 150 +++++++++ packages/polling-controller/src/index.ts | 1 + .../polling-controller/tsconfig.build.json | 14 + packages/polling-controller/tsconfig.json | 12 + packages/polling-controller/typedoc.json | 7 + yarn.lock | 23 ++ 12 files changed, 643 insertions(+) create mode 100644 packages/polling-controller/CHANGELOG.md create mode 100644 packages/polling-controller/LICENSE create mode 100644 packages/polling-controller/README.md create mode 100644 packages/polling-controller/jest.config.js create mode 100644 packages/polling-controller/package.json create mode 100644 packages/polling-controller/src/PollingController.test.ts create mode 100644 packages/polling-controller/src/PollingController.ts create mode 100644 packages/polling-controller/src/index.ts create mode 100644 packages/polling-controller/tsconfig.build.json create mode 100644 packages/polling-controller/tsconfig.json create mode 100644 packages/polling-controller/typedoc.json diff --git a/packages/polling-controller/CHANGELOG.md b/packages/polling-controller/CHANGELOG.md new file mode 100644 index 00000000000..27eb830b8c5 --- /dev/null +++ b/packages/polling-controller/CHANGELOG.md @@ -0,0 +1,9 @@ +# 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/polling-controller/LICENSE b/packages/polling-controller/LICENSE new file mode 100644 index 00000000000..ddfbecf9020 --- /dev/null +++ b/packages/polling-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/polling-controller/README.md b/packages/polling-controller/README.md new file mode 100644 index 00000000000..bade0a3e03d --- /dev/null +++ b/packages/polling-controller/README.md @@ -0,0 +1,15 @@ +# `@metamask/polling-controller` + +PollingController is used as the base for all controllers that need to poll for updates based on `networkClientId`. + +## Installation + +`yarn add @metamask/polling-controller` + +or + +`npm install @metamask/polling-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/polling-controller/jest.config.js b/packages/polling-controller/jest.config.js new file mode 100644 index 00000000000..17db4cd31b6 --- /dev/null +++ b/packages/polling-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: 65.31, + functions: 76.59, + lines: 75.83, + statements: 75.91, + }, + }, +}); diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json new file mode 100644 index 00000000000..40eb0868095 --- /dev/null +++ b/packages/polling-controller/package.json @@ -0,0 +1,58 @@ +{ + "name": "@metamask/polling-controller", + "version": "0.0.0", + "description": "Polling Controller is the base for controllers that polling by networkClientId", + "keywords": [ + "MetaMask", + "Ethereum" + ], + "homepage": "https://github.com/MetaMask/core/tree/main/packages/polling-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/polling-controller", + "publish:preview": "yarn npm publish --tag preview", + "test": "jest", + "test:watch": "jest --watch" + }, + "dependencies": { + "@metamask/base-controller": "^3.2.1", + "@metamask/controller-utils": "^5.0.0", + "@metamask/network-controller": "^13.0.0", + "@metamask/utils": "^6.2.0", + "@types/uuid": "^8.3.0", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@metamask/auto-changelog": "^3.1.0", + "@types/jest": "^27.4.1", + "deepmerge": "^4.2.2", + "jest": "^27.5.1", + "ts-jest": "^27.1.4", + "typedoc": "^0.23.15", + "typedoc-plugin-missing-exports": "^0.23.0", + "typescript": "~4.6.3" + }, + "peerDependencies": { + "@metamask/network-controller": "^13.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/packages/polling-controller/src/PollingController.test.ts b/packages/polling-controller/src/PollingController.test.ts new file mode 100644 index 00000000000..26c583d2c68 --- /dev/null +++ b/packages/polling-controller/src/PollingController.test.ts @@ -0,0 +1,308 @@ +import { ControllerMessenger } from '@metamask/base-controller'; + +import type { PollingCompleteType } from './PollingController'; +import PollingController from './PollingController'; + +const TICK_TIME = 1000; + +const createExecutePollMock = () => { + const executePollMock = jest.fn().mockImplementation(async () => { + return true; + }); + return executePollMock; +}; + +describe('PollingController', () => { + describe('start', () => { + it('should start polling if not polling', () => { + jest.useFakeTimers(); + + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + controller.stopAll(); + expect(controller.executePoll).toHaveBeenCalledTimes(1); + }); + }); + describe('stop', () => { + it('should stop polling when called with a valid polling that was the only active pollingToken for a given networkClient', () => { + jest.useFakeTimers(); + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + const pollingToken = controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + controller.stop(pollingToken); + jest.advanceTimersByTime(TICK_TIME); + expect(controller.executePoll).toHaveBeenCalledTimes(1); + controller.stopAll(); + }); + it('should not stop polling if called with one of multiple active polling tokens for a given networkClient', async () => { + jest.useFakeTimers(); + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + const pollingToken1 = controller.start('mainnet'); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + controller.stop(pollingToken1); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).toHaveBeenCalledTimes(2); + controller.stopAll(); + }); + it('should error if no pollingToken is passed', () => { + jest.useFakeTimers(); + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + controller.start('mainnet'); + expect(() => { + controller.stop(undefined as unknown as any); + }).toThrow('pollingToken required'); + controller.stopAll(); + }); + it('should error if no matching pollingToken is found', () => { + jest.useFakeTimers(); + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + controller.start('mainnet'); + expect(() => { + controller.stop('potato'); + }).toThrow('pollingToken not found'); + controller.stopAll(); + }); + }); + describe('poll', () => { + it('should call executePoll if polling', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).toHaveBeenCalledTimes(2); + }); + it('should continue calling executePoll when start is called again with the same networkClientId', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + controller.start('mainnet'); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).toHaveBeenCalledTimes(2); + controller.stopAll(); + }); + it('should publish "pollingComplete" when stop is called', async () => { + jest.useFakeTimers(); + const pollingComplete: any = jest.fn(); + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const name = 'PollingController'; + + const mockMessenger = new ControllerMessenger< + any, + PollingCompleteType + >(); + + mockMessenger.subscribe(`${name}:pollingComplete`, pollingComplete); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name, + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + const pollingToken = controller.start('mainnet'); + controller.stop(pollingToken); + expect(pollingComplete).toHaveBeenCalledTimes(1); + }); + it('should poll at the interval length passed via the constructor', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME * 3, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).not.toHaveBeenCalled(); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).not.toHaveBeenCalled(); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll).toHaveBeenCalledTimes(1); + jest.advanceTimersByTime(TICK_TIME * 3); + await Promise.resolve(); + expect(controller.executePoll).toHaveBeenCalledTimes(2); + }); + }); + describe('multiple networkClientIds', () => { + it('should poll for each networkClientId', async () => { + jest.useFakeTimers(); + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME, + }); + controller.start('mainnet'); + controller.start('rinkeby'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['rinkeby'], + ]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['rinkeby'], + ['mainnet'], + ['rinkeby'], + ]); + controller.stopAll(); + }); + + it('should poll multiple networkClientIds at the interval length passed via the constructor', async () => { + jest.useFakeTimers(); + + class MyGasFeeController extends PollingController { + executePoll = createExecutePollMock(); + } + const mockMessenger = new ControllerMessenger(); + + const controller = new MyGasFeeController({ + messenger: mockMessenger, + metadata: {}, + name: 'PollingController', + state: { foo: 'bar' }, + pollingIntervalLength: TICK_TIME * 2, + }); + controller.start('mainnet'); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + controller.start('sepolia'); + expect(controller.executePoll.mock.calls).toMatchObject([]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([['mainnet']]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['sepolia'], + ]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['sepolia'], + ['mainnet'], + ]); + jest.advanceTimersByTime(TICK_TIME); + await Promise.resolve(); + expect(controller.executePoll.mock.calls).toMatchObject([ + ['mainnet'], + ['sepolia'], + ['mainnet'], + ['sepolia'], + ]); + }); + }); +}); diff --git a/packages/polling-controller/src/PollingController.ts b/packages/polling-controller/src/PollingController.ts new file mode 100644 index 00000000000..a9595cc4e84 --- /dev/null +++ b/packages/polling-controller/src/PollingController.ts @@ -0,0 +1,150 @@ +import { BaseControllerV2 } from '@metamask/base-controller'; +import type { + RestrictedControllerMessenger, + StateMetadata, +} from '@metamask/base-controller'; +import type { NetworkClientId } from '@metamask/network-controller'; +import type { Json } from '@metamask/utils'; +import { v4 as random } from 'uuid'; + +export type PollingCompleteType = { + type: `${N}:pollingComplete`; + payload: [string]; +}; + +/** + * PollingController is an abstract class that implements the polling + * functionality for a controller. It is meant to be extended by a controller + * that needs to poll for data by networkClientId. + * + */ +export default abstract class PollingController< + Name extends string, + State extends Record, + messenger extends RestrictedControllerMessenger< + Name, + any, + PollingCompleteType | any, + string, + string + >, +> extends BaseControllerV2 { + readonly #intervalLength: number; + + private readonly networkClientIdTokensMap: Map> = + new Map(); + + private readonly intervalIds: Record = {}; + + constructor({ + name, + state, + messenger, + metadata, + pollingIntervalLength, + }: { + name: Name; + state: State; + metadata: StateMetadata; + messenger: messenger; + pollingIntervalLength: number; + }) { + super({ + name, + state, + messenger, + metadata, + }); + + if (!pollingIntervalLength) { + throw new Error('pollingIntervalLength required for PollingController'); + } + + this.#intervalLength = pollingIntervalLength; + } + + /** + * Starts polling for a networkClientId + * + * @param networkClientId - The networkClientId to start polling for + * @returns void + */ + start(networkClientId: NetworkClientId) { + const innerPollToken = random(); + if (this.networkClientIdTokensMap.has(networkClientId)) { + const set = this.networkClientIdTokensMap.get(networkClientId); + set?.add(innerPollToken); + } else { + const set = new Set(); + set.add(innerPollToken); + this.networkClientIdTokensMap.set(networkClientId, set); + } + this.#poll(networkClientId); + return innerPollToken; + } + + /** + * Stops polling for all networkClientIds + */ + stopAll() { + this.networkClientIdTokensMap.forEach((tokens, _networkClientId) => { + tokens.forEach((token) => { + this.stop(token); + }); + }); + } + + /** + * Stops polling for a networkClientId + * + * @param pollingToken - The polling token to stop polling for + */ + stop(pollingToken: string) { + if (!pollingToken) { + throw new Error('pollingToken required'); + } + let found = false; + this.networkClientIdTokensMap.forEach((tokens, networkClientId) => { + if (tokens.has(pollingToken)) { + found = true; + this.networkClientIdTokensMap + .get(networkClientId) + ?.delete(pollingToken); + if (this.networkClientIdTokensMap.get(networkClientId)?.size === 0) { + clearTimeout(this.intervalIds[networkClientId]); + delete this.intervalIds[networkClientId]; + this.networkClientIdTokensMap.delete(networkClientId); + this.messagingSystem.publish( + `${this.name}:pollingComplete`, + networkClientId, + ); + } + } + }); + if (!found) { + throw new Error('pollingToken not found'); + } + } + + /** + * Executes the poll for a networkClientId + * + * @param networkClientId - The networkClientId to execute the poll for + */ + abstract executePoll(networkClientId: NetworkClientId): Promise; + + #poll(networkClientId: NetworkClientId) { + if (this.intervalIds[networkClientId]) { + clearTimeout(this.intervalIds[networkClientId]); + delete this.intervalIds[networkClientId]; + } + this.intervalIds[networkClientId] = setTimeout(async () => { + try { + await this.executePoll(networkClientId); + } catch (error) { + console.error(error); + } + this.#poll(networkClientId); + }, this.#intervalLength); + } +} diff --git a/packages/polling-controller/src/index.ts b/packages/polling-controller/src/index.ts new file mode 100644 index 00000000000..1458c0cfe9b --- /dev/null +++ b/packages/polling-controller/src/index.ts @@ -0,0 +1 @@ +export { default as PollingController } from './PollingController'; diff --git a/packages/polling-controller/tsconfig.build.json b/packages/polling-controller/tsconfig.build.json new file mode 100644 index 00000000000..ac0df4920c6 --- /dev/null +++ b/packages/polling-controller/tsconfig.build.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.packages.build.json", + "compilerOptions": { + "baseUrl": "./", + "outDir": "./dist", + "rootDir": "./src" + }, + "references": [ + { "path": "../base-controller/tsconfig.build.json" }, + { "path": "../controller-utils/tsconfig.build.json" }, + { "path": "../network-controller/tsconfig.build.json" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/polling-controller/tsconfig.json b/packages/polling-controller/tsconfig.json new file mode 100644 index 00000000000..4bbb0be81b1 --- /dev/null +++ b/packages/polling-controller/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.packages.json", + "compilerOptions": { + "baseUrl": "./" + }, + "references": [ + { "path": "../base-controller" }, + { "path": "../controller-utils" }, + { "path": "../network-controller" } + ], + "include": ["../../types", "./src"] +} diff --git a/packages/polling-controller/typedoc.json b/packages/polling-controller/typedoc.json new file mode 100644 index 00000000000..c9da015dbf8 --- /dev/null +++ b/packages/polling-controller/typedoc.json @@ -0,0 +1,7 @@ +{ + "entryPoints": ["./src/index.ts"], + "excludePrivate": true, + "hideGenerator": true, + "out": "docs", + "tsconfig": "./tsconfig.build.json" +} diff --git a/yarn.lock b/yarn.lock index c51530c5a7e..056dd4d5f72 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2121,6 +2121,29 @@ __metadata: languageName: unknown linkType: soft +"@metamask/polling-controller@workspace:packages/polling-controller": + version: 0.0.0-use.local + resolution: "@metamask/polling-controller@workspace:packages/polling-controller" + dependencies: + "@metamask/auto-changelog": ^3.1.0 + "@metamask/base-controller": ^3.2.1 + "@metamask/controller-utils": ^5.0.0 + "@metamask/network-controller": ^13.0.0 + "@metamask/utils": ^6.2.0 + "@types/jest": ^27.4.1 + "@types/uuid": ^8.3.0 + deepmerge: ^4.2.2 + jest: ^27.5.1 + ts-jest: ^27.1.4 + typedoc: ^0.23.15 + typedoc-plugin-missing-exports: ^0.23.0 + typescript: ~4.6.3 + uuid: ^8.3.2 + peerDependencies: + "@metamask/network-controller": ^13.0.0 + 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" From 5e48e1ca51819397d1d371b87e8d394671c2887a Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Tue, 26 Sep 2023 12:40:14 -0230 Subject: [PATCH 06/14] chore: Remove AbortController polyfill (#1726) ## Explanation The `AbortController` polyfill has not been required since updating to a minimum Node.js version of v16 in #1262. This API has been [built into Node.js since v15.4.0](https://nodejs.org/docs/latest-v16.x/api/globals.html#class-abortcontroller), and is included in all versions of v16. It is also supported by [all browsers that our extension supports](https://developer.mozilla.org/en-US/docs/Web/API/AbortController#browser_compatibility), and has been [supported by React Native since v0.60.0](https://github.com/react-native-community/releases/blob/master/CHANGELOG.md#v0600). This helps unblock the TypeScript update to v4.8.4. The update resulted in some type errors from this polyfill package. ## References None ## Changelog ### `@metamask/assets-controllers` ### Removed - **BREAKING:** Remove AbortController polyfill - This package now assumes that the AbortController global exists ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've highlighted breaking changes using the "BREAKING" category above as appropriate --- packages/assets-controllers/package.json | 1 - .../src/TokenListController.ts | 7 +++--- .../src/TokensController.ts | 7 +++--- .../src/token-service.test.ts | 25 +++++++++---------- packages/controller-utils/package.json | 1 - yarn.lock | 18 ------------- 6 files changed, 18 insertions(+), 41 deletions(-) diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 031ba85cf5d..ea014ec3cb3 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -44,7 +44,6 @@ "@metamask/rpc-errors": "^5.1.1", "@metamask/utils": "^6.2.0", "@types/uuid": "^8.3.0", - "abort-controller": "^3.0.0", "async-mutex": "^0.2.6", "ethereumjs-util": "^7.0.10", "immer": "^9.0.6", diff --git a/packages/assets-controllers/src/TokenListController.ts b/packages/assets-controllers/src/TokenListController.ts index 8edde3e2a68..eef38badaa4 100644 --- a/packages/assets-controllers/src/TokenListController.ts +++ b/packages/assets-controllers/src/TokenListController.ts @@ -6,7 +6,6 @@ import type { NetworkState, } from '@metamask/network-controller'; import type { Hex } from '@metamask/utils'; -import { AbortController as WhatwgAbortController } from 'abort-controller'; import { Mutex } from 'async-mutex'; import type { Patch } from 'immer'; @@ -96,7 +95,7 @@ export class TokenListController extends BaseControllerV2< private chainId: Hex; - private abortController: WhatwgAbortController; + private abortController: AbortController; /** * Creates a TokenListController instance. @@ -139,7 +138,7 @@ export class TokenListController extends BaseControllerV2< this.cacheRefreshThreshold = cacheRefreshThreshold; this.chainId = chainId; this.updatePreventPollingOnNetworkRestart(preventPollingOnNetworkRestart); - this.abortController = new WhatwgAbortController(); + this.abortController = new AbortController(); if (onNetworkStateChange) { onNetworkStateChange(async (networkControllerState) => { await this.#onNetworkControllerStateChange(networkControllerState); @@ -163,7 +162,7 @@ export class TokenListController extends BaseControllerV2< async #onNetworkControllerStateChange(networkControllerState: NetworkState) { if (this.chainId !== networkControllerState.providerConfig.chainId) { this.abortController.abort(); - this.abortController = new WhatwgAbortController(); + this.abortController = new AbortController(); this.chainId = networkControllerState.providerConfig.chainId; if (this.state.preventPollingOnNetworkRestart) { this.clearingTokenListData(); diff --git a/packages/assets-controllers/src/TokensController.ts b/packages/assets-controllers/src/TokensController.ts index 33c1ee9c5d8..1f04ecccc23 100644 --- a/packages/assets-controllers/src/TokensController.ts +++ b/packages/assets-controllers/src/TokensController.ts @@ -23,7 +23,6 @@ import type { } from '@metamask/network-controller'; import type { PreferencesState } from '@metamask/preferences-controller'; import type { Hex } from '@metamask/utils'; -import { AbortController as WhatwgAbortController } from 'abort-controller'; import { Mutex } from 'async-mutex'; import { EventEmitter } from 'events'; import { v1 as random } from 'uuid'; @@ -125,7 +124,7 @@ export class TokensController extends BaseController< > { private readonly mutex = new Mutex(); - private abortController: WhatwgAbortController; + private abortController: AbortController; private readonly messagingSystem: TokensControllerMessenger; @@ -231,7 +230,7 @@ export class TokensController extends BaseController< }; this.initialize(); - this.abortController = new WhatwgAbortController(); + this.abortController = new AbortController(); this.getERC20TokenName = getERC20TokenName; this.getNetworkClientById = getNetworkClientById; @@ -253,7 +252,7 @@ export class TokensController extends BaseController< const { selectedAddress } = this.config; const { chainId } = providerConfig; this.abortController.abort(); - this.abortController = new WhatwgAbortController(); + this.abortController = new AbortController(); this.configure({ chainId }); this.update({ tokens: allTokens[chainId]?.[selectedAddress] || [], diff --git a/packages/assets-controllers/src/token-service.test.ts b/packages/assets-controllers/src/token-service.test.ts index 7fa3c083b07..7e168dd6cc6 100644 --- a/packages/assets-controllers/src/token-service.test.ts +++ b/packages/assets-controllers/src/token-service.test.ts @@ -1,5 +1,4 @@ import { toHex } from '@metamask/controller-utils'; -import { AbortController as WhatwgAbortController } from 'abort-controller'; import nock from 'nock'; import { @@ -140,7 +139,7 @@ const sampleChainId = toHex(sampleDecimalChainId); describe('Token service', () => { describe('fetchTokenList', () => { it('should call the tokens api and return the list of tokens', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) .reply(200, sampleTokenList) @@ -152,7 +151,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch is aborted', async () => { - const abortController = new WhatwgAbortController(); + const abortController = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) // well beyond time it will take to abort @@ -170,7 +169,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch fails with a network error', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) .replyWithError('Example network error') @@ -182,7 +181,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch fails with an unsuccessful status code', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) .reply(500) @@ -194,7 +193,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch fails with a timeout', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) // well beyond timeout @@ -212,7 +211,7 @@ describe('Token service', () => { describe('fetchTokenMetadata', () => { it('should call the api to return the token metadata for eth address provided', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get( `/token/${sampleDecimalChainId}?address=0x514910771af9ca656af840dff83e8264ecf986ca`, @@ -230,7 +229,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch is aborted', async () => { - const abortController = new WhatwgAbortController(); + const abortController = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) // well beyond time it will take to abort @@ -249,7 +248,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch fails with a network error', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) .replyWithError('Example network error') @@ -265,7 +264,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch fails with an unsuccessful status code', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) .reply(500) @@ -281,7 +280,7 @@ describe('Token service', () => { }); it('should return undefined if the fetch fails with a timeout', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) // well beyond timeout @@ -300,7 +299,7 @@ describe('Token service', () => { }); it('should throw error if fetching from non supported network', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); await expect( fetchTokenMetadata( toHex(5), @@ -312,7 +311,7 @@ describe('Token service', () => { }); it('should call the tokens api and return undefined', async () => { - const { signal } = new WhatwgAbortController(); + const { signal } = new AbortController(); nock(TOKEN_END_POINT_API) .get(`/tokens/${sampleDecimalChainId}`) .reply(404, undefined) diff --git a/packages/controller-utils/package.json b/packages/controller-utils/package.json index 491ad0301db..60904687711 100644 --- a/packages/controller-utils/package.json +++ b/packages/controller-utils/package.json @@ -40,7 +40,6 @@ "devDependencies": { "@metamask/auto-changelog": "^3.1.0", "@types/jest": "^27.4.1", - "abort-controller": "^3.0.0", "deepmerge": "^4.2.2", "jest": "^27.5.1", "nock": "^13.3.1", diff --git a/yarn.lock b/yarn.lock index 056dd4d5f72..d2fb062ebe0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1407,7 +1407,6 @@ __metadata: "@types/jest": ^27.4.1 "@types/node": ^16.18.24 "@types/uuid": ^8.3.0 - abort-controller: ^3.0.0 async-mutex: ^0.2.6 deepmerge: ^4.2.2 ethereumjs-util: ^7.0.10 @@ -1505,7 +1504,6 @@ __metadata: "@metamask/utils": ^6.2.0 "@spruceid/siwe-parser": 1.1.3 "@types/jest": ^27.4.1 - abort-controller: ^3.0.0 deepmerge: ^4.2.2 eth-ens-namehash: ^2.0.8 eth-rpc-errors: ^4.0.2 @@ -3449,15 +3447,6 @@ __metadata: languageName: node linkType: hard -"abort-controller@npm:^3.0.0": - version: 3.0.0 - resolution: "abort-controller@npm:3.0.0" - dependencies: - event-target-shim: ^5.0.0 - checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75 - languageName: node - linkType: hard - "acorn-globals@npm:^6.0.0": version: 6.0.0 resolution: "acorn-globals@npm:6.0.0" @@ -5596,13 +5585,6 @@ __metadata: languageName: node linkType: hard -"event-target-shim@npm:^5.0.0": - version: 5.0.1 - resolution: "event-target-shim@npm:5.0.1" - checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166 - languageName: node - linkType: hard - "evp_bytestokey@npm:^1.0.3": version: 1.0.3 resolution: "evp_bytestokey@npm:1.0.3" From ff9e2b3cdb0ab593d9669eb43b52248bbaaf0877 Mon Sep 17 00:00:00 2001 From: Matthew Walsh Date: Tue, 26 Sep 2023 16:45:59 +0100 Subject: [PATCH 07/14] Support rate limiting in name providers (#1715) Add onlyUpdateAfterDelay option to updateProposedNames method. Add updateDelay to NameProviderSourceResult. Add optional isEnabled callbacks to each NameProvider implementation. Only update existing proposed names in state if the new value is not undefined. --- packages/name-controller/package.json | 2 + .../src/NameController.test.ts | 1200 ++++++++++++++--- .../name-controller/src/NameController.ts | 228 ++-- packages/name-controller/src/logger.ts | 5 + .../name-controller/src/providers/ens.test.ts | 35 + packages/name-controller/src/providers/ens.ts | 49 +- .../src/providers/etherscan.test.ts | 101 +- .../src/providers/etherscan.ts | 123 +- .../src/providers/lens.test.ts | 32 + .../name-controller/src/providers/lens.ts | 55 +- .../src/providers/token.test.ts | 50 + .../name-controller/src/providers/token.ts | 48 +- packages/name-controller/src/types.ts | 6 + yarn.lock | 2 + 14 files changed, 1554 insertions(+), 382 deletions(-) create mode 100644 packages/name-controller/src/logger.ts diff --git a/packages/name-controller/package.json b/packages/name-controller/package.json index f2e4f28c228..38a0f99f0fc 100644 --- a/packages/name-controller/package.json +++ b/packages/name-controller/package.json @@ -30,6 +30,8 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", + "@metamask/utils": "^6.2.0", + "async-mutex": "^0.2.6", "immer": "^9.0.6" }, "devDependencies": { diff --git a/packages/name-controller/src/NameController.test.ts b/packages/name-controller/src/NameController.test.ts index de557e14492..802216fe90a 100644 --- a/packages/name-controller/src/NameController.test.ts +++ b/packages/name-controller/src/NameController.test.ts @@ -33,9 +33,14 @@ Date.now = jest.fn().mockReturnValue(TIME_MOCK * 1000); * Creates a mock name provider. * * @param index - Index of the provider used to generate unique values. + * @param options - Additional options to configure the mock provider. + * @param options.updateDelay - Optional update delay to return. * @returns Mock instance of a name provider. */ -function createMockProvider(index: number): jest.Mocked { +function createMockProvider( + index: number, + { updateDelay }: { updateDelay?: number } = {}, +): jest.Mocked { return { getMetadata: jest.fn().mockReturnValue({ sourceIds: { @@ -52,6 +57,7 @@ function createMockProvider(index: number): jest.Mocked { PROPOSED_NAME_MOCK + String(index), `${PROPOSED_NAME_MOCK + String(index)}_2`, ], + updateDelay, }, }, }), @@ -81,7 +87,6 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: NAME_MOCK, sourceId: `${SOURCE_ID_MOCK}1`, - proposedNamesLastUpdated: null, proposedNames: {}, }, }, @@ -103,9 +108,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: null, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: null, + updateDelay: null, + }, }, }, }, @@ -125,9 +133,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: NAME_MOCK, sourceId: `${SOURCE_ID_MOCK}1`, - proposedNamesLastUpdated: null, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: null, + updateDelay: null, + }, }, }, }, @@ -144,9 +155,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: null, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: null, + updateDelay: null, + }, }, }, }, @@ -165,9 +179,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: NAME_MOCK, sourceId: null, - proposedNamesLastUpdated: null, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: null, + updateDelay: null, + }, }, }, }, @@ -190,9 +207,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: NAME_MOCK, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, }, @@ -211,15 +231,17 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: NAME_MOCK, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, [alternateChainId]: { name: alternateName, sourceId: null, - proposedNamesLastUpdated: null, proposedNames: {}, }, }, @@ -241,9 +263,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: NAME_MOCK, sourceId: SOURCE_ID_MOCK, - proposedNamesLastUpdated: null, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: null, + updateDelay: null, + }, }, }, }, @@ -262,9 +287,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: null, proposedNames: { - [SOURCE_ID_MOCK]: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + [SOURCE_ID_MOCK]: { + proposedNames: [PROPOSED_NAME_MOCK, PROPOSED_NAME_2_MOCK], + lastRequestTime: null, + updateDelay: null, + }, }, }, }, @@ -382,7 +410,7 @@ describe('NameController', () => { 'creates entry with proposed names if value is new%s', async (_, getExistingState) => { const provider1 = createMockProvider(1); - const provider2 = createMockProvider(2); + const provider2 = createMockProvider(2, { updateDelay: 3 }); const controller = new NameController({ ...CONTROLLER_ARGS_MOCK, @@ -402,16 +430,23 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [ - `${PROPOSED_NAME_MOCK}1`, - `${PROPOSED_NAME_MOCK}1_2`, - ], - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: 3, + }, }, }, }, @@ -454,10 +489,17 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: 12, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: ['ShouldBeDeleted1'], - [`${SOURCE_ID_MOCK}2`]: ['ShouldBeDeleted2'], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldBeDeleted1'], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldBeDeleted2'], + lastRequestTime: 12, + updateDelay: null, + }, }, }, }, @@ -475,16 +517,23 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [ - `${PROPOSED_NAME_MOCK}1`, - `${PROPOSED_NAME_MOCK}1_2`, - ], - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, }, @@ -526,9 +575,12 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: 12, proposedNames: { - [`${SOURCE_ID_MOCK}3`]: ['ShouldBeDeleted3'], + [`${SOURCE_ID_MOCK}3`]: { + proposedNames: ['ShouldBeDeleted3'], + lastRequestTime: 12, + updateDelay: null, + }, }, }, }, @@ -546,16 +598,23 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [ - `${PROPOSED_NAME_MOCK}1`, - `${PROPOSED_NAME_MOCK}1_2`, - ], - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, }, @@ -592,72 +651,20 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, - proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [], - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], - }, - }, - }, - }, - }); - - expect(result).toStrictEqual({ - results: { - [`${SOURCE_ID_MOCK}1`]: { - proposedNames: [], - error: undefined, - }, - [`${SOURCE_ID_MOCK}2`]: { - proposedNames: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], - error: undefined, - }, - }, - }); - }); - - it('stores empty array if proposed names is undefined and no error', async () => { - const provider1 = createMockProvider(1); - const provider2 = createMockProvider(2); - - provider1.getProposedNames.mockResolvedValue({ - results: { - [`${SOURCE_ID_MOCK}1`]: { - proposedNames: undefined, - error: undefined, - }, - }, - }); - - const controller = new NameController({ - ...CONTROLLER_ARGS_MOCK, - providers: [provider1, provider2], - }); - - const result = await controller.updateProposedNames({ - value: VALUE_MOCK, - type: NameType.ETHEREUM_ADDRESS, - }); - - expect(controller.state.names).toStrictEqual({ - [NameType.ETHEREUM_ADDRESS]: { - [VALUE_MOCK]: { - [CHAIN_ID_MOCK]: { - name: null, - sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [], - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, }, @@ -681,7 +688,7 @@ describe('NameController', () => { }); }); - it('updates provider state', async () => { + it('updates source state', async () => { const provider1 = createMockProvider(1); const provider2 = createMockProvider(2); @@ -737,11 +744,22 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: 12, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: ['ShouldNotBeDeleted1'], - [`${SOURCE_ID_MOCK}2`]: ['ShouldNotBeDeleted2'], - [`${SOURCE_ID_MOCK}3`]: ['ShouldNotBeDeleted3'], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeDeleted1'], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeDeleted2'], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}3`]: { + proposedNames: ['ShouldNotBeDeleted3'], + lastRequestTime: 12, + updateDelay: null, + }, }, }, }, @@ -759,26 +777,44 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: 12, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: ['ShouldNotBeDeleted1'], - [`${SOURCE_ID_MOCK}2`]: ['ShouldNotBeDeleted2'], - [`${SOURCE_ID_MOCK}3`]: ['ShouldNotBeDeleted3'], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeDeleted1'], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeDeleted2'], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}3`]: { + proposedNames: ['ShouldNotBeDeleted3'], + lastRequestTime: 12, + updateDelay: null, + }, }, }, [alternateChainId]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [ - `${PROPOSED_NAME_MOCK}1`, - `${PROPOSED_NAME_MOCK}1_2`, - ], - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, }, @@ -786,67 +822,144 @@ describe('NameController', () => { }); }); - describe('with error', () => { - it('returns result errors if unhandled error while getting proposed name using provider', async () => { - const provider1 = createMockProvider(1); - const provider2 = createMockProvider(2); - const error = new Error('TestError'); + it('ignores source IDs in response if not in metadata', async () => { + const provider1 = createMockProvider(1); - provider1.getProposedNames.mockRejectedValue(error); + provider1.getProposedNames.mockResolvedValue({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + }, + }, + }); - const controller = new NameController({ - ...CONTROLLER_ARGS_MOCK, - providers: [provider1, provider2], - }); + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1], + }); - const result = await controller.updateProposedNames({ - value: VALUE_MOCK, - type: NameType.ETHEREUM_ADDRESS, - }); + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); - expect(controller.state.names).toStrictEqual({ - [NameType.ETHEREUM_ADDRESS]: { - [VALUE_MOCK]: { - [CHAIN_ID_MOCK]: { - name: null, - sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, - proposedNames: { - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, ], + lastRequestTime: TIME_MOCK, + updateDelay: null, }, }, }, }, - }); + }, + }); - expect(result).toStrictEqual({ - results: { - [`${SOURCE_ID_MOCK}1`]: { - proposedNames: undefined, - error, - }, - [`${SOURCE_ID_MOCK}2`]: { - proposedNames: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], - error: undefined, + expect(result).toStrictEqual({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + error: undefined, + }, + }, + }); + }); + + it('ignores empty or undefined proposed names', async () => { + const provider1 = createMockProvider(1); + + provider1.getProposedNames.mockResolvedValue({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + undefined, + null, + '', + `${PROPOSED_NAME_MOCK}1_2`, + ], + }, + }, + } as any); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1], + }); + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, }, }, - }); + }, }); - it('returns result errors if response error while getting proposed name using provider', async () => { + expect(result).toStrictEqual({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + error: undefined, + }, + }, + }); + }); + + describe('does not update existing proposed names if', () => { + it('new value is undefined', async () => { const provider1 = createMockProvider(1); const provider2 = createMockProvider(2); - const error = new Error('TestError'); provider1.getProposedNames.mockResolvedValue({ + results: { + [`${SOURCE_ID_MOCK}1`]: { proposedNames: undefined }, + }, + }); + + provider2.getProposedNames.mockResolvedValue({ results: {}, - error, }); const controller = new NameController({ @@ -854,28 +967,283 @@ describe('NameController', () => { providers: [provider1, provider2], }); - const result = await controller.updateProposedNames({ - value: VALUE_MOCK, - type: NameType.ETHEREUM_ADDRESS, - }); - - expect(controller.state.names).toStrictEqual({ + controller.state.names = { [NameType.ETHEREUM_ADDRESS]: { [VALUE_MOCK]: { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: 11, + updateDelay: 1, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: 12, + updateDelay: 2, + }, }, }, }, }, - }); + }; + + await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); + }); + + it('result error', async () => { + const provider1 = createMockProvider(1); + + provider1.getProposedNames.mockResolvedValue({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [PROPOSED_NAME_MOCK], + error: new Error('TestError'), + }, + }, + }); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1], + }); + + controller.state.names = { + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: 11, + updateDelay: 1, + }, + }, + }, + }, + }, + }; + + await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); + }); + + it('response error', async () => { + const provider1 = createMockProvider(1); + + provider1.getProposedNames.mockResolvedValue({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [PROPOSED_NAME_MOCK], + }, + }, + error: new Error('TestError'), + }); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1], + }); + + controller.state.names = { + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: 11, + updateDelay: 1, + }, + }, + }, + }, + }, + }; + + await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); + }); + }); + + describe('with error', () => { + it('returns result errors if unhandled error while getting proposed name using provider', async () => { + const provider1 = createMockProvider(1); + const provider2 = createMockProvider(2); + const error = new Error('TestError'); + + provider1.getProposedNames.mockRejectedValue(error); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1, provider2], + }); + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); + + expect(result).toStrictEqual({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: undefined, + error, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + error: undefined, + }, + }, + }); + }); + + it('returns result errors if response error while getting proposed name using provider', async () => { + const provider1 = createMockProvider(1); + const provider2 = createMockProvider(2); + const error = new Error('TestError'); + + provider1.getProposedNames.mockResolvedValue({ + results: {}, + error, + }); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1, provider2], + }); + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); expect(result).toStrictEqual({ results: { @@ -894,7 +1262,7 @@ describe('NameController', () => { }); }); - it('stores null if result error while getting proposed name using provider', async () => { + it('stores emtpy array if result error while getting proposed name using provider', async () => { const provider1 = createMockProvider(1); const provider2 = createMockProvider(2); const error = new Error('TestError'); @@ -923,13 +1291,20 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: null, - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, }, @@ -971,11 +1346,22 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: 12, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: ['ShouldNotBeDeleted1'], - [`${SOURCE_ID_MOCK}2`]: ['ShouldBeDeleted2'], - [`${SOURCE_ID_MOCK}3`]: ['ShouldNotBeDeleted3'], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeDeleted1'], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldBeDeleted2'], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}3`]: { + proposedNames: ['ShouldNotBeDeleted3'], + lastRequestTime: 12, + updateDelay: null, + }, }, }, }, @@ -994,14 +1380,25 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [`ShouldNotBeDeleted1`], - [`${SOURCE_ID_MOCK}2`]: [ - `${PROPOSED_NAME_MOCK}2`, - `${PROPOSED_NAME_MOCK}2_2`, - ], - [`${SOURCE_ID_MOCK}3`]: ['ShouldNotBeDeleted3'], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [`ShouldNotBeDeleted1`], + lastRequestTime: 12, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}3`]: { + proposedNames: ['ShouldNotBeDeleted3'], + lastRequestTime: 12, + updateDelay: null, + }, }, }, }, @@ -1099,12 +1496,15 @@ describe('NameController', () => { [CHAIN_ID_MOCK]: { name: null, sourceId: null, - proposedNamesLastUpdated: TIME_MOCK, proposedNames: { - [`${SOURCE_ID_MOCK}1`]: [ - `${PROPOSED_NAME_MOCK}1`, - `${PROPOSED_NAME_MOCK}1_2`, - ], + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, }, }, }, @@ -1212,5 +1612,381 @@ describe('NameController', () => { ); }); }); + + describe('with onlyUpdateAfterDelay', () => { + it('does not update if no updateDelay and controller delay not elapsed', async () => { + const provider1 = createMockProvider(1); + const provider2 = createMockProvider(2); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1, provider2], + updateDelay: 123, + }); + + controller.state.names = { + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK - 122, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: TIME_MOCK - 121, + updateDelay: null, + }, + }, + }, + }, + }, + }; + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + onlyUpdateAfterDelay: true, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK - 122, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: TIME_MOCK - 121, + updateDelay: null, + }, + }, + }, + }, + }, + }); + + expect(result).toStrictEqual({ + results: {}, + }); + }); + + it('does not update if updateDelay not elapsed', async () => { + const provider1 = createMockProvider(1); + const provider2 = createMockProvider(2); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1, provider2], + }); + + controller.state.names = { + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK - 9, + updateDelay: 10, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: TIME_MOCK - 6, + updateDelay: 7, + }, + }, + }, + }, + }, + }; + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + onlyUpdateAfterDelay: true, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK - 9, + updateDelay: 10, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: TIME_MOCK - 6, + updateDelay: 7, + }, + }, + }, + }, + }, + }); + + expect(result).toStrictEqual({ + results: {}, + }); + }); + + it('updates if controller delay elapsed', async () => { + const provider1 = createMockProvider(1); + const provider2 = createMockProvider(2); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1, provider2], + updateDelay: 123, + }); + + controller.state.names = { + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK - 123, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: TIME_MOCK - 124, + updateDelay: null, + }, + }, + }, + }, + }, + }; + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + onlyUpdateAfterDelay: true, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); + + expect(result).toStrictEqual({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + error: undefined, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + error: undefined, + }, + }, + }); + }); + + it('updates if updateDelay elapsed', async () => { + const provider1 = createMockProvider(1); + const provider2 = createMockProvider(2); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1, provider2], + }); + + controller.state.names = { + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: ['ShouldNotBeUpdated1'], + lastRequestTime: TIME_MOCK - 10, + updateDelay: 10, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: ['ShouldNotBeUpdated2'], + lastRequestTime: TIME_MOCK - 16, + updateDelay: 15, + }, + }, + }, + }, + }, + }; + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + onlyUpdateAfterDelay: true, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); + + expect(result).toStrictEqual({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + error: undefined, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + error: undefined, + }, + }, + }); + }); + + it('updates if no proposed name entry', async () => { + const provider1 = createMockProvider(1); + const provider2 = createMockProvider(2); + + const controller = new NameController({ + ...CONTROLLER_ARGS_MOCK, + providers: [provider1, provider2], + }); + + controller.state.names = {} as any; + + const result = await controller.updateProposedNames({ + value: VALUE_MOCK, + type: NameType.ETHEREUM_ADDRESS, + onlyUpdateAfterDelay: true, + }); + + expect(controller.state.names).toStrictEqual({ + [NameType.ETHEREUM_ADDRESS]: { + [VALUE_MOCK]: { + [CHAIN_ID_MOCK]: { + name: null, + sourceId: null, + proposedNames: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + lastRequestTime: TIME_MOCK, + updateDelay: null, + }, + }, + }, + }, + }, + }); + + expect(result).toStrictEqual({ + results: { + [`${SOURCE_ID_MOCK}1`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}1`, + `${PROPOSED_NAME_MOCK}1_2`, + ], + error: undefined, + }, + [`${SOURCE_ID_MOCK}2`]: { + proposedNames: [ + `${PROPOSED_NAME_MOCK}2`, + `${PROPOSED_NAME_MOCK}2_2`, + ], + error: undefined, + }, + }, + }); + }); + }); }); }); diff --git a/packages/name-controller/src/NameController.ts b/packages/name-controller/src/NameController.ts index f4475a178bb..cb357dcf080 100644 --- a/packages/name-controller/src/NameController.ts +++ b/packages/name-controller/src/NameController.ts @@ -6,9 +6,12 @@ import type { NameProvider, NameProviderRequest, NameProviderResult, + NameProviderSourceResult, } from './types'; import { NameType } from './types'; +const DEFAULT_UPDATE_DELAY = 60 * 2; // 2 Minutes + const controllerName = 'NameController'; const stateMetadata = { @@ -23,11 +26,16 @@ const getDefaultState = () => ({ nameSources: {}, }); +export type ProposedNamesEntry = { + proposedNames: string[]; + lastRequestTime: number | null; + updateDelay: number | null; +}; + export type NameEntry = { name: string | null; sourceId: string | null; - proposedNames: Record; - proposedNamesLastUpdated: number | null; + proposedNames: Record; }; export type SourceEntry = { @@ -67,12 +75,14 @@ export type NameControllerOptions = { messenger: NameControllerMessenger; providers: NameProvider[]; state?: Partial; + updateDelay?: number; }; export type UpdateProposedNamesRequest = { value: string; type: NameType; sourceIds?: string[]; + onlyUpdateAfterDelay?: boolean; }; export type UpdateProposedNamesResult = { @@ -98,6 +108,8 @@ export class NameController extends BaseControllerV2< #providers: NameProvider[]; + #updateDelay: number; + /** * Construct a Name controller. * @@ -106,12 +118,14 @@ export class NameController extends BaseControllerV2< * @param options.messenger - Restricted controller messenger for the name controller. * @param options.providers - Array of name provider instances to propose names. * @param options.state - Initial state to set on the controller. + * @param options.updateDelay - The delay in seconds before a new request to a source should be made. */ constructor({ getChainId, messenger, providers, state, + updateDelay, }: NameControllerOptions) { super({ name: controllerName, @@ -122,6 +136,7 @@ export class NameController extends BaseControllerV2< this.#getChainId = getChainId; this.#providers = providers; + this.#updateDelay = updateDelay ?? DEFAULT_UPDATE_DELAY; } /** @@ -139,7 +154,10 @@ export class NameController extends BaseControllerV2< const { value, type, name, sourceId: requestSourceId } = request; const sourceId = requestSourceId ?? null; - this.#updateEntry(value, type, { name, sourceId }); + this.#updateEntry(value, type, (entry: NameEntry) => { + entry.name = name; + entry.sourceId = sourceId; + }); } /** @@ -177,46 +195,35 @@ export class NameController extends BaseControllerV2< providerResponses: NameProviderResult[], ) { const { value, type } = request; - const newProposedNames: { [sourceId: string]: string[] | null } = {}; - - for (const providerResponse of providerResponses) { - const { results, error: responseError } = providerResponse; + const currentTime = this.#getCurrentTimeSeconds(); - if (responseError) { - continue; - } + this.#updateEntry(value, type, (entry: NameEntry) => { + this.#removeDormantProposedNames(entry.proposedNames, type); - for (const sourceId of Object.keys(providerResponse.results)) { - const result = results[sourceId]; - const { proposedNames } = result; - let finalProposedNames = result.error ? null : proposedNames ?? []; - - if (finalProposedNames) { - finalProposedNames = finalProposedNames.filter( - (proposedName) => proposedName?.length, - ); - } - - newProposedNames[sourceId] = finalProposedNames; - } - } - - const variationKey = this.#getTypeVariationKey(type); + for (const providerResponse of providerResponses) { + const { results } = providerResponse; - const existingProposedNames = - this.state.names[type]?.[value]?.[variationKey]?.proposedNames; + for (const sourceId of Object.keys(providerResponse.results)) { + const result = results[sourceId]; + const { proposedNames, updateDelay } = result; - const existingProposedNamesWithoutDormant = - this.#removeDormantProposedNames(existingProposedNames, type); + const proposedNameEntry = entry.proposedNames[sourceId] ?? { + proposedNames: [], + lastRequestTime: null, + updateDelay: null, + }; - const proposedNames = { - ...existingProposedNamesWithoutDormant, - ...newProposedNames, - }; + entry.proposedNames[sourceId] = proposedNameEntry; - const proposedNamesLastUpdated = this.#getCurrentTimeSeconds(); + if (proposedNames) { + proposedNameEntry.proposedNames = proposedNames; + } - this.#updateEntry(value, type, { proposedNames, proposedNamesLastUpdated }); + proposedNameEntry.lastRequestTime = currentTime; + proposedNameEntry.updateDelay = updateDelay ?? null; + } + } + }); } #updateSourceState(providers: NameProvider[]) { @@ -245,22 +252,11 @@ export class NameController extends BaseControllerV2< const { results } = providerResponse; for (const sourceId of Object.keys(results)) { - const { proposedNames: resultProposedNames, error: resultError } = - results[sourceId]; - - let proposedNames = resultError - ? undefined - : resultProposedNames ?? []; - - if (proposedNames) { - proposedNames = proposedNames.filter( - (proposedName) => proposedName?.length, - ); - } + const { proposedNames, error } = results[sourceId]; acc.results[sourceId] = { proposedNames, - error: resultError, + error, }; } @@ -275,15 +271,37 @@ export class NameController extends BaseControllerV2< chainId: string, provider: NameProvider, ): Promise { - const { value, type, sourceIds: requestedSourceIds } = request; + const { + value, + type, + sourceIds: requestedSourceIds, + onlyUpdateAfterDelay, + } = request; + + const variationKey = this.#getTypeVariationKey(type); const supportedSourceIds = this.#getSourceIds(provider, type); + const currentTime = this.#getCurrentTimeSeconds(); + + const matchingSourceIds = supportedSourceIds.filter((sourceId) => { + if (requestedSourceIds && !requestedSourceIds.includes(sourceId)) { + return false; + } + + if (onlyUpdateAfterDelay) { + const entry = this.state.names[type]?.[value]?.[variationKey] ?? {}; + const proposedNamesEntry = entry.proposedNames?.[sourceId] ?? {}; + const lastRequestTime = proposedNamesEntry.lastRequestTime ?? 0; + const updateDelay = proposedNamesEntry.updateDelay ?? this.#updateDelay; + + if (currentTime - lastRequestTime < updateDelay) { + return false; + } + } - const matchingSourceIds = - requestedSourceIds?.filter((sourceId) => - supportedSourceIds.includes(sourceId), - ) ?? supportedSourceIds; + return true; + }); - if (requestedSourceIds && !matchingSourceIds.length) { + if (!matchingSourceIds.length) { return undefined; } @@ -304,35 +322,63 @@ export class NameController extends BaseControllerV2< responseError = error; } - let results = {}; + return this.#normalizeProviderResult( + response, + responseError, + matchingSourceIds, + ); + } - if (response?.results) { - results = Object.keys(response.results).reduce( - (acc: NameProviderResult['results'], sourceId) => { - if (!requestedSourceIds || requestedSourceIds.includes(sourceId)) { - acc[sourceId] = (response as NameProviderResult).results[sourceId]; - } + #normalizeProviderResult( + result: NameProviderResult | undefined, + responseError: unknown, + matchingSourceIds: string[], + ): NameProviderResult { + const error = responseError ?? undefined; + + const results = matchingSourceIds.reduce((acc, sourceId) => { + const sourceResult = result?.results?.[sourceId]; - return acc; - }, - {}, + const normalizedSourceResult = this.#normalizeProviderSourceResult( + sourceResult, + responseError, ); - } - if (responseError) { - results = supportedSourceIds.reduce( - (acc: NameProviderResult['results'], sourceId) => { - acc[sourceId] = { proposedNames: [], error: responseError }; - return acc; - }, - {}, + return { + ...acc, + [sourceId]: normalizedSourceResult, + }; + }, {}); + + return { results, error }; + } + + #normalizeProviderSourceResult( + result: NameProviderSourceResult | undefined, + responseError: unknown, + ): NameProviderSourceResult | undefined { + const error = result?.error ?? responseError ?? undefined; + const updateDelay = result?.updateDelay ?? undefined; + let proposedNames = error ? undefined : result?.proposedNames ?? undefined; + + if (proposedNames) { + proposedNames = proposedNames.filter( + (proposedName) => proposedName?.length, ); } - return { results, error: responseError }; + return { + proposedNames, + error, + updateDelay, + }; } - #updateEntry(value: string, type: NameType, data: Partial) { + #updateEntry( + value: string, + type: NameType, + callback: (entry: NameEntry) => void, + ) { const variationKey = this.#getTypeVariationKey(type); this.update((state) => { @@ -342,16 +388,14 @@ export class NameController extends BaseControllerV2< const variationEntries = typeEntries[value] || {}; typeEntries[value] = variationEntries; - const currentEntry = variationEntries[variationKey] ?? { + const entry = variationEntries[variationKey] ?? { proposedNames: {}, - proposedNamesLastUpdated: null, name: null, sourceId: null, }; + variationEntries[variationKey] = entry; - const updatedEntry = { ...currentEntry, ...data }; - - variationEntries[variationKey] = updatedEntry; + callback(entry); }); } @@ -506,23 +550,21 @@ export class NameController extends BaseControllerV2< } #removeDormantProposedNames( - proposedNames: Record, + proposedNames: Record, type: NameType, - ): Record { - if (!proposedNames || Object.keys(proposedNames).length === 0) { - return proposedNames; + ) { + if (Object.keys(proposedNames).length === 0) { + return; } const typeSourceIds = this.#getAllSourceIds(type); - return Object.keys(proposedNames) - .filter((sourceId) => typeSourceIds.includes(sourceId)) - .reduce( - (acc, sourceId) => ({ - ...acc, - [sourceId]: proposedNames[sourceId], - }), - {}, - ); + const dormantSourceIds = Object.keys(proposedNames).filter( + (sourceId) => !typeSourceIds.includes(sourceId), + ); + + for (const dormantSourceId of dormantSourceIds) { + delete proposedNames[dormantSourceId]; + } } } diff --git a/packages/name-controller/src/logger.ts b/packages/name-controller/src/logger.ts new file mode 100644 index 00000000000..4e7415f91e4 --- /dev/null +++ b/packages/name-controller/src/logger.ts @@ -0,0 +1,5 @@ +import { createProjectLogger, createModuleLogger } from '@metamask/utils'; + +export const projectLogger = createProjectLogger('name-controller'); + +export { createModuleLogger }; diff --git a/packages/name-controller/src/providers/ens.test.ts b/packages/name-controller/src/providers/ens.test.ts index 33419a8c37b..7e28f069a3c 100644 --- a/packages/name-controller/src/providers/ens.test.ts +++ b/packages/name-controller/src/providers/ens.test.ts @@ -66,5 +66,40 @@ describe('ENSNameProvider', () => { expect(reverseLookupMock).toHaveBeenCalledTimes(1); expect(reverseLookupMock).toHaveBeenCalledWith(VALUE_MOCK, CHAIN_ID_MOCK); }); + + it('returns empty result if disabled', async () => { + const provider = new ENSNameProvider({ + ...CONSTRUCTOR_ARGS_MOCK, + isEnabled: () => false, + }); + + const response = await provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(response).toStrictEqual({ + results: { [SOURCE_ID]: { proposedNames: [] } }, + }); + }); + + it('throws if callback fails', async () => { + const reverseLookupMock = jest.fn().mockImplementation(() => { + throw new Error('TestError'); + }); + + const provider = new ENSNameProvider({ + reverseLookup: reverseLookupMock, + }); + + await expect( + provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }), + ).rejects.toThrow('TestError'); + }); }); }); diff --git a/packages/name-controller/src/providers/ens.ts b/packages/name-controller/src/providers/ens.ts index 5ca36ca4548..4614b4963c8 100644 --- a/packages/name-controller/src/providers/ens.ts +++ b/packages/name-controller/src/providers/ens.ts @@ -1,3 +1,4 @@ +import { projectLogger, createModuleLogger } from '../logger'; import type { NameProvider, NameProviderMetadata, @@ -14,10 +15,21 @@ export type ReverseLookupCallback = ( const ID = 'ens'; const LABEL = 'Ethereum Name Service (ENS)'; +const log = createModuleLogger(projectLogger, 'ens'); + export class ENSNameProvider implements NameProvider { + #isEnabled: () => boolean; + #reverseLookup: ReverseLookupCallback; - constructor({ reverseLookup }: { reverseLookup: ReverseLookupCallback }) { + constructor({ + isEnabled, + reverseLookup, + }: { + isEnabled?: () => boolean; + reverseLookup: ReverseLookupCallback; + }) { + this.#isEnabled = isEnabled || (() => true); this.#reverseLookup = reverseLookup; } @@ -31,13 +43,36 @@ export class ENSNameProvider implements NameProvider { async getProposedNames( request: NameProviderRequest, ): Promise { + if (!this.#isEnabled()) { + log('Skipping request as disabled'); + + return { + results: { + [ID]: { + proposedNames: [], + }, + }, + }; + } + const { value, chainId } = request; - const proposedName = await this.#reverseLookup(value, chainId); - return { - results: { - [ID]: { proposedNames: [proposedName] }, - }, - }; + log('Invoking callback', { value, chainId }); + + try { + const proposedName = await this.#reverseLookup(value, chainId); + const proposedNames = proposedName ? [proposedName] : []; + + log('New proposed names', proposedNames); + + return { + results: { + [ID]: { proposedNames }, + }, + }; + } catch (error) { + log('Request failed', error); + throw error; + } } } diff --git a/packages/name-controller/src/providers/etherscan.test.ts b/packages/name-controller/src/providers/etherscan.test.ts index 5ec87ad97b5..eef1dbf87ad 100644 --- a/packages/name-controller/src/providers/etherscan.test.ts +++ b/packages/name-controller/src/providers/etherscan.test.ts @@ -10,7 +10,6 @@ const CHAIN_ID_MOCK = '0x1'; const SOURCE_ID = 'etherscan'; const CONTRACT_NAME_MOCK = 'TestContractName'; const CONTRACT_NAME_2_MOCK = 'TestContractName2'; -const API_KEY_MOCK = 'TestApiKey'; describe('EtherscanNameProvider', () => { const handleFetchMock = jest.mocked(handleFetch); @@ -95,8 +94,8 @@ describe('EtherscanNameProvider', () => { }, ); - it('includes API key in requested URL if provided', async () => { - const provider = new EtherscanNameProvider({ apiKey: API_KEY_MOCK }); + it('requests alternate URL based on chain ID', async () => { + const provider = new EtherscanNameProvider(); handleFetchMock.mockResolvedValueOnce({ result: [ @@ -111,55 +110,107 @@ describe('EtherscanNameProvider', () => { await provider.getProposedNames({ value: VALUE_MOCK, - chainId: CHAIN_ID_MOCK, + chainId: CHAIN_IDS.LINEA_GOERLI, type: NameType.ETHEREUM_ADDRESS, }); expect(handleFetchMock).toHaveBeenCalledTimes(1); expect(handleFetchMock).toHaveBeenCalledWith( - `https://api.etherscan.io/api?module=contract&action=getsourcecode&address=${VALUE_MOCK}&apikey=${API_KEY_MOCK}`, + `https://goerli.lineascan.build/api?module=contract&action=getsourcecode&address=${VALUE_MOCK}`, ); }); - it('requests alternate URL based on chain ID', async () => { + it('throws if chain ID not supported', async () => { + const invalidChainId = '0x0'; const provider = new EtherscanNameProvider(); - handleFetchMock.mockResolvedValueOnce({ - result: [ - { - ContractName: CONTRACT_NAME_MOCK, + await expect( + provider.getProposedNames({ + value: VALUE_MOCK, + chainId: invalidChainId, + type: NameType.ETHEREUM_ADDRESS, + }), + ).rejects.toThrow( + `Etherscan does not support chain with ID: ${invalidChainId}`, + ); + }); + + it('returns delay only if within rate limit interval', async () => { + const provider = new EtherscanNameProvider(); + + await provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + const result = await provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(result).toStrictEqual({ + results: { + [SOURCE_ID]: { + updateDelay: 5, }, - { - ContractName: CONTRACT_NAME_2_MOCK, + }, + }); + }); + + it('returns delay only if request has warning', async () => { + const provider = new EtherscanNameProvider(); + + handleFetchMock.mockResolvedValueOnce({ + message: 'NOTOK', + }); + + const result = await provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(result).toStrictEqual({ + results: { + [SOURCE_ID]: { + updateDelay: 5, }, - ], + }, }); + }); - await provider.getProposedNames({ + it('returns empty result if disabled', async () => { + const provider = new EtherscanNameProvider({ + isEnabled: () => false, + }); + + const response = await provider.getProposedNames({ value: VALUE_MOCK, - chainId: CHAIN_IDS.LINEA_GOERLI, + chainId: CHAIN_ID_MOCK, type: NameType.ETHEREUM_ADDRESS, }); - expect(handleFetchMock).toHaveBeenCalledTimes(1); - expect(handleFetchMock).toHaveBeenCalledWith( - `https://goerli.lineascan.build/api?module=contract&action=getsourcecode&address=${VALUE_MOCK}`, - ); + expect(response).toStrictEqual({ + results: { [SOURCE_ID]: { proposedNames: [] } }, + }); }); - it('throws if chain ID not supported', async () => { - const invalidChainId = '0x0'; + it('throws if request fails', async () => { const provider = new EtherscanNameProvider(); + handleFetchMock.mockImplementation(() => { + throw new Error('TestError'); + }); + await expect( provider.getProposedNames({ value: VALUE_MOCK, - chainId: invalidChainId, + chainId: CHAIN_ID_MOCK, type: NameType.ETHEREUM_ADDRESS, }), - ).rejects.toThrow( - `Etherscan does not support chain with ID: ${invalidChainId}`, - ); + ).rejects.toThrow('TestError'); }); }); }); diff --git a/packages/name-controller/src/providers/etherscan.ts b/packages/name-controller/src/providers/etherscan.ts index b4b23736602..aa048922422 100644 --- a/packages/name-controller/src/providers/etherscan.ts +++ b/packages/name-controller/src/providers/etherscan.ts @@ -1,4 +1,7 @@ +import { Mutex } from 'async-mutex'; + import { ETHERSCAN_SUPPORTED_NETWORKS } from '../constants'; +import { createModuleLogger, projectLogger } from '../logger'; import type { NameProvider, NameProviderMetadata, @@ -10,6 +13,10 @@ import { handleFetch } from '../util'; const ID = 'etherscan'; const LABEL = 'Etherscan (Verified Contract Name)'; +const RATE_LIMIT_UPDATE_DELAY = 5; // 5 Seconds +const RATE_LIMIT_INTERVAL = RATE_LIMIT_UPDATE_DELAY * 1000; + +const log = createModuleLogger(projectLogger, 'etherscan'); type EtherscanGetSourceCodeResponse = { status: '1' | '0'; @@ -33,10 +40,14 @@ type EtherscanGetSourceCodeResponse = { }; export class EtherscanNameProvider implements NameProvider { - #apiKey?: string; + #isEnabled: () => boolean; + + #lastRequestTime = 0; + + #mutex = new Mutex(); - constructor({ apiKey }: { apiKey?: string } = {}) { - this.#apiKey = apiKey; + constructor({ isEnabled }: { isEnabled?: () => boolean } = {}) { + this.#isEnabled = isEnabled || (() => true); } getMetadata(): NameProviderMetadata { @@ -49,29 +60,94 @@ export class EtherscanNameProvider implements NameProvider { async getProposedNames( request: NameProviderRequest, ): Promise { - const { value, chainId } = request; + if (!this.#isEnabled()) { + log('Skipping request as disabled'); + + return { + results: { + [ID]: { + proposedNames: [], + }, + }, + }; + } - const url = this.#getUrl(chainId, { - module: 'contract', - action: 'getsourcecode', - address: value, - apikey: this.#apiKey, - }); + const releaseLock = await this.#mutex.acquire(); - const responseData = (await handleFetch( - url, - )) as EtherscanGetSourceCodeResponse; + try { + const { value, chainId } = request; - const results = responseData?.result ?? []; - const proposedNames = results.map((result) => result.ContractName); + const time = Date.now(); + const timeSinceLastRequest = time - this.#lastRequestTime; - return { - results: { - [ID]: { - proposedNames, + if (timeSinceLastRequest < RATE_LIMIT_INTERVAL) { + log('Skipping request to avoid rate limit'); + + return { + results: { + [ID]: { + updateDelay: RATE_LIMIT_UPDATE_DELAY, + }, + }, + }; + } + + const url = this.#getUrl(chainId, { + module: 'contract', + action: 'getsourcecode', + address: value, + }); + + const { responseData, error } = await this.#sendRequest(url); + + if (error) { + log('Request failed', error); + throw error; + } + + if (responseData?.message === 'NOTOK') { + log('Request warning', responseData.result); + + return { + results: { + [ID]: { + updateDelay: RATE_LIMIT_UPDATE_DELAY, + }, + }, + }; + } + + const results = responseData?.result ?? []; + const proposedNames = results.map((result) => result.ContractName); + + log('New proposed names', proposedNames); + + return { + results: { + [ID]: { + proposedNames, + }, }, - }, - }; + }; + } finally { + releaseLock(); + } + } + + async #sendRequest(url: string) { + try { + log('Sending request', url); + + const responseData = (await handleFetch( + url, + )) as EtherscanGetSourceCodeResponse; + + return { responseData }; + } catch (error) { + return { error }; + } finally { + this.#lastRequestTime = Date.now(); + } } #getUrl(chainId: string, params: Record): string { @@ -88,11 +164,6 @@ export class EtherscanNameProvider implements NameProvider { Object.keys(params).forEach((key, index) => { const value = params[key]; - - if (!value) { - return; - } - url += `${index === 0 ? '?' : '&'}${key}=${value}`; }); diff --git a/packages/name-controller/src/providers/lens.test.ts b/packages/name-controller/src/providers/lens.test.ts index c22141121ac..05821a6d272 100644 --- a/packages/name-controller/src/providers/lens.test.ts +++ b/packages/name-controller/src/providers/lens.test.ts @@ -86,5 +86,37 @@ describe('LensNameProvider', () => { }, }); }); + + it('returns empty result if disabled', async () => { + const provider = new LensNameProvider({ + isEnabled: () => false, + }); + + const response = await provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(response).toStrictEqual({ + results: { [SOURCE_ID]: { proposedNames: [] } }, + }); + }); + + it('throws if request fails', async () => { + graphqlMock.mockImplementation(() => { + throw new Error('TestError'); + }); + + const provider = new LensNameProvider(); + + await expect( + provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }), + ).rejects.toThrow('TestError'); + }); }); }); diff --git a/packages/name-controller/src/providers/lens.ts b/packages/name-controller/src/providers/lens.ts index 939499bed86..36e05744806 100644 --- a/packages/name-controller/src/providers/lens.ts +++ b/packages/name-controller/src/providers/lens.ts @@ -1,3 +1,4 @@ +import { createModuleLogger, projectLogger } from '../logger'; import type { NameProvider, NameProviderMetadata, @@ -20,6 +21,8 @@ query HandlesForAddress($address: EthereumAddress!) { } }`; +const log = createModuleLogger(projectLogger, 'lens'); + type LensResponse = { profiles: { items: [ @@ -31,6 +34,12 @@ type LensResponse = { }; export class LensNameProvider implements NameProvider { + #isEnabled: () => boolean; + + constructor({ isEnabled }: { isEnabled?: () => boolean } = {}) { + this.#isEnabled = isEnabled || (() => true); + } + getMetadata(): NameProviderMetadata { return { sourceIds: { [NameType.ETHEREUM_ADDRESS]: [ID] }, @@ -41,21 +50,45 @@ export class LensNameProvider implements NameProvider { async getProposedNames( request: NameProviderRequest, ): Promise { + if (!this.#isEnabled()) { + log('Skipping request as disabled'); + + return { + results: { + [ID]: { + proposedNames: [], + }, + }, + }; + } + const { value } = request; + const variables = { address: value }; - const responseData = await graphQL(LENS_URL, QUERY, { - address: value, - }); + log('Sending request', { variables }); - const profiles = responseData?.profiles?.items ?? []; - const proposedNames = profiles.map((profile) => profile.handle); + try { + const responseData = await graphQL( + LENS_URL, + QUERY, + variables, + ); - return { - results: { - [ID]: { - proposedNames, + const profiles = responseData?.profiles?.items ?? []; + const proposedNames = profiles.map((profile) => profile.handle); + + log('New proposed names', proposedNames); + + return { + results: { + [ID]: { + proposedNames, + }, }, - }, - }; + }; + } catch (error) { + log('Request failed', error); + throw error; + } } } diff --git a/packages/name-controller/src/providers/token.test.ts b/packages/name-controller/src/providers/token.test.ts index 421bf03c690..6e17f581d0e 100644 --- a/packages/name-controller/src/providers/token.test.ts +++ b/packages/name-controller/src/providers/token.test.ts @@ -54,5 +54,55 @@ describe('TokenNameProvider', () => { `https://token-api.metaswap.codefi.network/token/${CHAIN_ID_MOCK}?address=${VALUE_MOCK}`, ); }); + + it('returns empty array if no token name in infura response', async () => { + const provider = new TokenNameProvider(); + + handleFetchMock.mockResolvedValueOnce({ name: undefined }); + + const response = await provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(response).toStrictEqual({ + results: { + [SOURCE_ID]: { proposedNames: [] }, + }, + }); + }); + + it('returns empty result if disabled', async () => { + const provider = new TokenNameProvider({ + isEnabled: () => false, + }); + + const response = await provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }); + + expect(response).toStrictEqual({ + results: { [SOURCE_ID]: { proposedNames: [] } }, + }); + }); + + it('throws if request fails', async () => { + handleFetchMock.mockImplementation(() => { + throw new Error('TestError'); + }); + + const provider = new TokenNameProvider(); + + await expect( + provider.getProposedNames({ + value: VALUE_MOCK, + chainId: CHAIN_ID_MOCK, + type: NameType.ETHEREUM_ADDRESS, + }), + ).rejects.toThrow('TestError'); + }); }); }); diff --git a/packages/name-controller/src/providers/token.ts b/packages/name-controller/src/providers/token.ts index 0d55f3372ba..f52ad19ed7a 100644 --- a/packages/name-controller/src/providers/token.ts +++ b/packages/name-controller/src/providers/token.ts @@ -1,3 +1,4 @@ +import { createModuleLogger, projectLogger } from '../logger'; import type { NameProvider, NameProviderMetadata, @@ -10,7 +11,15 @@ import { handleFetch } from '../util'; const ID = 'token'; const LABEL = 'Blockchain (Token Name)'; +const log = createModuleLogger(projectLogger, 'token'); + export class TokenNameProvider implements NameProvider { + #isEnabled: () => boolean; + + constructor({ isEnabled }: { isEnabled?: () => boolean } = {}) { + this.#isEnabled = isEnabled || (() => true); + } + getMetadata(): NameProviderMetadata { return { sourceIds: { [NameType.ETHEREUM_ADDRESS]: [ID] }, @@ -21,17 +30,40 @@ export class TokenNameProvider implements NameProvider { async getProposedNames( request: NameProviderRequest, ): Promise { + if (!this.#isEnabled()) { + log('Skipping request as disabled'); + + return { + results: { + [ID]: { + proposedNames: [], + }, + }, + }; + } + const { value, chainId } = request; const url = `https://token-api.metaswap.codefi.network/token/${chainId}?address=${value}`; - const responseData = await handleFetch(url); - const proposedName = responseData.name; - return { - results: { - [ID]: { - proposedNames: [proposedName], + log('Sending request', url); + + try { + const responseData = await handleFetch(url); + const proposedName = responseData.name; + const proposedNames = proposedName ? [proposedName] : []; + + log('New proposed names', proposedNames); + + return { + results: { + [ID]: { + proposedNames, + }, }, - }, - }; + }; + } catch (error) { + log('Request failed', error); + throw error; + } } } diff --git a/packages/name-controller/src/types.ts b/packages/name-controller/src/types.ts index 24c506f93ac..0adba9c136f 100644 --- a/packages/name-controller/src/types.ts +++ b/packages/name-controller/src/types.ts @@ -42,6 +42,12 @@ export type NameProviderSourceResult = { */ proposedNames?: string[]; + /** + * The delay in seconds before the next request to the source should be made. + * Can be used to avoid rate limiting for example. + */ + updateDelay?: number; + /** * An error that occurred while fetching the proposed names from the source. * Undefined if there was no error. diff --git a/yarn.lock b/yarn.lock index d2fb062ebe0..bb678fa8161 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1973,7 +1973,9 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 + "@metamask/utils": ^6.2.0 "@types/jest": ^27.4.1 + async-mutex: ^0.2.6 deepmerge: ^4.2.2 immer: ^9.0.6 jest: ^27.5.1 From d5c993df3b88444f26944c97e097edac39920b57 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 26 Sep 2023 20:27:53 +0000 Subject: [PATCH 08/14] bump @metamask/utils to 8.1.0 --- packages/name-controller/package.json | 2 +- packages/polling-controller/package.json | 2 +- yarn.lock | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/name-controller/package.json b/packages/name-controller/package.json index 38a0f99f0fc..a8cc3e9f6d2 100644 --- a/packages/name-controller/package.json +++ b/packages/name-controller/package.json @@ -30,7 +30,7 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", - "@metamask/utils": "^6.2.0", + "@metamask/utils": "^8.1.0", "async-mutex": "^0.2.6", "immer": "^9.0.6" }, diff --git a/packages/polling-controller/package.json b/packages/polling-controller/package.json index 40eb0868095..ab1be76a786 100644 --- a/packages/polling-controller/package.json +++ b/packages/polling-controller/package.json @@ -31,7 +31,7 @@ "@metamask/base-controller": "^3.2.1", "@metamask/controller-utils": "^5.0.0", "@metamask/network-controller": "^13.0.0", - "@metamask/utils": "^6.2.0", + "@metamask/utils": "^8.1.0", "@types/uuid": "^8.3.0", "uuid": "^8.3.2" }, diff --git a/yarn.lock b/yarn.lock index 182916bbdad..65feed24d78 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1973,7 +1973,7 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 - "@metamask/utils": ^6.2.0 + "@metamask/utils": ^8.1.0 "@types/jest": ^27.4.1 async-mutex: ^0.2.6 deepmerge: ^4.2.2 @@ -2129,7 +2129,7 @@ __metadata: "@metamask/base-controller": ^3.2.1 "@metamask/controller-utils": ^5.0.0 "@metamask/network-controller": ^13.0.0 - "@metamask/utils": ^6.2.0 + "@metamask/utils": ^8.1.0 "@types/jest": ^27.4.1 "@types/uuid": ^8.3.0 deepmerge: ^4.2.2 @@ -2644,7 +2644,7 @@ __metadata: languageName: node linkType: hard -"@metamask/utils@npm:^6.0.1, @metamask/utils@npm:^6.2.0": +"@metamask/utils@npm:^6.0.1": version: 6.2.0 resolution: "@metamask/utils@npm:6.2.0" dependencies: From 794c5addfe6c3941be02326be74220966173df39 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 26 Sep 2023 20:28:54 +0000 Subject: [PATCH 09/14] approval-controller: move to @metamask/rpc-errors --- packages/approval-controller/package.json | 2 +- .../src/ApprovalController.test.ts | 16 ++++++++-------- .../src/ApprovalController.ts | 14 +++++++------- yarn.lock | 2 +- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/approval-controller/package.json b/packages/approval-controller/package.json index 9b0a43ad34d..fc1f74a4b7d 100644 --- a/packages/approval-controller/package.json +++ b/packages/approval-controller/package.json @@ -29,8 +29,8 @@ }, "dependencies": { "@metamask/base-controller": "^3.2.1", + "@metamask/rpc-errors": "^6.0.0", "@metamask/utils": "^8.1.0", - "eth-rpc-errors": "^4.0.2", "immer": "^9.0.6", "nanoid": "^3.1.31" }, diff --git a/packages/approval-controller/src/ApprovalController.test.ts b/packages/approval-controller/src/ApprovalController.test.ts index 94db8cbc8b4..d9901406156 100644 --- a/packages/approval-controller/src/ApprovalController.test.ts +++ b/packages/approval-controller/src/ApprovalController.test.ts @@ -1,7 +1,7 @@ /* eslint-disable jest/expect-expect */ import { ControllerMessenger } from '@metamask/base-controller'; -import { errorCodes, EthereumRpcError } from 'eth-rpc-errors'; +import { errorCodes, JsonRpcError } from '@metamask/rpc-errors'; import type { ApprovalControllerActions, @@ -642,7 +642,7 @@ describe('approval controller', () => { approvalController.reject('2', new Error('foo')); expect(approvalController.getTotalApprovalCount()).toBe(2); - approvalController.clear(new EthereumRpcError(1, 'clear')); + approvalController.clear(new JsonRpcError(1, 'clear')); expect(approvalController.getTotalApprovalCount()).toBe(0); }); @@ -666,7 +666,7 @@ describe('approval controller', () => { approvalController.reject('2', new Error('foo')); expect(approvalController.getTotalApprovalCount()).toBe(1); - approvalController.clear(new EthereumRpcError(1, 'clear')); + approvalController.clear(new JsonRpcError(1, 'clear')); expect(approvalController.getTotalApprovalCount()).toBe(0); }); }); @@ -1025,7 +1025,7 @@ describe('approval controller', () => { describe('clear', () => { it('does nothing if state is already empty', () => { expect(() => - approvalController.clear(new EthereumRpcError(1, 'clear')), + approvalController.clear(new JsonRpcError(1, 'clear')), ).not.toThrow(); }); @@ -1040,7 +1040,7 @@ describe('approval controller', () => { .add({ id: 'foo3', origin: 'fizz.buzz', type: 'myType' }) .catch((_error) => undefined); - approvalController.clear(new EthereumRpcError(1, 'clear')); + approvalController.clear(new JsonRpcError(1, 'clear')); expect( approvalController.state[PENDING_APPROVALS_STORE_KEY], @@ -1055,16 +1055,16 @@ describe('approval controller', () => { type: 'myType', }); - approvalController.clear(new EthereumRpcError(1000, 'foo')); + approvalController.clear(new JsonRpcError(1000, 'foo')); await expect(rejectPromise).rejects.toThrow( - new EthereumRpcError(1000, 'foo'), + new JsonRpcError(1000, 'foo'), ); }); it('does not clear approval flows', async () => { approvalController.startFlow(); - approvalController.clear(new EthereumRpcError(1, 'clear')); + approvalController.clear(new JsonRpcError(1, 'clear')); expect(approvalController.state[APPROVAL_FLOWS_STORE_KEY]).toHaveLength( 1, diff --git a/packages/approval-controller/src/ApprovalController.ts b/packages/approval-controller/src/ApprovalController.ts index 1c2096164fe..4e3259e892d 100644 --- a/packages/approval-controller/src/ApprovalController.ts +++ b/packages/approval-controller/src/ApprovalController.ts @@ -1,8 +1,8 @@ import type { RestrictedControllerMessenger } from '@metamask/base-controller'; import { BaseControllerV2 } from '@metamask/base-controller'; +import type { JsonRpcError, DataWithOptionalCause } from '@metamask/rpc-errors'; +import { rpcErrors } from '@metamask/rpc-errors'; import type { Json, OptionalField } from '@metamask/utils'; -import type { EthereumRpcError } from 'eth-rpc-errors'; -import { ethErrors } from 'eth-rpc-errors'; import type { Patch } from 'immer'; import { nanoid } from 'nanoid'; @@ -256,7 +256,7 @@ export type GetApprovalsState = { export type ClearApprovalRequests = { type: `${typeof controllerName}:clearRequests`; - handler: (error: EthereumRpcError) => void; + handler: (error: JsonRpcError) => void; }; export type AddApprovalRequest = { @@ -719,10 +719,10 @@ export class ApprovalController extends BaseControllerV2< /** * Rejects and deletes all approval requests. * - * @param rejectionError - The EthereumRpcError to reject the approval + * @param rejectionError - The JsonRpcError to reject the approval * requests with. */ - clear(rejectionError: EthereumRpcError): void { + clear(rejectionError: JsonRpcError): void { for (const id of this.#approvals.keys()) { this.reject(id, rejectionError); } @@ -878,7 +878,7 @@ export class ApprovalController extends BaseControllerV2< !this.#typesExcludedFromRateLimiting.includes(type) && this.has({ origin, type }) ) { - throw ethErrors.rpc.resourceUnavailable( + throw rpcErrors.resourceUnavailable( getAlreadyPendingMessage(origin, type), ); } @@ -937,7 +937,7 @@ export class ApprovalController extends BaseControllerV2< } if (errorMessage) { - throw ethErrors.rpc.internal(errorMessage); + throw rpcErrors.internal(errorMessage); } } diff --git a/yarn.lock b/yarn.lock index 65feed24d78..62bfef629b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1369,10 +1369,10 @@ __metadata: dependencies: "@metamask/auto-changelog": ^3.1.0 "@metamask/base-controller": ^3.2.1 + "@metamask/rpc-errors": ^6.0.0 "@metamask/utils": ^8.1.0 "@types/jest": ^27.4.1 deepmerge: ^4.2.2 - eth-rpc-errors: ^4.0.2 immer: ^9.0.6 jest: ^27.5.1 nanoid: ^3.1.31 From a957b47725c3dff175a25256ed2fb4815a9e532f Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 26 Sep 2023 22:49:00 +0000 Subject: [PATCH 10/14] wipwip --- packages/permission-controller/README.md | 2 +- packages/permission-controller/package.json | 2 +- .../permission-controller/src/Permission.ts | 17 +++++----- .../src/PermissionController.test.ts | 30 ++++++++--------- .../src/PermissionController.ts | 5 +-- packages/permission-controller/src/errors.ts | 32 ++++++++++++------- .../src/permission-middleware.ts | 4 +-- .../src/rpc-methods/getPermissions.ts | 4 +-- .../rpc-methods/requestPermissions.test.ts | 6 ++-- .../src/rpc-methods/requestPermissions.ts | 4 +-- yarn.lock | 2 +- 11 files changed, 58 insertions(+), 50 deletions(-) diff --git a/packages/permission-controller/README.md b/packages/permission-controller/README.md index b05c2240073..9d3e8f0edd9 100644 --- a/packages/permission-controller/README.md +++ b/packages/permission-controller/README.md @@ -1,6 +1,6 @@ # `@metamask/permission-controller` -Mediates access to JSON-RPC methods, used to interact with pieces of the MetaMask stack, via middleware for `json-rpc-engine`. +Mediates access to JSON-RPC methods, used to interact with pieces of the MetaMask stack, via middleware for `@metamask/json-rpc-engine`. ## Installation diff --git a/packages/permission-controller/package.json b/packages/permission-controller/package.json index 62e3b7a5b7b..93532f56115 100644 --- a/packages/permission-controller/package.json +++ b/packages/permission-controller/package.json @@ -32,10 +32,10 @@ "@metamask/base-controller": "^3.2.1", "@metamask/controller-utils": "^5.0.0", "@metamask/json-rpc-engine": "^7.1.1", + "@metamask/rpc-errors": "^6.0.0", "@metamask/utils": "^8.1.0", "@types/deep-freeze-strict": "^1.1.0", "deep-freeze-strict": "^1.1.1", - "eth-rpc-errors": "^4.0.2", "immer": "^9.0.6", "nanoid": "^3.1.31" }, diff --git a/packages/permission-controller/src/Permission.ts b/packages/permission-controller/src/Permission.ts index 36cd03ff025..c9951630f0e 100644 --- a/packages/permission-controller/src/Permission.ts +++ b/packages/permission-controller/src/Permission.ts @@ -3,7 +3,7 @@ import type { EventConstraint, } from '@metamask/base-controller'; import type { NonEmptyArray } from '@metamask/controller-utils'; -import type { Json, JsonRpcParams } from '@metamask/utils'; +import type { Json } from '@metamask/utils'; import { nanoid } from 'nanoid'; // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -210,19 +210,20 @@ type RestrictedMethodContext = Readonly<{ [key: string]: any; }>; -export type RestrictedMethodParameters = JsonRpcParams; +export type RestrictedMethodParameters = Json[] | Record; /** * The arguments passed to a restricted method implementation. * * @template Params - The JSON-RPC parameters of the restricted method. */ -export type RestrictedMethodOptions = - { - method: TargetName; - params?: Params; - context: RestrictedMethodContext; - }; +export type RestrictedMethodOptions< + Params extends RestrictedMethodParameters | null, +> = { + method: TargetName; + params?: Params; + context: RestrictedMethodContext; +}; /** * A synchronous restricted method implementation. diff --git a/packages/permission-controller/src/PermissionController.test.ts b/packages/permission-controller/src/PermissionController.test.ts index 841315b7193..eb960aee3c8 100644 --- a/packages/permission-controller/src/PermissionController.test.ts +++ b/packages/permission-controller/src/PermissionController.test.ts @@ -7,12 +7,8 @@ import type { import { ControllerMessenger } from '@metamask/base-controller'; import { isPlainObject } from '@metamask/controller-utils'; import { JsonRpcEngine } from '@metamask/json-rpc-engine'; +import type { Json, PendingJsonRpcResponse } from '@metamask/utils'; import { hasProperty } from '@metamask/utils'; -import type { - Json, - PendingJsonRpcResponse, - JsonRpcParams, -} from '@metamask/utils'; import assert from 'assert'; import type { @@ -254,7 +250,7 @@ function getDefaultPermissionSpecifications() { CaveatTypes.filterArrayResponse, CaveatTypes.reverseArrayResponse, ], - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return ['a', 'b', 'c']; }, }, @@ -265,7 +261,9 @@ function getDefaultPermissionSpecifications() { CaveatTypes.filterObjectResponse, CaveatTypes.noopCaveat, ], - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: ( + _args: RestrictedMethodOptions>, + ) => { return { a: 'x', b: 'y', c: 'z' }; }, validator: (permission: PermissionConstraint) => { @@ -295,7 +293,7 @@ function getDefaultPermissionSpecifications() { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.wallet_noop, allowedCaveats: null, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, }, @@ -303,7 +301,7 @@ function getDefaultPermissionSpecifications() { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.wallet_noopWithPermittedAndFailureSideEffects, allowedCaveats: null, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, sideEffect: { @@ -315,7 +313,7 @@ function getDefaultPermissionSpecifications() { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.wallet_noopWithPermittedAndFailureSideEffects2, allowedCaveats: null, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, sideEffect: { @@ -327,7 +325,7 @@ function getDefaultPermissionSpecifications() { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.wallet_noopWithPermittedSideEffects, allowedCaveats: null, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, sideEffect: { @@ -338,7 +336,7 @@ function getDefaultPermissionSpecifications() { [PermissionKeys.wallet_noopWithValidator]: { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.wallet_noopWithValidator, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, allowedCaveats: [CaveatTypes.noopCaveat, CaveatTypes.filterArrayResponse], @@ -355,7 +353,7 @@ function getDefaultPermissionSpecifications() { [PermissionKeys.wallet_noopWithRequiredCaveat]: { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.wallet_noopWithRequiredCaveat, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, allowedCaveats: [CaveatTypes.noopCaveat], @@ -391,7 +389,7 @@ function getDefaultPermissionSpecifications() { [PermissionKeys.wallet_noopWithFactory]: { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.wallet_noopWithFactory, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, allowedCaveats: [CaveatTypes.filterArrayResponse], @@ -418,7 +416,7 @@ function getDefaultPermissionSpecifications() { permissionType: PermissionType.RestrictedMethod, targetName: PermissionKeys.snap_foo, allowedCaveats: null, - methodImplementation: (_args: RestrictedMethodOptions) => { + methodImplementation: (_args: RestrictedMethodOptions) => { return null; }, subjectTypes: [SubjectType.Snap], @@ -1853,7 +1851,7 @@ describe('PermissionController', () => { expect(() => controller.removeCaveat( origin, - PermissionNames.wallet_noopWithRequiredCaveat, + PermissionNames.wallet_noopWithRequiredCaveat as any, CaveatTypes.noopCaveat, ), ).toThrow( diff --git a/packages/permission-controller/src/PermissionController.ts b/packages/permission-controller/src/PermissionController.ts index db282cda67c..5bebdb970a8 100644 --- a/packages/permission-controller/src/PermissionController.ts +++ b/packages/permission-controller/src/PermissionController.ts @@ -18,10 +18,10 @@ import { isPlainObject, isValidJson, } from '@metamask/controller-utils'; +import { JsonRpcError } from '@metamask/rpc-errors'; import { hasProperty } from '@metamask/utils'; import type { Json, Mutable } from '@metamask/utils'; import deepFreeze from 'deep-freeze-strict'; -import { EthereumRpcError } from 'eth-rpc-errors'; import { castDraft } from 'immer'; import type { Draft, Patch } from 'immer'; import { nanoid } from 'nanoid'; @@ -1802,6 +1802,7 @@ export class PermissionController< target: string, ): void { if (!isPlainObject(caveat)) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal throw new InvalidCaveatError(caveat, origin, target); } @@ -2149,7 +2150,7 @@ export class PermissionController< try { this.validateRequestedPermissions(origin, permissions); } catch (error) { - if (error instanceof EthereumRpcError) { + if (error instanceof JsonRpcError) { // Re-throw as an internal error; we should never receive invalid approved // permissions. throw internalError( diff --git a/packages/permission-controller/src/errors.ts b/packages/permission-controller/src/errors.ts index 04a41ba0538..7e9e33ba246 100644 --- a/packages/permission-controller/src/errors.ts +++ b/packages/permission-controller/src/errors.ts @@ -1,4 +1,10 @@ -import { errorCodes, ethErrors, EthereumRpcError } from 'eth-rpc-errors'; +import type { DataWithOptionalCause } from '@metamask/rpc-errors'; +import { + errorCodes, + providerErrors, + rpcErrors, + JsonRpcError, +} from '@metamask/rpc-errors'; import type { PermissionType } from './Permission'; @@ -13,7 +19,7 @@ type UnauthorizedArg = { * @returns The built error */ export function unauthorized(opts: UnauthorizedArg) { - return ethErrors.provider.unauthorized({ + return providerErrors.unauthorized({ message: 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions', data: opts.data, @@ -27,19 +33,19 @@ export function unauthorized(opts: UnauthorizedArg) { * @param data - Optional data for context. * @returns The built error */ -export function methodNotFound(method: string, data?: unknown) { +export function methodNotFound(method: string, data?: DataWithOptionalCause) { const message = `The method "${method}" does not exist / is not available.`; - const opts: Parameters[0] = { message }; + const opts: Parameters[0] = { message }; if (data !== undefined) { opts.data = data; } - return ethErrors.rpc.methodNotFound(opts); + return rpcErrors.methodNotFound(opts); } type InvalidParamsArg = { message?: string; - data?: unknown; + data?: DataWithOptionalCause; }; /** @@ -49,7 +55,7 @@ type InvalidParamsArg = { * @returns The built error */ export function invalidParams(opts: InvalidParamsArg) { - return ethErrors.rpc.invalidParams({ + return rpcErrors.invalidParams({ data: opts.data, message: opts.message, }); @@ -63,8 +69,8 @@ export function invalidParams(opts: InvalidParamsArg) { */ export function userRejectedRequest>( data?: Data, -): EthereumRpcError { - return ethErrors.provider.userRejectedRequest({ data }); +): JsonRpcError { + return providerErrors.userRejectedRequest({ data }); } /** @@ -77,8 +83,8 @@ export function userRejectedRequest>( export function internalError>( message: string, data?: Data, -): EthereumRpcError { - return ethErrors.rpc.internal({ message, data }); +): JsonRpcError { + return rpcErrors.internal({ message, data }); } export class InvalidSubjectIdentifierError extends Error { @@ -183,7 +189,9 @@ export class CaveatAlreadyExistsError extends Error { } } -export class InvalidCaveatError extends EthereumRpcError { +export class InvalidCaveatError extends JsonRpcError< + DataWithOptionalCause | undefined +> { public override data: { origin: string; target: string }; constructor(receivedCaveat: unknown, origin: string, target: string) { diff --git a/packages/permission-controller/src/permission-middleware.ts b/packages/permission-controller/src/permission-middleware.ts index f618e1cfd65..c18b90c622b 100644 --- a/packages/permission-controller/src/permission-middleware.ts +++ b/packages/permission-controller/src/permission-middleware.ts @@ -7,8 +7,8 @@ import type { } from '@metamask/json-rpc-engine'; import type { Json, - JsonRpcRequest, PendingJsonRpcResponse, + JsonRpcRequest, } from '@metamask/utils'; import type { @@ -62,7 +62,7 @@ export function getPermissionMiddlewareFactory({ } const permissionsMiddleware = async ( - req: JsonRpcRequest, + req: JsonRpcRequest, res: PendingJsonRpcResponse, next: AsyncJsonRpcEngineNextCallback, ): Promise => { diff --git a/packages/permission-controller/src/rpc-methods/getPermissions.ts b/packages/permission-controller/src/rpc-methods/getPermissions.ts index 7e34f698d0d..6debc06c849 100644 --- a/packages/permission-controller/src/rpc-methods/getPermissions.ts +++ b/packages/permission-controller/src/rpc-methods/getPermissions.ts @@ -1,5 +1,5 @@ import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine'; -import type { JsonRpcParams, PendingJsonRpcResponse } from '@metamask/utils'; +import type { PendingJsonRpcResponse } from '@metamask/utils'; import type { PermissionConstraint } from '../Permission'; import type { SubjectPermissions } from '../PermissionController'; @@ -8,7 +8,7 @@ import { MethodNames } from '../utils'; export const getPermissionsHandler: PermittedHandlerExport< GetPermissionsHooks, - JsonRpcParams, + any, PermissionConstraint[] > = { methodNames: [MethodNames.getPermissions], diff --git a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts index 45b346b9618..889bef7afce 100644 --- a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts +++ b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts @@ -2,7 +2,7 @@ import { JsonRpcEngine, createAsyncMiddleware, } from '@metamask/json-rpc-engine'; -import { ethErrors, serializeError } from 'eth-rpc-errors'; +import { rpcErrors, serializeError } from '@metamask/rpc-errors'; import { requestPermissionsHandler } from './requestPermissions'; @@ -94,7 +94,7 @@ describe('requestPermissions RPC method', () => { params: [], // doesn't matter }; - const expectedError = ethErrors.rpc + const expectedError = rpcErrors .invalidRequest({ message: 'Invalid request: Must specify a valid id.', data: { request: { ...req } }, @@ -128,7 +128,7 @@ describe('requestPermissions RPC method', () => { params: invalidParams, }; - const expectedError = ethErrors.rpc + const expectedError = rpcErrors .invalidParams({ data: { request: { ...req } }, }) diff --git a/packages/permission-controller/src/rpc-methods/requestPermissions.ts b/packages/permission-controller/src/rpc-methods/requestPermissions.ts index b32b1a6bb78..1976eb2934d 100644 --- a/packages/permission-controller/src/rpc-methods/requestPermissions.ts +++ b/packages/permission-controller/src/rpc-methods/requestPermissions.ts @@ -1,7 +1,7 @@ import { isPlainObject } from '@metamask/controller-utils'; import type { JsonRpcEngineEndCallback } from '@metamask/json-rpc-engine'; +import { rpcErrors } from '@metamask/rpc-errors'; import type { JsonRpcRequest, PendingJsonRpcResponse } from '@metamask/utils'; -import { ethErrors } from 'eth-rpc-errors'; import { invalidParams } from '../errors'; import type { PermissionConstraint, RequestedPermissions } from '../Permission'; @@ -56,7 +56,7 @@ async function requestPermissionsImplementation( (typeof id === 'string' && !id) ) { return end( - ethErrors.rpc.invalidRequest({ + rpcErrors.invalidRequest({ message: 'Invalid request: Must specify a valid id.', data: { request: req }, }), diff --git a/yarn.lock b/yarn.lock index 62bfef629b3..39933cd1ab2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2081,12 +2081,12 @@ __metadata: "@metamask/base-controller": ^3.2.1 "@metamask/controller-utils": ^5.0.0 "@metamask/json-rpc-engine": ^7.1.1 + "@metamask/rpc-errors": ^6.0.0 "@metamask/utils": ^8.1.0 "@types/deep-freeze-strict": ^1.1.0 "@types/jest": ^27.4.1 deep-freeze-strict: ^1.1.1 deepmerge: ^4.2.2 - eth-rpc-errors: ^4.0.2 immer: ^9.0.6 jest: ^27.5.1 nanoid: ^3.1.31 From 881f918215e065f0ead301f5cbf4db480c6e8213 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Tue, 26 Sep 2023 23:38:04 +0000 Subject: [PATCH 11/14] wip: permission-controller: @metamask/rpc-errors migration --- .../src/PermissionController.test.ts | 8 +++++++- packages/permission-controller/src/errors.ts | 1 + .../src/rpc-methods/requestPermissions.test.ts | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/permission-controller/src/PermissionController.test.ts b/packages/permission-controller/src/PermissionController.test.ts index eb960aee3c8..a78d1c6bfdc 100644 --- a/packages/permission-controller/src/PermissionController.test.ts +++ b/packages/permission-controller/src/PermissionController.test.ts @@ -5271,7 +5271,13 @@ describe('PermissionController', () => { }; const expectedError = errors.unauthorized({ - data: { origin, method: PermissionNames.wallet_getSecretArray }, + data: { + origin, + method: PermissionNames.wallet_getSecretArray, + cause: null, + }, + message: + 'Unauthorized to perform action. Try requesting the required permission(s) first. For more information, see: https://docs.metamask.io/guide/rpc-api.html#permissions', }); const { error }: any = await engine.handle(request); diff --git a/packages/permission-controller/src/errors.ts b/packages/permission-controller/src/errors.ts index 7e9e33ba246..43a80b7d87f 100644 --- a/packages/permission-controller/src/errors.ts +++ b/packages/permission-controller/src/errors.ts @@ -10,6 +10,7 @@ import type { PermissionType } from './Permission'; type UnauthorizedArg = { data?: Record; + message?: string; }; /** diff --git a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts index 889bef7afce..dfa18ef953e 100644 --- a/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts +++ b/packages/permission-controller/src/rpc-methods/requestPermissions.test.ts @@ -70,7 +70,13 @@ describe('requestPermissions RPC method', () => { }); expect(response.result).toBeUndefined(); - expect(response.error).toStrictEqual(serializeError(new Error('foo'))); + delete response.error.stack; + delete response.error.data.cause.stack; + const expectedError = new Error('foo'); + delete expectedError.stack; + expect(response.error).toStrictEqual( + serializeError(expectedError, { shouldIncludeStack: false }), + ); expect(mockRequestPermissionsForOrigin).toHaveBeenCalledTimes(1); expect(mockRequestPermissionsForOrigin).toHaveBeenCalledWith({}, '1'); }); From 62ed28f3bcb6eb17c7d03c6094f4cbe4260175a5 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Wed, 27 Sep 2023 00:44:48 +0000 Subject: [PATCH 12/14] deps(controller-utils): remove unused eth-rpc-errors --- packages/controller-utils/package.json | 1 - yarn.lock | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/controller-utils/package.json b/packages/controller-utils/package.json index 9d373ce2a31..33c48d0b00f 100644 --- a/packages/controller-utils/package.json +++ b/packages/controller-utils/package.json @@ -32,7 +32,6 @@ "@metamask/utils": "^8.1.0", "@spruceid/siwe-parser": "1.1.3", "eth-ens-namehash": "^2.0.8", - "eth-rpc-errors": "^4.0.2", "ethereumjs-util": "^7.0.10", "ethjs-unit": "^0.1.6", "fast-deep-equal": "^3.1.3" diff --git a/yarn.lock b/yarn.lock index 39933cd1ab2..c3ed1ab063e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1506,7 +1506,6 @@ __metadata: "@types/jest": ^27.4.1 deepmerge: ^4.2.2 eth-ens-namehash: ^2.0.8 - eth-rpc-errors: ^4.0.2 ethereumjs-util: ^7.0.10 ethjs-unit: ^0.1.6 fast-deep-equal: ^3.1.3 From 159e990fbf18e3d156046fccaf8236945f7cc529 Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Wed, 27 Sep 2023 01:34:17 +0000 Subject: [PATCH 13/14] deps(assets-controllers): @metamask/rpc-errors@^5.1.1->^6.0.0 --- packages/assets-controllers/package.json | 2 +- yarn.lock | 12 +----------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/packages/assets-controllers/package.json b/packages/assets-controllers/package.json index 53b5d98f351..40073f0584d 100644 --- a/packages/assets-controllers/package.json +++ b/packages/assets-controllers/package.json @@ -41,7 +41,7 @@ "@metamask/metamask-eth-abis": "3.0.0", "@metamask/network-controller": "^13.0.0", "@metamask/preferences-controller": "^4.4.1", - "@metamask/rpc-errors": "^5.1.1", + "@metamask/rpc-errors": "^6.0.0", "@metamask/utils": "^8.1.0", "@types/uuid": "^8.3.0", "async-mutex": "^0.2.6", diff --git a/yarn.lock b/yarn.lock index c3ed1ab063e..45dbc1826b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1402,7 +1402,7 @@ __metadata: "@metamask/metamask-eth-abis": 3.0.0 "@metamask/network-controller": ^13.0.0 "@metamask/preferences-controller": ^4.4.1 - "@metamask/rpc-errors": ^5.1.1 + "@metamask/rpc-errors": ^6.0.0 "@metamask/utils": ^8.1.0 "@types/jest": ^27.4.1 "@types/node": ^16.18.24 @@ -2246,16 +2246,6 @@ __metadata: languageName: unknown linkType: soft -"@metamask/rpc-errors@npm:^5.1.1": - version: 5.1.1 - resolution: "@metamask/rpc-errors@npm:5.1.1" - dependencies: - "@metamask/utils": ^5.0.0 - fast-safe-stringify: ^2.0.6 - checksum: ccd1b24da66af3ae63960b79c04b86efb8b96acb89ca6f7e0bbfe636d23ba5cddeba533c0692eafb87c44ec6f840085372d0f21b39e05df9a80700ff61538a30 - languageName: node - linkType: hard - "@metamask/rpc-errors@npm:^6.0.0": version: 6.0.0 resolution: "@metamask/rpc-errors@npm:6.0.0" From 9afb2830e3621506d606ff1dd0276169970cc28d Mon Sep 17 00:00:00 2001 From: legobt <6wbvkn0j@anonaddy.me> Date: Wed, 27 Sep 2023 02:01:35 +0000 Subject: [PATCH 14/14] test(permission-controller): work around error equality prop check after @metamask/rpc-errors upgrade --- .../src/PermissionController.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packages/permission-controller/src/PermissionController.test.ts b/packages/permission-controller/src/PermissionController.test.ts index a78d1c6bfdc..d32f3fd22a9 100644 --- a/packages/permission-controller/src/PermissionController.test.ts +++ b/packages/permission-controller/src/PermissionController.test.ts @@ -5300,6 +5300,11 @@ describe('PermissionController', () => { const expectedError = errors.methodNotFound('wallet_foo', { origin }); const { error }: any = await engine.handle(request); + + expect(error.message).toStrictEqual(expectedError.message); + expect(error.data.cause).toBeNull(); + delete error.message; + delete error.data.cause; expect(error).toMatchObject(expect.objectContaining(expectedError)); }); @@ -5341,6 +5346,10 @@ describe('PermissionController', () => { ); const { error }: any = await engine.handle(request); + expect(error.message).toStrictEqual(expectedError.message); + expect(error.data.cause).toBeNull(); + delete error.message; + delete error.data.cause; expect(error).toMatchObject(expect.objectContaining(expectedError)); }); });