@metamask-previews/keyring-controller 26.0.0-preview-1e1b476aa → 26.0.0-preview-e15374a5d

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Fixed
11
+
12
+ - Automatically remove and destroy non-primary keyrings whose last account is removed during a `withKeyring` or `withKeyringV2` callback ([#8951](https://github.com/MetaMask/core/pull/8951))
13
+ - Previously, draining a keyring of all its accounts via these APIs left an empty keyring entry in `state.keyrings` and persisted it in the vault.
14
+ - Pre-existing empty keyrings (e.g. those created intentionally via `addNewKeyring` without subsequent account creation) are still preserved, matching the behavior of `removeAccount`.
15
+ - The primary keyring is never auto-removed, even if drained; this preserves the existing `removeAccount` invariant against losing the primary keyring.
16
+
10
17
  ## [26.0.0]
11
18
 
12
19
  ### Changed
@@ -36,7 +36,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
36
36
  var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  return (mod && mod.__esModule) ? mod : { "default": mod };
38
38
  };
39
- var _KeyringController_instances, _KeyringController_controllerOperationMutex, _KeyringController_vaultOperationMutex, _KeyringController_keyringBuilders, _KeyringController_keyringV2Builders, _KeyringController_encryptor, _KeyringController_keyrings, _KeyringController_unsupportedKeyrings, _KeyringController_encryptionKey, _KeyringController_getKeyringForAccount, _KeyringController_getKeyringEntryForAccount, _KeyringController_findKeyringIndexForAccount, _KeyringController_getKeyringEntriesByType, _KeyringController_assertNoUnsafeDirectKeyringAccess, _KeyringController_registerMessageHandlers, _KeyringController_selectKeyringEntry, _KeyringController_getKeyringById, _KeyringController_getKeyringEntryById, _KeyringController_getKeyringByIdOrDefault, _KeyringController_getKeyringMetadata, _KeyringController_getKeyringBuilderForType, _KeyringController_getKeyringV2BuilderForType, _KeyringController_createNewVaultWithKeyring, _KeyringController_deriveAndSetEncryptionKey, _KeyringController_setEncryptionKey, _KeyringController_verifySeedPhrase, _KeyringController_getUpdatedKeyrings, _KeyringController_getSerializedKeyrings, _KeyringController_getSessionState, _KeyringController_restoreSerializedKeyrings, _KeyringController_unlockKeyrings, _KeyringController_updateVault, _KeyringController_isNewEncryptionAvailable, _KeyringController_getAccountsFromKeyrings, _KeyringController_createKeyringWithFirstAccount, _KeyringController_newKeyring, _KeyringController_createKeyring, _KeyringController_clearKeyrings, _KeyringController_restoreKeyring, _KeyringController_destroyKeyring, _KeyringController_assertNoDuplicateAccounts, _KeyringController_setUnlocked, _KeyringController_assertIsUnlocked, _KeyringController_persistOrRollback, _KeyringController_withRollback, _KeyringController_assertControllerMutexIsLocked, _KeyringController_assertNotRemovingPrimaryKeyring, _KeyringController_withControllerLock, _KeyringController_withVaultLock;
39
+ var _KeyringController_instances, _KeyringController_controllerOperationMutex, _KeyringController_vaultOperationMutex, _KeyringController_keyringBuilders, _KeyringController_keyringV2Builders, _KeyringController_encryptor, _KeyringController_keyrings, _KeyringController_unsupportedKeyrings, _KeyringController_encryptionKey, _KeyringController_getKeyringForAccount, _KeyringController_getKeyringEntryForAccount, _KeyringController_findKeyringIndexForAccount, _KeyringController_getKeyringEntriesByType, _KeyringController_assertNoUnsafeDirectKeyringAccess, _KeyringController_registerMessageHandlers, _KeyringController_selectKeyringEntry, _KeyringController_getKeyringById, _KeyringController_getKeyringEntryById, _KeyringController_getKeyringByIdOrDefault, _KeyringController_getKeyringMetadata, _KeyringController_getKeyringBuilderForType, _KeyringController_getKeyringV2BuilderForType, _KeyringController_createNewVaultWithKeyring, _KeyringController_deriveAndSetEncryptionKey, _KeyringController_setEncryptionKey, _KeyringController_verifySeedPhrase, _KeyringController_getUpdatedKeyrings, _KeyringController_getSerializedKeyrings, _KeyringController_getSessionState, _KeyringController_restoreSerializedKeyrings, _KeyringController_unlockKeyrings, _KeyringController_updateVault, _KeyringController_isNewEncryptionAvailable, _KeyringController_getAccountsFromKeyrings, _KeyringController_createKeyringWithFirstAccount, _KeyringController_newKeyring, _KeyringController_createKeyring, _KeyringController_cleanUpEmptiedKeyringsAfter, _KeyringController_clearKeyrings, _KeyringController_restoreKeyring, _KeyringController_destroyKeyring, _KeyringController_assertNoDuplicateAccounts, _KeyringController_setUnlocked, _KeyringController_assertIsUnlocked, _KeyringController_persistOrRollback, _KeyringController_withRollback, _KeyringController_assertControllerMutexIsLocked, _KeyringController_isPrimaryKeyring, _KeyringController_assertNotRemovingPrimaryKeyring, _KeyringController_withControllerLock, _KeyringController_withVaultLock;
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.KeyringController = exports.getDefaultKeyringState = exports.keyringBuilderFactory = exports.SignTypedDataVersion = exports.AccountImportStrategy = exports.isCustodyKeyring = exports.KeyringTypes = void 0;
42
42
  const util_1 = require("@ethereumjs/util");
@@ -1026,7 +1026,7 @@ class KeyringController extends base_controller_1.BaseController {
1026
1026
  }
1027
1027
  const { metadata } = entry;
1028
1028
  const keyring = entry.keyring;
1029
- return __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_assertNoUnsafeDirectKeyringAccess).call(this, await operation({ keyring, metadata }), keyring);
1029
+ return __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_assertNoUnsafeDirectKeyringAccess).call(this, await __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_cleanUpEmptiedKeyringsAfter).call(this, async () => operation({ keyring, metadata })), keyring);
1030
1030
  });
1031
1031
  }
1032
1032
  /**
@@ -1111,10 +1111,10 @@ class KeyringController extends base_controller_1.BaseController {
1111
1111
  }
1112
1112
  const { metadata } = entry;
1113
1113
  const keyring = entry.keyringV2;
1114
- return __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_assertNoUnsafeDirectKeyringAccess).call(this, await operation({
1114
+ return __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_assertNoUnsafeDirectKeyringAccess).call(this, await __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_cleanUpEmptiedKeyringsAfter).call(this, async () => operation({
1115
1115
  keyring,
1116
1116
  metadata,
1117
- }), keyring);
1117
+ })), keyring);
1118
1118
  });
1119
1119
  }
1120
1120
  /**
@@ -1729,6 +1729,52 @@ async function _KeyringController_createKeyring(type, data, metadata) {
1729
1729
  keyringV2 = keyringBuilderV2(keyring, keyringMetadata);
1730
1730
  }
1731
1731
  return { keyring, keyringV2, metadata: keyringMetadata };
1732
+ }, _KeyringController_cleanUpEmptiedKeyringsAfter =
1733
+ /**
1734
+ * Run the given operation and afterwards clean up any keyring whose
1735
+ * account list transitioned from non-empty to empty during the operation.
1736
+ *
1737
+ * This mirrors the cleanup behavior of {@link KeyringController.removeAccount}
1738
+ * for code paths where the consumer mutates a keyring directly via
1739
+ * {@link KeyringController.withKeyring} or
1740
+ * {@link KeyringController.withKeyringV2}: if the consumer drains the last
1741
+ * account from a keyring, the now-empty keyring is removed from
1742
+ * {@link KeyringController.#keyrings} and destroyed before persistence runs.
1743
+ *
1744
+ * Pre-existing empty keyrings (e.g. those created intentionally via
1745
+ * {@link KeyringController.addNewKeyring} without subsequent account
1746
+ * creation) are left alone, as are keyrings created within the operation
1747
+ * itself (they are not part of the pre-operation snapshot). The primary
1748
+ * keyring (see {@link KeyringController.#isPrimaryKeyring}) is also preserved
1749
+ * unconditionally to keep `removeAccount`'s primary-keyring invariant intact.
1750
+ *
1751
+ * @param operation - The operation to execute.
1752
+ * @returns The result of the operation.
1753
+ * @template Result - The type of the value resolved by the operation.
1754
+ */
1755
+ async function _KeyringController_cleanUpEmptiedKeyringsAfter(operation) {
1756
+ // Only the primary keyring exists, which is never auto-removed, so there
1757
+ // is nothing to clean up regardless of what the operation does.
1758
+ if (__classPrivateFieldGet(this, _KeyringController_keyrings, "f").length <= 1) {
1759
+ return operation();
1760
+ }
1761
+ const wasNonEmpty = new WeakSet();
1762
+ await Promise.all(__classPrivateFieldGet(this, _KeyringController_keyrings, "f").map(async ({ keyring }) => {
1763
+ if ((await keyring.getAccounts()).length > 0) {
1764
+ wasNonEmpty.add(keyring);
1765
+ }
1766
+ }));
1767
+ const result = await operation();
1768
+ const isNowEmpty = await Promise.all(__classPrivateFieldGet(this, _KeyringController_keyrings, "f").map(async ({ keyring }) => (await keyring.getAccounts()).length === 0));
1769
+ const emptied = __classPrivateFieldGet(this, _KeyringController_keyrings, "f").filter((entry, index) => !__classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_isPrimaryKeyring).call(this, entry, __classPrivateFieldGet(this, _KeyringController_keyrings, "f")) &&
1770
+ wasNonEmpty.has(entry.keyring) &&
1771
+ isNowEmpty[index]);
1772
+ if (emptied.length > 0) {
1773
+ const removed = new Set(emptied);
1774
+ __classPrivateFieldSet(this, _KeyringController_keyrings, __classPrivateFieldGet(this, _KeyringController_keyrings, "f").filter((entry) => !removed.has(entry)), "f");
1775
+ await Promise.all(emptied.map(({ keyring, keyringV2 }) => __classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_destroyKeyring).call(this, keyring, keyringV2)));
1776
+ }
1777
+ return result;
1732
1778
  }, _KeyringController_clearKeyrings =
1733
1779
  /**
1734
1780
  * Remove all managed keyrings, destroying all their
@@ -1865,9 +1911,11 @@ async function _KeyringController_withRollback(callback) {
1865
1911
  if (!__classPrivateFieldGet(this, _KeyringController_controllerOperationMutex, "f").isLocked()) {
1866
1912
  throw new errors_1.KeyringControllerError(constants_1.KeyringControllerErrorMessage.ControllerLockRequired);
1867
1913
  }
1914
+ }, _KeyringController_isPrimaryKeyring = function _KeyringController_isPrimaryKeyring(entry, keyrings) {
1915
+ return (keyrings[0] === entry &&
1916
+ entry.keyring.type === KeyringTypes.hd);
1868
1917
  }, _KeyringController_assertNotRemovingPrimaryKeyring = function _KeyringController_assertNotRemovingPrimaryKeyring(entry, keyrings) {
1869
- if (keyrings[0] === entry &&
1870
- entry.keyring.type === KeyringTypes.hd) {
1918
+ if (__classPrivateFieldGet(this, _KeyringController_instances, "m", _KeyringController_isPrimaryKeyring).call(this, entry, keyrings)) {
1871
1919
  throw new errors_1.KeyringControllerError(constants_1.KeyringControllerErrorMessage.CannotRemovePrimaryKeyring);
1872
1920
  }
1873
1921
  }, _KeyringController_withControllerLock =