From c906ab9519192cb91780e11282160319355ae6d2 Mon Sep 17 00:00:00 2001 From: Elliot Winkler Date: Thu, 23 Apr 2026 08:32:53 -0600 Subject: [PATCH 1/5] Add guideline for defining the name of a controller (#8528) ## Explanation There is no written standard for defining the name of a controller, and some contributors do not have a common understanding of the do's and dont's for the name. This commit adds such a guideline, highlighting the following points: - The name of a controller should be defined in a variable called `CONTROLLER_NAME` (formerly `controllerName`) - The name should be used to define custom actions and events and should be passed to `BaseController` - The name should not be exported from the package Besides updating the guidelines, this commit also updates the `sample-controllers` package to follow them, most notably changing `controllerName` to `CONTROLLER_NAME` and removing the `export` from this constant. ## References ## Checklist - [ ] 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 - [ ] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them --- > [!NOTE] > **Medium Risk** > Removes an exported constant and renames `controllerName` to `CONTROLLER_NAME` in sample controllers, which could break any consumers importing that symbol even though runtime behavior is unchanged. > > **Overview** > Adds a new controller guideline requiring a single internal `CONTROLLER_NAME` constant to namespace messenger actions/events and `BaseController` state, and explicitly discouraging exporting that constant from the package. > > Updates the `sample-controllers` examples (`sample-gas-prices-controller` and `sample-petnames-controller`) to follow the guideline by replacing exported `controllerName` with an internal `CONTROLLER_NAME` and wiring all messenger/type and `BaseController` references to it. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 4757fdb82a12c7cfc1307f2e0a0461c4a7ba8094. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --- docs/code-guidelines/controller-guidelines.md | 171 ++++++++++++++++++ .../src/sample-gas-prices-controller.ts | 12 +- .../src/sample-petnames-controller.ts | 12 +- 3 files changed, 183 insertions(+), 12 deletions(-) diff --git a/docs/code-guidelines/controller-guidelines.md b/docs/code-guidelines/controller-guidelines.md index 6367a1a50d7..e41d85b648e 100644 --- a/docs/code-guidelines/controller-guidelines.md +++ b/docs/code-guidelines/controller-guidelines.md @@ -30,6 +30,177 @@ The name of the controller should reflect its responsibility. If, when creating Each public method and each state property of a controller should have a purpose, and the name of the method or state property should be readable and should reflect the purpose clearly. If something does not need to be public, it should be made private; if it is unnecessary, it should be removed. +## Define, but do not export, a name for the controller + +Every controller has a name, which is used to namespace not only the controller's messenger actions and events, but also the controller's state data when composed with other controllers. + +The name should be defined in a constant called `CONTROLLER_NAME` so that it can be easily changed if the need arises. The name should be used to initialize the messenger, and it should also be passed to the `BaseController` constructor. + +The constant should be used to define actions and events. It may be exported from the file in which it is defined, but should not listed as an export of the package. + +🚫 **The messenger namespace is not defined as a constant, but is repeated** + +```typescript +export type TransactionsControllerStateChangedEvent = + ControllerStateChangedEvent< + // 🚫 Name is repeated. + 'TransactionsController', + TransactionsControllerState + >; + +export type TransactionsControllerTransactionApprovedEvent = { + // 🚫 Name is repeated. + type: 'TransactionsController:transactionApprovedEvent'; + payload: [transaction: Transaction]; +}; + +export type TransactionsControllerEvents = + | TransactionsControllerStateChangedEvent + | TransactionsControllerTransactionApprovedEvent; + +export type TransactionsControllerMessenger = Messenger< + // 🚫 Name is repeated. + 'TransactionsController', + never, + TransactionsControllerEvents +>; + +// 🚫 Name is repeated. +export class TransactionsController extends BaseController<'TransactionsController' /*, ... */> { + constructor(/* ... */) { + // 🚫 Name is repeated. + super({ name: 'TransactionsController' /* ... */ }); + + // ... + } +} +``` + +🚫 **The messenger namespace is defined as a constant, but it is called `controllerName` (legacy name)** + +```typescript +// 🚫 Uses legacy name. +const controllerName = 'TransactionsController'; + +export type TransactionsControllerStateChangedEvent = ControllerStateChangedEvent< + typeof controllerName, + TransactionsControllerState +>; + +export type TransactionsControllerTransactionApprovedEvent = { + type: `${typeof controllerName}:transactionApprovedEvent`; + payload: [transaction: Transaction] +}; + +export type TransactionsControllerEvents = + | TransactionsControllerStateChangedEvent + | TransactionsControllerTransactionApprovedEvent + +export type TransactionsControllerMessenger = Messenger< + typeof controllerName, + never, + TransactionsControllerEvents +>; + +export class TransactionsController extends BaseController; + +export type TransactionsControllerTransactionApprovedEvent = { + type: `${typeof CONTROLLER_NAME}:transactionApprovedEvent`; + payload: [transaction: Transaction] +}; + +export type TransactionsControllerEvents = + | TransactionsControllerStateChangedEvent + | TransactionsControllerTransactionApprovedEvent + +export type TransactionsControllerMessenger = Messenger< + typeof CONTROLLER_NAME, + never, + TransactionsControllerEvents +>; + +export class TransactionsController extends BaseController; + +export type TransactionsControllerTransactionApprovedEvent = { + type: `${typeof CONTROLLER_NAME}:transactionApprovedEvent`; + payload: [transaction: Transaction] +}; + +export type TransactionsControllerEvents = + | TransactionsControllerStateChangedEvent + | TransactionsControllerTransactionApprovedEvent + +export type TransactionsControllerMessenger = Messenger< + typeof CONTROLLER_NAME, + never, + TransactionsControllerEvents +>; + +export class TransactionsController extends BaseController; @@ -117,7 +117,7 @@ type AllowedActions = */ export type SampleGasPricesControllerStateChangeEvent = ControllerStateChangeEvent< - typeof controllerName, + typeof CONTROLLER_NAME, SampleGasPricesControllerState >; @@ -138,7 +138,7 @@ type AllowedEvents = NetworkControllerStateChangeEvent; * {@link SampleGasPricesController}. */ export type SampleGasPricesControllerMessenger = Messenger< - typeof controllerName, + typeof CONTROLLER_NAME, SampleGasPricesControllerActions | AllowedActions, SampleGasPricesControllerEvents | AllowedEvents >; @@ -219,7 +219,7 @@ export type SampleGasPricesControllerMessenger = Messenger< * ``` */ export class SampleGasPricesController extends BaseController< - typeof controllerName, + typeof CONTROLLER_NAME, SampleGasPricesControllerState, SampleGasPricesControllerMessenger > { @@ -246,7 +246,7 @@ export class SampleGasPricesController extends BaseController< super({ messenger, metadata: gasPricesControllerMetadata, - name: controllerName, + name: CONTROLLER_NAME, state: { ...getDefaultSampleGasPricesControllerState(), ...state, diff --git a/packages/sample-controllers/src/sample-petnames-controller.ts b/packages/sample-controllers/src/sample-petnames-controller.ts index 4124688f96d..a8b18844537 100644 --- a/packages/sample-controllers/src/sample-petnames-controller.ts +++ b/packages/sample-controllers/src/sample-petnames-controller.ts @@ -17,7 +17,7 @@ import type { SamplePetnamesControllerMethodActions } from './sample-petnames-co * controller's actions and events and to namespace the controller's state data * when composed with other controllers. */ -export const controllerName = 'SamplePetnamesController'; +const CONTROLLER_NAME = 'SamplePetnamesController'; // === STATE === @@ -70,7 +70,7 @@ const MESSENGER_EXPOSED_METHODS = ['assignPetname'] as const; * Retrieves the state of the {@link SamplePetnamesController}. */ export type SamplePetnamesControllerGetStateAction = ControllerGetStateAction< - typeof controllerName, + typeof CONTROLLER_NAME, SamplePetnamesControllerState >; @@ -91,7 +91,7 @@ type AllowedActions = never; */ export type SamplePetnamesControllerStateChangeEvent = ControllerStateChangeEvent< - typeof controllerName, + typeof CONTROLLER_NAME, SamplePetnamesControllerState >; @@ -112,7 +112,7 @@ type AllowedEvents = never; * {@link SamplePetnamesController}. */ export type SamplePetnamesControllerMessenger = Messenger< - typeof controllerName, + typeof CONTROLLER_NAME, SamplePetnamesControllerActions | AllowedActions, SamplePetnamesControllerEvents | AllowedEvents >; @@ -167,7 +167,7 @@ export type SamplePetnamesControllerMessenger = Messenger< * ``` */ export class SamplePetnamesController extends BaseController< - typeof controllerName, + typeof CONTROLLER_NAME, SamplePetnamesControllerState, SamplePetnamesControllerMessenger > { @@ -189,7 +189,7 @@ export class SamplePetnamesController extends BaseController< super({ messenger, metadata: samplePetnamesControllerMetadata, - name: controllerName, + name: CONTROLLER_NAME, state: { ...getDefaultPetnamesControllerState(), ...state, From 16e275c08817151abe40620a87f03ee1944219e7 Mon Sep 17 00:00:00 2001 From: Bryan Fullam Date: Thu, 23 Apr 2026 16:44:48 +0200 Subject: [PATCH 2/5] Revert "Release 934.0.0" (#8570) Reverts MetaMask/core#8569 --- > [!NOTE] > **Low Risk** > Low risk because this PR only reverts package versioning/changelog metadata and downgrades internal dependency ranges; no runtime code changes are included. > > **Overview** > Reverts the `934.0.0` release metadata by rolling back the monorepo and several package versions (`bridge-controller`, `bridge-status-controller`, `perps-controller`, `transaction-pay-controller`). > > Updates changelog compare links/headers accordingly and downgrades `@metamask/bridge-controller` / `@metamask/bridge-status-controller` dependency ranges in downstream packages, with corresponding `yarn.lock` adjustments. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 826c037b5e2dceeac5ac12136003f3474a89e469. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --- package.json | 2 +- packages/bridge-controller/CHANGELOG.md | 5 +---- packages/bridge-controller/package.json | 2 +- packages/bridge-status-controller/CHANGELOG.md | 7 ++----- packages/bridge-status-controller/package.json | 4 ++-- packages/perps-controller/CHANGELOG.md | 5 +---- packages/perps-controller/package.json | 2 +- packages/transaction-pay-controller/CHANGELOG.md | 7 +------ packages/transaction-pay-controller/package.json | 6 +++--- yarn.lock | 10 +++++----- 10 files changed, 18 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 09811372c13..93220958472 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/core-monorepo", - "version": "934.0.0", + "version": "933.0.0", "private": true, "description": "Monorepo for packages shared between MetaMask clients", "repository": { diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 7f7a99de36a..143fea637b0 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [71.0.0] - ### Added - Add `AccountHardwareType` type and `getAccountHardwareType` function to the package exports ([#8503](https://github.com/MetaMask/core/pull/8503)) @@ -1376,8 +1374,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release ([#5317](https://github.com/MetaMask/core/pull/5317)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@71.0.0...HEAD -[71.0.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.1.1...@metamask/bridge-controller@71.0.0 +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.1.1...HEAD [70.1.1]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.1.0...@metamask/bridge-controller@70.1.1 [70.1.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.0.1...@metamask/bridge-controller@70.1.0 [70.0.1]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.0.0...@metamask/bridge-controller@70.0.1 diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index dfbec53811a..784b06ae6c3 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/bridge-controller", - "version": "71.0.0", + "version": "70.1.1", "description": "Manages bridge-related quote fetching functionality for MetaMask", "keywords": [ "Ethereum", diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index 31d3e7f670b..feb61b7f54a 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [71.0.0] - ### Added - Remove stale bridge transactions from `txHistory` to prevent excessive polling. Once a history item exceeds the configured maximum age, the status is fetched once, then the src tx hash's receipt is retrieved. If there is no receipt, the history item's hash is presumed to be invalid and the entry is deleted from state. ([#8479](https://github.com/MetaMask/core/pull/8479)) @@ -31,7 +29,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373)) - Bump `@metamask/transaction-controller` from `^64.0.0` to `^64.3.0` ([#8432](https://github.com/MetaMask/core/pull/8432), [#8447](https://github.com/MetaMask/core/pull/8447), [#8482](https://github.com/MetaMask/core/pull/8482)) - Bump `@metamask/base-controller` from `^9.0.1` to `^9.1.0` ([#8457](https://github.com/MetaMask/core/pull/8457)) -- Bump `@metamask/bridge-controller` from `^70.0.1` to `^71.0.0` ([#8466](https://github.com/MetaMask/core/pull/8466), [#8474](https://github.com/MetaMask/core/pull/8474), [#8569](https://github.com/MetaMask/core/pull/8569)) +- Bump `@metamask/bridge-controller` from `^70.0.1` to `^70.1.1` ([#8466](https://github.com/MetaMask/core/pull/8466), [#8474](https://github.com/MetaMask/core/pull/8474)) ### Fixed @@ -1135,8 +1133,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release ([#5317](https://github.com/MetaMask/core/pull/5317)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@71.0.0...HEAD -[71.0.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.5...@metamask/bridge-status-controller@71.0.0 +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.5...HEAD [70.0.5]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.4...@metamask/bridge-status-controller@70.0.5 [70.0.4]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.3...@metamask/bridge-status-controller@70.0.4 [70.0.3]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.2...@metamask/bridge-status-controller@70.0.3 diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 2d5b0fb1aa3..0e4e2eebfdc 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/bridge-status-controller", - "version": "71.0.0", + "version": "70.0.5", "description": "Manages bridge-related status fetching functionality for MetaMask", "keywords": [ "Ethereum", @@ -54,7 +54,7 @@ "dependencies": { "@metamask/accounts-controller": "^37.2.0", "@metamask/base-controller": "^9.1.0", - "@metamask/bridge-controller": "^71.0.0", + "@metamask/bridge-controller": "^70.1.1", "@metamask/controller-utils": "^11.20.0", "@metamask/gas-fee-controller": "^26.1.1", "@metamask/keyring-controller": "^25.2.0", diff --git a/packages/perps-controller/CHANGELOG.md b/packages/perps-controller/CHANGELOG.md index 1a5dd9af885..1e17c0b1463 100644 --- a/packages/perps-controller/CHANGELOG.md +++ b/packages/perps-controller/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [4.0.0] - ### Added - Add `coalescePerpsRestRequest` utility for deduplicating concurrent REST requests with account-scoped cache keys ([#8560](https://github.com/MetaMask/core/pull/8560)) @@ -245,8 +243,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/controller-utils` from `^11.18.0` to `^11.19.0` ([#7995](https://github.com/MetaMask/core/pull/7995)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@4.0.0...HEAD -[4.0.0]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.2.0...@metamask/perps-controller@4.0.0 +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.2.0...HEAD [3.2.0]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.1.1...@metamask/perps-controller@3.2.0 [3.1.1]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.1.0...@metamask/perps-controller@3.1.1 [3.1.0]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.0.0...@metamask/perps-controller@3.1.0 diff --git a/packages/perps-controller/package.json b/packages/perps-controller/package.json index f9b9471a2a8..501342b0a9b 100644 --- a/packages/perps-controller/package.json +++ b/packages/perps-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/perps-controller", - "version": "4.0.0", + "version": "3.2.0", "description": "Controller for perpetual trading functionality in MetaMask", "keywords": [ "Ethereum", diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index 412c41f9c0e..92207d1419b 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -7,8 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [19.3.0] - ### Added - Add support for transaction config parameter accountOverride ([#8454](https://github.com/MetaMask/core/pull/8454)) @@ -17,8 +15,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/assets-controller` from `^6.0.0` to `^6.1.0` ([#8559](https://github.com/MetaMask/core/pull/8559)) - Bump `@metamask/assets-controllers` from `^104.2.0` to `^104.3.0` ([#8559](https://github.com/MetaMask/core/pull/8559)) -- Bump `@metamask/bridge-controller` from `^70.1.1` to `^71.0.0` ([#8569](https://github.com/MetaMask/core/pull/8569)) -- Bump `@metamask/bridge-status-controller` from `^70.0.5` to `^71.0.0` ([#8569](https://github.com/MetaMask/core/pull/8569)) ## [19.2.2] @@ -702,8 +698,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release ([#6820](https://github.com/MetaMask/core/pull/6820)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.3.0...HEAD -[19.3.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.2...@metamask/transaction-pay-controller@19.3.0 +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.2...HEAD [19.2.2]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.1...@metamask/transaction-pay-controller@19.2.2 [19.2.1]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.0...@metamask/transaction-pay-controller@19.2.1 [19.2.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.1.3...@metamask/transaction-pay-controller@19.2.0 diff --git a/packages/transaction-pay-controller/package.json b/packages/transaction-pay-controller/package.json index 9a1eece5e33..bf449a6c629 100644 --- a/packages/transaction-pay-controller/package.json +++ b/packages/transaction-pay-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/transaction-pay-controller", - "version": "19.3.0", + "version": "19.2.2", "description": "Manages alternate payment strategies to provide required funds for transactions in MetaMask", "keywords": [ "Ethereum", @@ -60,8 +60,8 @@ "@metamask/assets-controller": "^6.1.0", "@metamask/assets-controllers": "^104.3.0", "@metamask/base-controller": "^9.1.0", - "@metamask/bridge-controller": "^71.0.0", - "@metamask/bridge-status-controller": "^71.0.0", + "@metamask/bridge-controller": "^70.1.1", + "@metamask/bridge-status-controller": "^70.0.5", "@metamask/controller-utils": "^11.20.0", "@metamask/gas-fee-controller": "^26.1.1", "@metamask/messenger": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index 975a7d3ff66..5c1220e5fed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3009,7 +3009,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/bridge-controller@npm:^71.0.0, @metamask/bridge-controller@workspace:packages/bridge-controller": +"@metamask/bridge-controller@npm:^70.1.1, @metamask/bridge-controller@workspace:packages/bridge-controller": version: 0.0.0-use.local resolution: "@metamask/bridge-controller@workspace:packages/bridge-controller" dependencies: @@ -3056,14 +3056,14 @@ __metadata: languageName: unknown linkType: soft -"@metamask/bridge-status-controller@npm:^71.0.0, @metamask/bridge-status-controller@workspace:packages/bridge-status-controller": +"@metamask/bridge-status-controller@npm:^70.0.5, @metamask/bridge-status-controller@workspace:packages/bridge-status-controller": version: 0.0.0-use.local resolution: "@metamask/bridge-status-controller@workspace:packages/bridge-status-controller" dependencies: "@metamask/accounts-controller": "npm:^37.2.0" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/bridge-controller": "npm:^71.0.0" + "@metamask/bridge-controller": "npm:^70.1.1" "@metamask/controller-utils": "npm:^11.20.0" "@metamask/gas-fee-controller": "npm:^26.1.1" "@metamask/keyring-controller": "npm:^25.2.0" @@ -5665,8 +5665,8 @@ __metadata: "@metamask/assets-controllers": "npm:^104.3.0" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/bridge-controller": "npm:^71.0.0" - "@metamask/bridge-status-controller": "npm:^71.0.0" + "@metamask/bridge-controller": "npm:^70.1.1" + "@metamask/bridge-status-controller": "npm:^70.0.5" "@metamask/controller-utils": "npm:^11.20.0" "@metamask/gas-fee-controller": "npm:^26.1.1" "@metamask/messenger": "npm:^1.1.1" From f60ccbabd1c41a37f0150c53d0277a2d008df30a Mon Sep 17 00:00:00 2001 From: Charly Chevalier Date: Thu, 23 Apr 2026 16:47:45 +0200 Subject: [PATCH 3/5] feat(keyring-controller): add `withController` for atomic operations over multiple keyrings (#8416) ## Explanation Today there's no way to make multiple operations in an "atomic" (read transactional) way. A good example of this is if you want to use a keyring using `withKeyring` that's not existing yet (I'm omitting the `createIfMissing` variants, as we wanted to move away from this pattern). To do this in a safe way, you usually have to use your own lock to make sure you can get-or-create the keyring and prevent concurrent keyring creations. This new `withController` is based on the `withKeyring` but with an access to a "restricted" state and methods of the controller. This way, you can interact with multiple keyring at once while being guarded (to prevent race-conditions) by the controller's global lock. The former problem can then be written that way now: ```ts const account = await keyringController.withController(async (controller) => { // Here, `controller.keyrings` is a "view" on the existing keyrings (instances), only valid // for this block. let keyring: MyKeyring | undefined = controller.keyrings.find(isMyKeyring); if (!keyring) { const { keyring: myKeyring } = await controller.addNewKeyring({ type: 'My Keyring', data: { ... }}); keyring = myKeyring; } const [account] = await keyring.createAccounts(...); return account; }); ``` This will also be used to write the migration from the existing `SnapKeyring` (1 for ALL Snaps) to multiple `SnapKeyring` (1 PER Snap) in a safe way like: ```ts await keyringController.withController(async (controller) => { const accounts: Map = new Map(); // Get existing Snap accounts from the single Snap keyring instance we have today. const keyring: SnapKeyring | undefined = controller.keyrings.find(isSnapKeyring); if (keyring) { for (const account of keyring.listAccounts()) { accounts[account.metadata.snap.id] ??= []; accounts[account.metadata.snap.id].push(account); } } // Re-create all new Snap keyrings, 1 per Snap. for (const [snapId, snapAccounts] of accounts.entries()) { await controller.addNewKeyring({ type: 'Snap keyring', data: snapAccounts }); } // We can safely remove the existing Snap keyring now. await controller.removeKeyring(...); }); ``` ## References N/A ## 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 communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them --- > [!NOTE] > **Medium Risk** > Adds a new transactional API that can create/remove keyrings and then persist/rollback state, which touches keyring lifecycle and vault persistence paths. Risk is moderate due to potential edge cases around staged mutations, keyring destruction, and primary keyring protection. > > **Overview** > Adds `KeyringController.withController` (and messenger action `KeyringController:withController`) to run **single-lock, atomic operations across multiple keyrings** via a `RestrictedController` that exposes a live read-only view plus staged `addNewKeyring`/`removeKeyring` mutations. > > On success it commits staged keyring list changes, persists via existing persist/rollback flow, and destroys removed keyrings; on error it rolls back and destroys any newly created keyrings. The PR also blocks removal of the primary HD keyring (`CannotRemovePrimaryKeyring`), extends `KeyringEntry`/callback typings, updates mocks to include `destroy`, and adds comprehensive unit + messenger tests for the new behavior. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 0ae24f643bfb15f9356ca49c8802e4557ce87478. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --- packages/keyring-controller/CHANGELOG.md | 2 + .../KeyringController-method-action-types.ts | 21 +- .../src/KeyringController.test.ts | 297 ++++++++++++++++++ .../src/KeyringController.ts | 195 ++++++++++-- packages/keyring-controller/src/constants.ts | 1 + packages/keyring-controller/src/index.ts | 1 + .../tests/mocks/mockKeyring.ts | 4 + 7 files changed, 501 insertions(+), 20 deletions(-) diff --git a/packages/keyring-controller/CHANGELOG.md b/packages/keyring-controller/CHANGELOG.md index 76b50c87d51..2c94b6cbea2 100644 --- a/packages/keyring-controller/CHANGELOG.md +++ b/packages/keyring-controller/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Add `withController` action to run atomic operations on multiple keyrings (within a single transaction) ([#8416](https://github.com/MetaMask/core/pull/8416)) + - This action uses a `RestrictedController` object that exposes `addNewKeyring` and `removeKeyring` methods to add and remove keyring during the transaction (atomic) call. - Expose `KeyringController:signTransaction` method through `KeyringController` messenger ([#8408](https://github.com/MetaMask/core/pull/8408)) - Persist vault when keyring state changes during unlock ([#8415](https://github.com/MetaMask/core/pull/8415)) - If a keyring's serialized state differs after deserialization (e.g. a migration ran, or metadata was missing), the vault is now re-persisted so the change is not lost on the next unlock. diff --git a/packages/keyring-controller/src/KeyringController-method-action-types.ts b/packages/keyring-controller/src/KeyringController-method-action-types.ts index 6fb608c050d..3f1bd62fee4 100644 --- a/packages/keyring-controller/src/KeyringController-method-action-types.ts +++ b/packages/keyring-controller/src/KeyringController-method-action-types.ts @@ -375,6 +375,24 @@ export type KeyringControllerWithKeyringV2UnsafeAction = { handler: KeyringController['withKeyringV2Unsafe']; }; +/** + * Execute an operation against all keyrings as a mutually exclusive atomic + * operation. The operation receives a {@link RestrictedController} instance + * that exposes a read-only live view of all keyrings as well as + * `addNewKeyring` and `removeKeyring` methods to stage mutations. + * + * The method automatically persists changes at the end of the function + * execution, or rolls back the changes if an error is thrown. + * + * @param operation - Function to execute with the restricted controller. + * @returns Promise resolving to the result of the function execution. + * @template CallbackResult - The type of the value resolved by the callback function. + */ +export type KeyringControllerWithControllerAction = { + type: `KeyringController:withController`; + handler: KeyringController['withController']; +}; + /** * Union of all KeyringController action types. */ @@ -401,4 +419,5 @@ export type KeyringControllerMethodActions = | KeyringControllerWithKeyringAction | KeyringControllerWithKeyringUnsafeAction | KeyringControllerWithKeyringV2Action - | KeyringControllerWithKeyringV2UnsafeAction; + | KeyringControllerWithKeyringV2UnsafeAction + | KeyringControllerWithControllerAction; diff --git a/packages/keyring-controller/src/KeyringController.test.ts b/packages/keyring-controller/src/KeyringController.test.ts index 57cd39afe3f..0aa455b3775 100644 --- a/packages/keyring-controller/src/KeyringController.test.ts +++ b/packages/keyring-controller/src/KeyringController.test.ts @@ -4068,6 +4068,281 @@ describe('KeyringController', () => { }); }); + describe('withController', () => { + it('throws if the controller is locked', async () => { + await withController( + { skipVaultCreation: true }, + async ({ controller }) => { + await expect(controller.withController(jest.fn())).rejects.toThrow( + KeyringControllerErrorMessage.ControllerLocked, + ); + }, + ); + }); + + it('provides the current keyrings to the callback', async () => { + await withController(async ({ controller, initialState }) => { + await controller.withController(async (restrictedController) => { + expect(restrictedController.keyrings).toHaveLength(1); + expect(restrictedController.keyrings[0].metadata).toStrictEqual( + initialState.keyrings[0].metadata, + ); + }); + }); + }); + + it('returns the result of the callback', async () => { + await withController(async ({ controller }) => { + const result = await controller.withController(async () => 'hello'); + expect(result).toBe('hello'); + }); + }); + + it('throws if the callback returns a raw keyring instance', async () => { + await withController(async ({ controller }) => { + await expect( + controller.withController(async (restrictedController) => { + return restrictedController.keyrings[0].keyring; + }), + ).rejects.toThrow( + KeyringControllerErrorMessage.UnsafeDirectKeyringAccess, + ); + }); + }); + + it('throws if the callback returns a raw keyring (v2) instance', async () => { + await withController(async ({ controller }) => { + await expect( + controller.withController(async (restrictedController) => { + return restrictedController.keyrings[0].keyringV2; + }), + ).rejects.toThrow( + KeyringControllerErrorMessage.UnsafeDirectKeyringAccess, + ); + }); + }); + + describe('addNewKeyring', () => { + it('creates an initialized keyring and stages it for commit', async () => { + const mockAddress = '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4'; + stubKeyringClassWithAccount(MockKeyring, mockAddress); + + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.withController(async (restrictedController) => { + const entry = await restrictedController.addNewKeyring( + MockKeyring.type, + ); + + expect(entry.keyring).toBeInstanceOf(MockKeyring); + expect(entry.metadata.id).toBeDefined(); + }); + + expect(controller.state.keyrings).toHaveLength(2); + }, + ); + }); + + it('populates keyringV2 when a V2 builder is registered for the type', async () => { + await withController(async ({ controller }) => { + await controller.withController(async (restrictedController) => { + const entry = await restrictedController.addNewKeyring( + KeyringTypes.simple, + ); + + expect(entry.keyringV2).toBeDefined(); + }); + }); + }); + + it('appears immediately in restrictedController.keyrings', async () => { + const mockAddress = '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4'; + stubKeyringClassWithAccount(MockKeyring, mockAddress); + + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.withController(async (restrictedController) => { + expect(restrictedController.keyrings).toHaveLength(1); + await restrictedController.addNewKeyring(MockKeyring.type); + expect(restrictedController.keyrings).toHaveLength(2); + }); + }, + ); + }); + + it('destroys created keyrings and does not commit them if the callback throws', async () => { + const mockAddress = '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4'; + stubKeyringClassWithAccount(MockKeyring, mockAddress); + const destroySpy = jest + .spyOn(MockKeyring.prototype, 'destroy') + .mockResolvedValue(undefined); + + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await expect( + controller.withController(async (restrictedController) => { + await restrictedController.addNewKeyring(MockKeyring.type); + throw new Error('Oops'); + }), + ).rejects.toThrow('Oops'); + + expect(destroySpy).toHaveBeenCalledTimes(1); + expect(controller.state.keyrings).toHaveLength(1); + }, + ); + }); + }); + + describe('removeKeyring', () => { + it('removes a keyring by id and commits the removal', async () => { + const mockAddress = '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4'; + stubKeyringClassWithAccount(MockKeyring, mockAddress); + + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.addNewKeyring(MockKeyring.type); + const idToRemove = controller.state.keyrings[1].metadata.id; + + await controller.withController(async (restrictedController) => { + await restrictedController.removeKeyring(idToRemove); + }); + + expect(controller.state.keyrings).toHaveLength(1); + expect( + controller.state.keyrings.find( + (k) => k.metadata.id === idToRemove, + ), + ).toBeUndefined(); + }, + ); + }); + + it('disappears from restrictedController.keyrings immediately', async () => { + const mockAddress = '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4'; + stubKeyringClassWithAccount(MockKeyring, mockAddress); + + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.addNewKeyring(MockKeyring.type); + const idToRemove = controller.state.keyrings[1].metadata.id; + + await controller.withController(async (restrictedController) => { + expect(restrictedController.keyrings).toHaveLength(2); + await restrictedController.removeKeyring(idToRemove); + expect(restrictedController.keyrings).toHaveLength(1); + }); + }, + ); + }); + + it('destroys the removed keyring', async () => { + const mockAddress = '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4'; + stubKeyringClassWithAccount(MockKeyring, mockAddress); + const destroySpy = jest + .spyOn(MockKeyring.prototype, 'destroy') + .mockResolvedValue(undefined); + + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.addNewKeyring(MockKeyring.type); + const idToRemove = controller.state.keyrings[1].metadata.id; + + await controller.withController(async (restrictedController) => { + await restrictedController.removeKeyring(idToRemove); + }); + + expect(destroySpy).toHaveBeenCalledTimes(1); + }, + ); + }); + + it('throws KeyringNotFound for an unknown id', async () => { + await withController(async ({ controller }) => { + await expect( + controller.withController(async (restrictedController) => { + await restrictedController.removeKeyring('non-existent-id'); + }), + ).rejects.toThrow(KeyringControllerErrorMessage.KeyringNotFound); + }); + }); + + it('throws CannotRemovePrimaryHdKeyring when attempting to remove the primary HD keyring', async () => { + await withController(async ({ controller }) => { + const primaryId = controller.state.keyrings[0].metadata.id; + + await expect( + controller.withController(async (restrictedController) => { + await restrictedController.removeKeyring(primaryId); + }), + ).rejects.toThrow( + KeyringControllerErrorMessage.CannotRemovePrimaryKeyring, + ); + + expect(controller.state.keyrings).toHaveLength(1); + }); + }); + + it('destroys a keyring that was created then removed within the same callback', async () => { + const mockAddress = '0x4584d2B4905087A100420AFfCe1b2d73fC69B8E4'; + stubKeyringClassWithAccount(MockKeyring, mockAddress); + const destroySpy = jest + .spyOn(MockKeyring.prototype, 'destroy') + .mockResolvedValue(undefined); + + await withController( + { keyringBuilders: [keyringBuilderFactory(MockKeyring)] }, + async ({ controller }) => { + await controller.withController(async (restrictedController) => { + const { metadata } = await restrictedController.addNewKeyring( + MockKeyring.type, + ); + await restrictedController.removeKeyring(metadata.id); + }); + + expect(destroySpy).toHaveBeenCalledTimes(1); + expect(controller.state.keyrings).toHaveLength(1); + }, + ); + }); + }); + + it('rolls back on error', async () => { + await withController(async ({ controller, initialState }) => { + await expect( + controller.withController(async (restrictedController) => { + await restrictedController.addNewKeyring(KeyringTypes.simple); + throw new Error('Oops'); + }), + ).rejects.toThrow('Oops'); + + expect(controller.state.keyrings).toHaveLength( + initialState.keyrings.length, + ); + expect(await controller.getAccounts()).toStrictEqual( + initialState.keyrings[0].accounts, + ); + }); + }); + + it('does not update the vault if no keyrings change', async () => { + await withController(async ({ controller, encryptor }) => { + const encryptSpy = jest.spyOn(encryptor, 'encrypt'); + + await controller.withController(async () => { + // no-op + }); + + expect(encryptSpy).not.toHaveBeenCalled(); + }); + }); + }); + describe('withKeyringUnsafe', () => { it('calls the given function without acquiring the lock', async () => { await withController(async ({ controller }) => { @@ -5025,6 +5300,28 @@ describe('KeyringController', () => { }); }); + describe('withController', () => { + it('should call withController', async () => { + await withController(async ({ messenger }) => { + const operation = jest.fn().mockResolvedValue('result'); + + const actionReturnValue = await messenger.call( + 'KeyringController:withController', + operation, + ); + + expect(operation).toHaveBeenCalledWith( + expect.objectContaining({ + keyrings: expect.any(Array), + addNewKeyring: expect.any(Function), + removeKeyring: expect.any(Function), + }), + ); + expect(actionReturnValue).toBe('result'); + }); + }); + }); + describe('addNewKeyring', () => { it('should call addNewKeyring', async () => { const mockKeyringMetadata: KeyringMetadata = { diff --git a/packages/keyring-controller/src/KeyringController.ts b/packages/keyring-controller/src/KeyringController.ts index 415a912b9d6..3b8ef6aa691 100644 --- a/packages/keyring-controller/src/KeyringController.ts +++ b/packages/keyring-controller/src/KeyringController.ts @@ -67,6 +67,7 @@ const MESSENGER_EXPOSED_METHODS = [ 'patchUserOperation', 'signUserOperation', 'addNewAccount', + 'withController', 'withKeyring', 'withKeyringUnsafe', 'withKeyringV2', @@ -232,7 +233,10 @@ export type KeyringMetadata = { name: string; }; -type KeyringEntry = { +/** + * A keyring entry, including the keyring instance (+ v2 instance) and its metadata. + */ +export type KeyringEntry = { /** * The keyring instance. */ @@ -249,6 +253,37 @@ type KeyringEntry = { metadata: KeyringMetadata; }; +/** + * A restricted view of the {@link KeyringController} exposed to the callback + * passed to {@link KeyringController.withController}. + * + * It provides a read-only live view of all keyrings and the ability to stage + * keyring additions and removals atomically within a single transaction. + */ +export type RestrictedController = { + /** + * Read-only live view of all keyrings in the current transaction (original + * keyrings plus any added, minus any removed so far in this callback). + */ + readonly keyrings: readonly KeyringEntry[]; + /** + * Create a new keyring of the given type and stage it for commit. The new + * entry is immediately visible in {@link RestrictedController.keyrings}. + * + * @param type - The type of keyring to create. + * @param opts - Optional data to pass to the keyring builder. + * @returns The newly created `{ keyring, metadata }` entry. + */ + addNewKeyring(type: string, opts?: unknown): Promise; + /** + * Stage the keyring with the given id for removal. The keyring is + * immediately removed from {@link RestrictedController.keyrings}. + * + * @param id - The id of the keyring to remove. + */ + removeKeyring(id: string): Promise; +}; + /** * A strategy for importing an account */ @@ -668,10 +703,7 @@ function isSerializedKeyringsArray( async function displayForKeyring({ keyring, metadata, -}: { - keyring: EthKeyring; - metadata: KeyringMetadata; -}): Promise { +}: KeyringEntry): Promise { const accounts = await keyring.getAccounts(); return { @@ -1765,13 +1797,7 @@ export class KeyringController< CallbackResult = void, >( selector: KeyringSelector, - operation: ({ - keyring, - metadata, - }: { - keyring: SelectedKeyring; - metadata: KeyringMetadata; - }) => Promise, + operation: ({ keyring, metadata }: KeyringEntry) => Promise, // eslint-disable-next-line @typescript-eslint/unified-signatures options: | { createIfMissing?: false } @@ -1798,13 +1824,7 @@ export class KeyringController< CallbackResult = void, >( selector: KeyringSelector, - operation: ({ - keyring, - metadata, - }: { - keyring: SelectedKeyring; - metadata: KeyringMetadata; - }) => Promise, + operation: ({ keyring, metadata }: KeyringEntry) => Promise, ): Promise; async withKeyring< @@ -2069,6 +2089,122 @@ export class KeyringController< ); } + /** + * Execute an operation against all keyrings as a mutually exclusive atomic + * operation. The operation receives a {@link RestrictedController} instance + * that exposes a read-only live view of all keyrings as well as + * `addNewKeyring` and `removeKeyring` methods to stage mutations. + * + * The method automatically persists changes at the end of the function + * execution, or rolls back the changes if an error is thrown. + * + * @param operation - Function to execute with the restricted controller. + * @returns Promise resolving to the result of the function execution. + * @template CallbackResult - The type of the value resolved by the callback function. + */ + async withController( + operation: ( + restrictedController: RestrictedController, + ) => Promise, + ): Promise { + this.#assertIsUnlocked(); + + return this.#persistOrRollback(async () => { + // Track created and removed keyrings during the operation execution. + const createdEntries = new Set(); + const removedEntries = new Set(); + + // Copy of the current keyrings that is mutated during the operation execution. + const restrictedEntries = [...this.#keyrings]; + + // The restricted controller proxies the current keyrings and allows staging + // mutations that are only applied to the real keyrings if the operation + // completes successfully. This allows us to have a single source of truth + // for the keyrings during the operation execution, and to automatically + // roll back any changes if an error is thrown. + const restrictedController: RestrictedController = { + // We freeze the array to prevent direct mutations, but the keyring instances + // themselves are not frozen, allowing safe read-only access. + get keyrings() { + return Object.freeze([...restrictedEntries]); + }, + + // Method to create a new keyring and adds it to the restricted entries. + addNewKeyring: async (type: string, opts?: unknown) => { + const entry = await this.#createKeyring(type, opts); + + restrictedEntries.push(entry); + createdEntries.add(entry); + + return entry; + }, + + // Method to remove a keyring from the restricted entries. + removeKeyring: async (id: string) => { + const index = restrictedEntries.findIndex( + (entry) => entry.metadata.id === id, + ); + if (index === -1) { + throw new KeyringControllerError( + KeyringControllerErrorMessage.KeyringNotFound, + ); + } + + this.#assertNotRemovingPrimaryKeyring( + restrictedEntries[index], + restrictedEntries, + ); + + const [removed] = restrictedEntries.splice(index, 1) as [ + KeyringEntry, + ]; + removedEntries.add(removed); + }, + }; + + const destroyKeyrings = async ( + entries: Iterable, + ): Promise => { + await Promise.all( + [...entries].map(({ keyring, keyringV2 }) => + this.#destroyKeyring(keyring, keyringV2), + ), + ); + }; + + let result: CallbackResult; + try { + result = await operation(restrictedController); + } catch (error) { + await destroyKeyrings(createdEntries); + + throw error; + } + + await destroyKeyrings(removedEntries); + + // We update the real keyrings only after the operation completes successfully, so that + // they will be persisted in the vault. + this.#keyrings = restrictedEntries; + + // As usual, we want to prevent returning direct references to keyring instances, so we check + // the result for any unsafe direct access before returning. + for (const { keyring, keyringV2 } of [ + ...this.#keyrings, + // We also check for keyrings that got removed during the operation, since the result could + // still have references to them. + ...removedEntries, + ]) { + this.#assertNoUnsafeDirectKeyringAccess(result, keyring); + if (keyringV2) { + this.#assertNoUnsafeDirectKeyringAccess(result, keyringV2); + } + } + + return result; + }); + } + async getAccountKeyringType(account: string): Promise { this.#assertIsUnlocked(); @@ -2969,6 +3105,27 @@ export class KeyringController< } } + /** + * Assert that the given keyring entry is not the primary HD keyring. + * + * @param entry - The keyring entry to check. + * @param keyrings - The current list of keyring entries. + * @throws If the entry is the primary keyring. + */ + #assertNotRemovingPrimaryKeyring( + entry: KeyringEntry, + keyrings: KeyringEntry[], + ): void { + if ( + keyrings[0] === entry && + entry.keyring.type === (KeyringTypes.hd as string) + ) { + throw new KeyringControllerError( + KeyringControllerErrorMessage.CannotRemovePrimaryKeyring, + ); + } + } + /** * Lock the controller mutex before executing the given function, * and release it after the function is resolved or after an diff --git a/packages/keyring-controller/src/constants.ts b/packages/keyring-controller/src/constants.ts index a5b71a6408a..f97cdd646cb 100644 --- a/packages/keyring-controller/src/constants.ts +++ b/packages/keyring-controller/src/constants.ts @@ -39,4 +39,5 @@ export enum KeyringControllerErrorMessage { LastAccountInPrimaryKeyring = 'KeyringController - Last account in primary keyring cannot be removed', EncryptionKeyNotSet = 'KeyringController - Encryption key not set', KeyringV2NotSupported = 'KeyringController - The selected keyring does not support the KeyringV2 API.', + CannotRemovePrimaryKeyring = 'KeyringController - Cannot remove the primary keyring', } diff --git a/packages/keyring-controller/src/index.ts b/packages/keyring-controller/src/index.ts index 9ac85f95897..4d570f0c98e 100644 --- a/packages/keyring-controller/src/index.ts +++ b/packages/keyring-controller/src/index.ts @@ -19,6 +19,7 @@ export type { KeyringControllerPrepareUserOperationAction, KeyringControllerPatchUserOperationAction, KeyringControllerSignUserOperationAction, + KeyringControllerWithControllerAction, KeyringControllerWithKeyringAction, KeyringControllerWithKeyringUnsafeAction, KeyringControllerWithKeyringV2Action, diff --git a/packages/keyring-controller/tests/mocks/mockKeyring.ts b/packages/keyring-controller/tests/mocks/mockKeyring.ts index 4bfbeb77c52..89bff0f1abd 100644 --- a/packages/keyring-controller/tests/mocks/mockKeyring.ts +++ b/packages/keyring-controller/tests/mocks/mockKeyring.ts @@ -32,4 +32,8 @@ export class MockKeyring implements EthKeyring { async deserialize(_: unknown): Promise { return Promise.resolve(); } + + async destroy(): Promise { + return Promise.resolve(); + } } From c47708169df8944d12d61ef3cf9d15626c2f8b0a Mon Sep 17 00:00:00 2001 From: Guillaume Roux Date: Thu, 23 Apr 2026 16:52:29 +0200 Subject: [PATCH 4/5] chore!: Update `CurrencyRateController` to expose all methods through messenger (#8561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Explanation This updates the `CurrencyRateController` to expose all methods through the messenger in a standardized way. ## 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 communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them --- > [!NOTE] > **Medium Risk** > Medium risk because it introduces a **breaking** rename of the `CurrencyRateController` get-state action type and changes the controller’s messenger surface area, requiring downstream type/import updates. > > **Overview** > `CurrencyRateController` now exposes `setCurrentCurrency` and `updateExchangeRate` via the messenger by registering method action handlers, and adds generated method action type definitions for these calls. > > This standardizes/renames the get-state messenger action type from `GetCurrencyRateState` to `CurrencyRateControllerGetStateAction` (type-only breaking change) and updates internal consumers (`MultichainAssetsRatesController`, `TokenSearchDiscoveryDataController`, and `bridge-controller`) plus exports the new action types from `assets-controllers`. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit ee67d75361238094b8a13050bb1ac97490da4b8e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --- packages/assets-controllers/CHANGELOG.md | 14 ++++++++ ...rencyRateController-method-action-types.ts | 33 +++++++++++++++++++ .../src/CurrencyRateController.ts | 17 ++++++++-- .../MultichainAssetsRatesController.ts | 4 +-- .../TokenSearchDiscoveryDataController.ts | 4 +-- packages/assets-controllers/src/index.ts | 4 +++ packages/bridge-controller/src/types.ts | 4 +-- 7 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 packages/assets-controllers/src/CurrencyRateController-method-action-types.ts diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 80662fac603..b2211ffd559 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Expose missing public `CurrencyRateController` methods through its messenger ([#8561](https://github.com/MetaMask/core/pull/8561)) + - The following actions are now available: + - `CurrencyRateController:setCurrentCurrency` + - `CurrencyRateController:updateExchangeRate` + - Corresponding action types (e.g. `CurrencyRateControllerSetCurrentCurrencyAction`) are available as well. + +### Changed + +- **BREAKING:** Standardize names of `CurrencyRateController` messenger action types ([#8561](https://github.com/MetaMask/core/pull/8561)) + - The `GetCurrencyRateState` messenger action has been renamed to `CurrencyRateControllerGetStateAction` to follow the convention. You will need to update imports appropriately. + - These changes only affect the types. The action type strings themselves have not changed, so you do not need to update the list of actions you pass when initializing `CurrencyRateController` messenger. + ## [104.3.0] ### Added diff --git a/packages/assets-controllers/src/CurrencyRateController-method-action-types.ts b/packages/assets-controllers/src/CurrencyRateController-method-action-types.ts new file mode 100644 index 00000000000..d8a71ecc84e --- /dev/null +++ b/packages/assets-controllers/src/CurrencyRateController-method-action-types.ts @@ -0,0 +1,33 @@ +/** + * This file is auto generated. + * Do not edit manually. + */ + +import type { CurrencyRateController } from './CurrencyRateController'; + +/** + * Sets a currency to track. + * + * @param currentCurrency - ISO 4217 currency code. + */ +export type CurrencyRateControllerSetCurrentCurrencyAction = { + type: `CurrencyRateController:setCurrentCurrency`; + handler: CurrencyRateController['setCurrentCurrency']; +}; + +/** + * Updates the exchange rate for the current currency and native currency pairs. + * + * @param nativeCurrencies - The native currency symbols to fetch exchange rates for. + */ +export type CurrencyRateControllerUpdateExchangeRateAction = { + type: `CurrencyRateController:updateExchangeRate`; + handler: CurrencyRateController['updateExchangeRate']; +}; + +/** + * Union of all CurrencyRateController action types. + */ +export type CurrencyRateControllerMethodActions = + | CurrencyRateControllerSetCurrentCurrencyAction + | CurrencyRateControllerUpdateExchangeRateAction; diff --git a/packages/assets-controllers/src/CurrencyRateController.ts b/packages/assets-controllers/src/CurrencyRateController.ts index d6266083c4c..981f5434585 100644 --- a/packages/assets-controllers/src/CurrencyRateController.ts +++ b/packages/assets-controllers/src/CurrencyRateController.ts @@ -17,6 +17,7 @@ import { StaticIntervalPollingController } from '@metamask/polling-controller'; import type { Hex } from '@metamask/utils'; import { Mutex } from 'async-mutex'; +import type { CurrencyRateControllerMethodActions } from './CurrencyRateController-method-action-types'; import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service'; import { getNativeTokenAddress } from './token-prices-service/codefi-v2'; @@ -45,6 +46,11 @@ export type CurrencyRateState = { const name = 'CurrencyRateController'; +const MESSENGER_EXPOSED_METHODS = [ + 'setCurrentCurrency', + 'updateExchangeRate', +] as const; + export type CurrencyRateStateChange = ControllerStateChangeEvent< typeof name, CurrencyRateState @@ -52,12 +58,14 @@ export type CurrencyRateStateChange = ControllerStateChangeEvent< export type CurrencyRateControllerEvents = CurrencyRateStateChange; -export type GetCurrencyRateState = ControllerGetStateAction< +export type CurrencyRateControllerGetStateAction = ControllerGetStateAction< typeof name, CurrencyRateState >; -export type CurrencyRateControllerActions = GetCurrencyRateState; +export type CurrencyRateControllerActions = + | CurrencyRateControllerGetStateAction + | CurrencyRateControllerMethodActions; type AllowedActions = | NetworkControllerGetNetworkClientByIdAction @@ -168,6 +176,11 @@ export class CurrencyRateController extends StaticIntervalPollingController Date: Thu, 23 Apr 2026 17:39:37 +0200 Subject: [PATCH 5/5] Release 934.0.0 (#8571) ## Explanation ## References ## 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 communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [ ] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them --- > [!NOTE] > **Low Risk** > Low risk because this PR only updates package versions, changelogs, and dependency ranges/lockfile entries without any source code logic changes. > > **Overview** > Bumps the monorepo release to `934.0.0` and publishes new versions of `@metamask/bridge-controller` (`70.2.0`), `@metamask/bridge-status-controller` (`71.0.0`), `@metamask/perps-controller` (`4.0.0`), and `@metamask/transaction-pay-controller` (`19.3.0`). > > Updates corresponding changelog sections and compare links, and aligns downstream dependency ranges (notably `transaction-pay-controller` and `bridge-status-controller`) plus `yarn.lock` to reference the new bridge package versions. > > Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 3aa4d08b06b81e46c1beec7b29a7decab5a8d90a. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Maarten Zuidhoorn --- package.json | 2 +- packages/bridge-controller/CHANGELOG.md | 17 ++++++++------- packages/bridge-controller/package.json | 2 +- .../bridge-status-controller/CHANGELOG.md | 21 +++++++++++-------- .../bridge-status-controller/package.json | 4 ++-- packages/perps-controller/CHANGELOG.md | 7 +++++-- packages/perps-controller/package.json | 2 +- .../transaction-pay-controller/CHANGELOG.md | 7 ++++++- .../transaction-pay-controller/package.json | 6 +++--- yarn.lock | 10 ++++----- 10 files changed, 46 insertions(+), 32 deletions(-) diff --git a/package.json b/package.json index 93220958472..09811372c13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/core-monorepo", - "version": "933.0.0", + "version": "934.0.0", "private": true, "description": "Monorepo for packages shared between MetaMask clients", "repository": { diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 143fea637b0..577af3313d9 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [70.2.0] + ### Added - Add `AccountHardwareType` type and `getAccountHardwareType` function to the package exports ([#8503](https://github.com/MetaMask/core/pull/8503)) @@ -158,8 +160,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bump `@metamask/assets-controllers` from `^100.0.3` to `^100.2.0` ([#8107](https://github.com/MetaMask/core/pull/8107)), ([#8107](https://github.com/MetaMask/core/pull/8107)), ([#8140](https://github.com/MetaMask/core/pull/8140)) -- Bump `@metamask/transaction-controller` from `^62.19.0` to `^62.21.0` ([#8104](https://github.com/MetaMask/core/pull/8104)), ([#8104](https://github.com/MetaMask/core/pull/8104)), ([#8140](https://github.com/MetaMask/core/pull/8140)) +- Bump `@metamask/assets-controllers` from `^100.0.3` to `^100.2.0`,, ([#8107](https://github.com/MetaMask/core/pull/8107), [#8140](https://github.com/MetaMask/core/pull/8140)) +- Bump `@metamask/transaction-controller` from `^62.19.0` to `^62.21.0`,, ([#8104](https://github.com/MetaMask/core/pull/8104), [#8140](https://github.com/MetaMask/core/pull/8140)) - Bump `@metamask/accounts-controller` from `36.0.1` to `37.0.0` ([#8140](https://github.com/MetaMask/core/pull/8140)) - Bump `@metamask/assets-controller` from `2.2.0` to `2.3.0` ([#8140](https://github.com/MetaMask/core/pull/8140)) - Bump `@metamask/multichain-network-controller` from `3.0.4` to `3.0.5` ([#8140](https://github.com/MetaMask/core/pull/8140)) @@ -268,7 +270,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Refresh asset exchange rates each time quotes are fetched ([#7896](https://github.com/MetaMask/core/pull/7896)) - Return checksummed EVM assetIds from the `formatAddressToAssetId` utility ([#7896](https://github.com/MetaMask/core/pull/7896)) - Bump `@metamask/keyring-api` from `^21.0.0` to `^21.5.0` ([#7857](https://github.com/MetaMask/core/pull/7857)) -- Bump `@metamask/transaction-controller` from `^62.15.0` to `^62.17.0` ([#7872](https://github.com/MetaMask/core/pull/7872)), ([#7897](https://github.com/MetaMask/core/pull/7897)) +- Bump `@metamask/transaction-controller` from `^62.15.0` to `^62.17.0`, ([#7872](https://github.com/MetaMask/core/pull/7872), [#7897](https://github.com/MetaMask/core/pull/7897)) - Bump `@metamask/multichain-network-controller` from `^3.0.2` to `^3.0.3` ([#7897](https://github.com/MetaMask/core/pull/7897)) - Bump `@metamask/assets-controllers` from `^99.3.1` to `^99.3.2` ([#7897](https://github.com/MetaMask/core/pull/7897)) - Bump `@metamask/accounts-controller` from `^35.0.2` to `^36.0.0` ([#7897](https://github.com/MetaMask/core/pull/7897)) @@ -769,7 +771,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bump `@metamask/assets-controllers` from `^76.0.0` to `^77.0.0` ([#6716](https://github.com/MetaMask/core/pull/6716), [#6629](https://github.com/MetaMask/core/pull/6716)) +- Bump `@metamask/assets-controllers` from `^76.0.0` to `^77.0.0` ([#6716](https://github.com/MetaMask/core/pull/6716), [#6629](https://github.com/MetaMask/core/pull/6629)) ## [44.0.1] @@ -945,7 +947,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Bump peer dependency `@metamask/accounts-controller` from `^31.0.0` to `^32.0.0` ([#6171](https://github.com/MetaMask/core/pull/6171)) - **BREAKING:** Bump peer dependency `@metamask/assets-controllers` from `^72.0.0` to `^73.0.0` ([#6171](https://github.com/MetaMask/core/pull/6171)) -- **BREAKING:** Bump peer dependency `@metamask/transaction-controller` from `^58.0.0` to `^59.0.0` ([#6171](https://github.com/MetaMask/core/pull/6171)), ([#6027](https://github.com/MetaMask/core/pull/6027)) +- **BREAKING:** Bump peer dependency `@metamask/transaction-controller` from `^58.0.0` to `^59.0.0`, ([#6171](https://github.com/MetaMask/core/pull/6171), [#6027](https://github.com/MetaMask/core/pull/6027)) ## [36.2.0] @@ -1127,7 +1129,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added optional `isUnifiedUIEnabled` flag to chain-level feature-flag `ChainConfiguration` type and updated the validation schema to accept the new flag ([#5783](https://github.com/MetaMask/core/pull/5783)) -- Add and export `calcSlippagePercentage`, a utility that calculates the absolute slippage percentage based on the adjusted return and the sent amount ([#5723](https://github.com/MetaMask/core/pull/5723)). +- Add and export `calcSlippagePercentage`, a utility that calculates the absolute slippage percentage based on the adjusted return and the sent amount. ([#5723](https://github.com/MetaMask/core/pull/5723)) - Error logs for invalid getQuote responses ([#5816](https://github.com/MetaMask/core/pull/5816)) ### Changed @@ -1374,7 +1376,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release ([#5317](https://github.com/MetaMask/core/pull/5317)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.1.1...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.2.0...HEAD +[70.2.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.1.1...@metamask/bridge-controller@70.2.0 [70.1.1]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.1.0...@metamask/bridge-controller@70.1.1 [70.1.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.0.1...@metamask/bridge-controller@70.1.0 [70.0.1]: https://github.com/MetaMask/core/compare/@metamask/bridge-controller@70.0.0...@metamask/bridge-controller@70.0.1 diff --git a/packages/bridge-controller/package.json b/packages/bridge-controller/package.json index 784b06ae6c3..14a40e19dc7 100644 --- a/packages/bridge-controller/package.json +++ b/packages/bridge-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/bridge-controller", - "version": "70.1.1", + "version": "70.2.0", "description": "Manages bridge-related quote fetching functionality for MetaMask", "keywords": [ "Ethereum", diff --git a/packages/bridge-status-controller/CHANGELOG.md b/packages/bridge-status-controller/CHANGELOG.md index feb61b7f54a..39b62830189 100644 --- a/packages/bridge-status-controller/CHANGELOG.md +++ b/packages/bridge-status-controller/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [71.0.0] + ### Added - Remove stale bridge transactions from `txHistory` to prevent excessive polling. Once a history item exceeds the configured maximum age, the status is fetched once, then the src tx hash's receipt is retrieved. If there is no receipt, the history item's hash is presumed to be invalid and the entry is deleted from state. ([#8479](https://github.com/MetaMask/core/pull/8479)) @@ -29,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/messenger` from `^1.0.0` to `^1.1.1` ([#8364](https://github.com/MetaMask/core/pull/8364), [#8373](https://github.com/MetaMask/core/pull/8373)) - Bump `@metamask/transaction-controller` from `^64.0.0` to `^64.3.0` ([#8432](https://github.com/MetaMask/core/pull/8432), [#8447](https://github.com/MetaMask/core/pull/8447), [#8482](https://github.com/MetaMask/core/pull/8482)) - Bump `@metamask/base-controller` from `^9.0.1` to `^9.1.0` ([#8457](https://github.com/MetaMask/core/pull/8457)) -- Bump `@metamask/bridge-controller` from `^70.0.1` to `^70.1.1` ([#8466](https://github.com/MetaMask/core/pull/8466), [#8474](https://github.com/MetaMask/core/pull/8474)) +- Bump `@metamask/bridge-controller` from `^70.0.1` to `^70.2.0` ([#8466](https://github.com/MetaMask/core/pull/8466), [#8474](https://github.com/MetaMask/core/pull/8474), [#8571](https://github.com/MetaMask/core/pull/8571)) ### Fixed @@ -141,7 +143,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bump `@metamask/transaction-controller` from `^62.19.0` to `^62.21.0` ([#8104](https://github.com/MetaMask/core/pull/8104)), ([#8140](https://github.com/MetaMask/core/pull/8140)) +- Bump `@metamask/transaction-controller` from `^62.19.0` to `^62.21.0`, ([#8104](https://github.com/MetaMask/core/pull/8104), [#8140](https://github.com/MetaMask/core/pull/8140)) - Bump `@metamask/accounts-controller` from `^36.0.1` to `^37.0.0` ([8140](https://github.com/MetaMask/core/pull/8140)) - Bump `@metamask/bridge-controller` from `^68.0.0` to `^69.0.0` ([8140](https://github.com/MetaMask/core/pull/8140)) @@ -206,8 +208,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Bump `@metamask/accounts-controller` from `^35.0.2` to `^36.0.0` ([#7897](https://github.com/MetaMask/core/pull/7897)) -- Bump `@metamask/bridge-controller` from `^65.3.0` to `^66.1.0` ([#7862](https://github.com/MetaMask/core/pull/7862)), ([#7897](https://github.com/MetaMask/core/pull/7897)) -- Bump `@metamask/transaction-controller` from `^62.14.0` to `^62.17.0` ([#7854](https://github.com/MetaMask/core/pull/7854), [#7872](https://github.com/MetaMask/core/pull/7872)), ([#7897](https://github.com/MetaMask/core/pull/7897)) +- Bump `@metamask/bridge-controller` from `^65.3.0` to `^66.1.0`, ([#7862](https://github.com/MetaMask/core/pull/7862), [#7897](https://github.com/MetaMask/core/pull/7897)) +- Bump `@metamask/transaction-controller` from `^62.14.0` to `^62.17.0`, ([#7854](https://github.com/MetaMask/core/pull/7854), [#7872](https://github.com/MetaMask/core/pull/7872), [#7897](https://github.com/MetaMask/core/pull/7897)) ## [66.0.0] @@ -572,7 +574,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- Bump `@metamask/bridge-controller` from `^44.0.1` to `^45.0.0` ([#6716](https://github.com/MetaMask/core/pull/6716), [#6629](https://github.com/MetaMask/core/pull/6716)) +- Bump `@metamask/bridge-controller` from `^44.0.1` to `^45.0.0` ([#6716](https://github.com/MetaMask/core/pull/6716), [#6629](https://github.com/MetaMask/core/pull/6629)) ## [44.1.0] @@ -713,7 +715,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Bump peer dependency `@metamask/accounts-controller` to `^32.0.0` ([#6171](https://github.com/MetaMask/core/pull/6171)) - **BREAKING:** Bump peer dependency `@metamask/bridge-controller` to `^37.0.0` ([#6171](https://github.com/MetaMask/core/pull/6171)) -- **BREAKING:** Bump peer dependency `@metamask/transaction-controller` to `^59.0.0` ([#6171](https://github.com/MetaMask/core/pull/6171)), ([#6027](https://github.com/MetaMask/core/pull/6027)) +- **BREAKING:** Bump peer dependency `@metamask/transaction-controller` to `^59.0.0`, ([#6171](https://github.com/MetaMask/core/pull/6171), [#6027](https://github.com/MetaMask/core/pull/6027)) ## [36.1.0] @@ -1028,7 +1030,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **BREAKING** Change `@metamask/bridge-controller` from dependency to peer dependency and bump to `^16.0.0` ([#5657](https://github.com/MetaMask/core/pull/5657), [#5665](https://github.com/MetaMask/core/pull/5665), [#5643](https://github.com/MetaMask/core/pull/5643) [#5672](https://github.com/MetaMask/core/pull/5672)) +- **BREAKING** Change `@metamask/bridge-controller` from dependency to peer dependency and bump to `^16.0.0` ([#5657](https://github.com/MetaMask/core/pull/5657), [#5665](https://github.com/MetaMask/core/pull/5665), [#5643](https://github.com/MetaMask/core/pull/5643), [#5672](https://github.com/MetaMask/core/pull/5672)) - Add optional config.customBridgeApiBaseUrl constructor arg to set the bridge-api base URL ([#5634](https://github.com/MetaMask/core/pull/5634)) - Add required `addTransactionFn` and `estimateGasFeeFn` args to the BridgeStatusController constructor to enable calling TransactionController's methods from `submitTx` ([#5643](https://github.com/MetaMask/core/pull/5643)) - Add optional `addUserOperationFromTransactionFn` arg to the BridgeStatusController constructor to enable submitting txs from smart accounts using the UserOperationController's addUserOperationFromTransaction method ([#5643](https://github.com/MetaMask/core/pull/5643)) @@ -1055,7 +1057,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **BREAKING:** Bump `@metamask/transaction-controller` peer dependency to `^53.0.0` ([#5585](https://github.com/MetaMask/core/pull/5585)) - Bump `@metamask/bridge-controller` dependency to `^11.0.0` ([#5525](https://github.com/MetaMask/core/pull/5525)) -- **BREAKING:** Change controller to fetch multichain address instead of EVM ([#5554](https://github.com/MetaMask/core/pull/5540)) +- **BREAKING:** Change controller to fetch multichain address instead of EVM ([#5554](https://github.com/MetaMask/core/pull/5554)) ## [10.0.0] @@ -1133,7 +1135,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release ([#5317](https://github.com/MetaMask/core/pull/5317)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.5...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@71.0.0...HEAD +[71.0.0]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.5...@metamask/bridge-status-controller@71.0.0 [70.0.5]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.4...@metamask/bridge-status-controller@70.0.5 [70.0.4]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.3...@metamask/bridge-status-controller@70.0.4 [70.0.3]: https://github.com/MetaMask/core/compare/@metamask/bridge-status-controller@70.0.2...@metamask/bridge-status-controller@70.0.3 diff --git a/packages/bridge-status-controller/package.json b/packages/bridge-status-controller/package.json index 0e4e2eebfdc..7613956f89e 100644 --- a/packages/bridge-status-controller/package.json +++ b/packages/bridge-status-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/bridge-status-controller", - "version": "70.0.5", + "version": "71.0.0", "description": "Manages bridge-related status fetching functionality for MetaMask", "keywords": [ "Ethereum", @@ -54,7 +54,7 @@ "dependencies": { "@metamask/accounts-controller": "^37.2.0", "@metamask/base-controller": "^9.1.0", - "@metamask/bridge-controller": "^70.1.1", + "@metamask/bridge-controller": "^70.2.0", "@metamask/controller-utils": "^11.20.0", "@metamask/gas-fee-controller": "^26.1.1", "@metamask/keyring-controller": "^25.2.0", diff --git a/packages/perps-controller/CHANGELOG.md b/packages/perps-controller/CHANGELOG.md index 1e17c0b1463..cd05410c1db 100644 --- a/packages/perps-controller/CHANGELOG.md +++ b/packages/perps-controller/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [4.0.0] + ### Added - Add `coalescePerpsRestRequest` utility for deduplicating concurrent REST requests with account-scoped cache keys ([#8560](https://github.com/MetaMask/core/pull/8560)) @@ -20,7 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed -- Drop the dead `spotState` parameter from `adaptAccountStateFromSDK`. Spot balances are layered on by `addSpotBalanceToAccountState`, which enforces the USDC-only policy via `SPOT_COLLATERAL_COINS`; removing the dormant branch keeps one source of truth and prevents a future caller from silently getting ALL-coins behavior ([#8560](https://github.com/MetaMask/core/pull/8560)) +- **BREAKING:** Drop the dead `spotState` parameter from `adaptAccountStateFromSDK`. Spot balances are layered on by `addSpotBalanceToAccountState`, which enforces the USDC-only policy via `SPOT_COLLATERAL_COINS`; removing the dormant branch keeps one source of truth and prevents a future caller from silently getting ALL-coins behavior ([#8560](https://github.com/MetaMask/core/pull/8560)) ### Fixed @@ -243,7 +245,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/controller-utils` from `^11.18.0` to `^11.19.0` ([#7995](https://github.com/MetaMask/core/pull/7995)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.2.0...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@4.0.0...HEAD +[4.0.0]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.2.0...@metamask/perps-controller@4.0.0 [3.2.0]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.1.1...@metamask/perps-controller@3.2.0 [3.1.1]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.1.0...@metamask/perps-controller@3.1.1 [3.1.0]: https://github.com/MetaMask/core/compare/@metamask/perps-controller@3.0.0...@metamask/perps-controller@3.1.0 diff --git a/packages/perps-controller/package.json b/packages/perps-controller/package.json index 501342b0a9b..f9b9471a2a8 100644 --- a/packages/perps-controller/package.json +++ b/packages/perps-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/perps-controller", - "version": "3.2.0", + "version": "4.0.0", "description": "Controller for perpetual trading functionality in MetaMask", "keywords": [ "Ethereum", diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index 92207d1419b..d599896821b 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [19.3.0] + ### Added - Add support for transaction config parameter accountOverride ([#8454](https://github.com/MetaMask/core/pull/8454)) @@ -15,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/assets-controller` from `^6.0.0` to `^6.1.0` ([#8559](https://github.com/MetaMask/core/pull/8559)) - Bump `@metamask/assets-controllers` from `^104.2.0` to `^104.3.0` ([#8559](https://github.com/MetaMask/core/pull/8559)) +- Bump `@metamask/bridge-controller` from `^70.1.1` to `^70.2.0` ([#8571](https://github.com/MetaMask/core/pull/8571)) +- Bump `@metamask/bridge-status-controller` from `^70.0.5` to `^71.0.0` ([#8571](https://github.com/MetaMask/core/pull/8571)) ## [19.2.2] @@ -698,7 +702,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release ([#6820](https://github.com/MetaMask/core/pull/6820)) -[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.2...HEAD +[Unreleased]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.3.0...HEAD +[19.3.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.2...@metamask/transaction-pay-controller@19.3.0 [19.2.2]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.1...@metamask/transaction-pay-controller@19.2.2 [19.2.1]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.2.0...@metamask/transaction-pay-controller@19.2.1 [19.2.0]: https://github.com/MetaMask/core/compare/@metamask/transaction-pay-controller@19.1.3...@metamask/transaction-pay-controller@19.2.0 diff --git a/packages/transaction-pay-controller/package.json b/packages/transaction-pay-controller/package.json index bf449a6c629..460fb50d5fe 100644 --- a/packages/transaction-pay-controller/package.json +++ b/packages/transaction-pay-controller/package.json @@ -1,6 +1,6 @@ { "name": "@metamask/transaction-pay-controller", - "version": "19.2.2", + "version": "19.3.0", "description": "Manages alternate payment strategies to provide required funds for transactions in MetaMask", "keywords": [ "Ethereum", @@ -60,8 +60,8 @@ "@metamask/assets-controller": "^6.1.0", "@metamask/assets-controllers": "^104.3.0", "@metamask/base-controller": "^9.1.0", - "@metamask/bridge-controller": "^70.1.1", - "@metamask/bridge-status-controller": "^70.0.5", + "@metamask/bridge-controller": "^70.2.0", + "@metamask/bridge-status-controller": "^71.0.0", "@metamask/controller-utils": "^11.20.0", "@metamask/gas-fee-controller": "^26.1.1", "@metamask/messenger": "^1.1.1", diff --git a/yarn.lock b/yarn.lock index 5c1220e5fed..d50feba1e21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3009,7 +3009,7 @@ __metadata: languageName: unknown linkType: soft -"@metamask/bridge-controller@npm:^70.1.1, @metamask/bridge-controller@workspace:packages/bridge-controller": +"@metamask/bridge-controller@npm:^70.2.0, @metamask/bridge-controller@workspace:packages/bridge-controller": version: 0.0.0-use.local resolution: "@metamask/bridge-controller@workspace:packages/bridge-controller" dependencies: @@ -3056,14 +3056,14 @@ __metadata: languageName: unknown linkType: soft -"@metamask/bridge-status-controller@npm:^70.0.5, @metamask/bridge-status-controller@workspace:packages/bridge-status-controller": +"@metamask/bridge-status-controller@npm:^71.0.0, @metamask/bridge-status-controller@workspace:packages/bridge-status-controller": version: 0.0.0-use.local resolution: "@metamask/bridge-status-controller@workspace:packages/bridge-status-controller" dependencies: "@metamask/accounts-controller": "npm:^37.2.0" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/bridge-controller": "npm:^70.1.1" + "@metamask/bridge-controller": "npm:^70.2.0" "@metamask/controller-utils": "npm:^11.20.0" "@metamask/gas-fee-controller": "npm:^26.1.1" "@metamask/keyring-controller": "npm:^25.2.0" @@ -5665,8 +5665,8 @@ __metadata: "@metamask/assets-controllers": "npm:^104.3.0" "@metamask/auto-changelog": "npm:^6.1.0" "@metamask/base-controller": "npm:^9.1.0" - "@metamask/bridge-controller": "npm:^70.1.1" - "@metamask/bridge-status-controller": "npm:^70.0.5" + "@metamask/bridge-controller": "npm:^70.2.0" + "@metamask/bridge-status-controller": "npm:^71.0.0" "@metamask/controller-utils": "npm:^11.20.0" "@metamask/gas-fee-controller": "npm:^26.1.1" "@metamask/messenger": "npm:^1.1.1"