@metamask-previews/eth-snap-keyring 19.0.0-fd40efd → 20.0.0-018fb62

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,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - Add `SnapKeyringV2` class, a per-snap keyring wrapper with O(1) account lookups by ID and address ([#501](https://github.com/MetaMask/accounts/pull/501))
13
+
14
+ ### Changed
15
+
16
+ - Refactor `SnapKeyring` to store accounts in per-snap `SnapKeyringV2` wrappers instead of a single flat map ([#501](https://github.com/MetaMask/accounts/pull/501))
17
+ - Bump `@metamask/keyring-sdk` from none to `^1.1.0`
18
+ - Bump `@metamask/messenger` from `^0.3.0` to `^1.1.1` ([#489](https://github.com/MetaMask/accounts/pull/489), [#500](https://github.com/MetaMask/accounts/pull/500))
19
+ - Bump `@metamask/snaps-controllers` from `^19.0.0` to `^19.0.1` ([#500](https://github.com/MetaMask/accounts/pull/500))
20
+ - Bump `@metamask/snaps-utils` from `^12.1.2` to `^12.1.3` ([#500](https://github.com/MetaMask/accounts/pull/500))
21
+ - Bump `@metamask/utils` from `^11.1.0` to `^11.10.0` ([#489](https://github.com/MetaMask/accounts/pull/489))
22
+
23
+ ## [20.0.0]
24
+
25
+ ### Changed
26
+
27
+ - **BREAKING:** Bump `@metamask/keyring-api` from `^21.6.0` to `^22.0.0` ([#482](https://github.com/MetaMask/accounts/pull/482))
28
+ - **BREAKING:** Bump `@metamask/snaps-controllers` from `^18.0.0` to `^19.0.0` ([#486](https://github.com/MetaMask/accounts/pull/486))
29
+ - `SnapKeyring` now requires `SnapController:getSnap` instead of `SnapController:get`.
30
+ - Bump `@metamask/keyring-internal-api` from `^10.0.0` to `^10.0.1` ([#482](https://github.com/MetaMask/accounts/pull/482))
31
+ - Bump `@metamask/keyring-internal-snap-client` from `^9.0.0` to `^9.0.1` ([#482](https://github.com/MetaMask/accounts/pull/482))
32
+ - Bump `@metamask/keyring-snap-sdk` from `^7.2.1` to `^8.0.0` ([#482](https://github.com/MetaMask/accounts/pull/482))
33
+ - Bump `@metamask/snaps-sdk` from `^10.4.0` to `^11.0.0` ([#486](https://github.com/MetaMask/accounts/pull/486))
34
+ - Bump `@metamask/snaps-utils` from `^12.1.0` to `^12.1.2` ([#486](https://github.com/MetaMask/accounts/pull/486))
35
+
10
36
  ## [19.0.0]
11
37
 
12
38
  ### Added
@@ -632,7 +658,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
632
658
 
633
659
  - Initial release.
634
660
 
635
- [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@19.0.0...HEAD
661
+ [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@20.0.0...HEAD
662
+ [20.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@19.0.0...@metamask/eth-snap-keyring@20.0.0
636
663
  [19.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@18.0.2...@metamask/eth-snap-keyring@19.0.0
637
664
  [18.0.2]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@18.0.1...@metamask/eth-snap-keyring@18.0.2
638
665
  [18.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@18.0.0...@metamask/eth-snap-keyring@18.0.1
@@ -10,7 +10,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
10
10
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
11
11
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
12
12
  };
13
- var _SnapKeyring_instances, _SnapKeyring_messenger, _SnapKeyring_snapClient, _SnapKeyring_accounts, _SnapKeyring_selectedAccounts, _SnapKeyring_requests, _SnapKeyring_options, _SnapKeyring_callbacks, _SnapKeyring_isAnyAccountTypeAllowed, _SnapKeyring_lock, _SnapKeyring_withLock, _SnapKeyring_isMinimumPlatformVersion, _SnapKeyring_assertAccountCanBeUsed, _SnapKeyring_getExistingAccount, _SnapKeyring_getInternalOptions, _SnapKeyring_handleAccountCreated, _SnapKeyring_handleAccountUpdated, _SnapKeyring_handleAccountDeleted, _SnapKeyring_handleGetSelectedAccounts, _SnapKeyring_handleRequestApproved, _SnapKeyring_handleRequestRejected, _SnapKeyring_rePublishAccountEvent, _SnapKeyring_handleAccountBalancesUpdated, _SnapKeyring_handleAccountAssetListUpdated, _SnapKeyring_handleAccountTransactionsUpdated, _SnapKeyring_getAccount, _SnapKeyring_submitRequest, _SnapKeyring_submitSnapRequest, _SnapKeyring_hasMethod, _SnapKeyring_createRequestPromise, _SnapKeyring_clearRequestPromise, _SnapKeyring_handleSyncResponse, _SnapKeyring_handleAsyncResponse, _SnapKeyring_sanitizeRedirectUrl, _SnapKeyring_validateRedirectUrl, _SnapKeyring_deleteAccount, _SnapKeyring_resolveAddress, _SnapKeyring_updateSelectedAccountsMap, _SnapKeyring_getSnap, _SnapKeyring_getSnapMetadata, _SnapKeyring_getSnapAllowedOrigins, _SnapKeyring_transformToInternalAccount;
13
+ var _SnapKeyring_instances, _SnapKeyring_messenger, _SnapKeyring_snapClient, _SnapKeyring_snapKeyrings, _SnapKeyring_accountIndex, _SnapKeyring_selectedAccounts, _SnapKeyring_requests, _SnapKeyring_options, _SnapKeyring_callbacks, _SnapKeyring_isAnyAccountTypeAllowed, _SnapKeyring_lock, _SnapKeyring_withLock, _SnapKeyring_getOrCreateKeyringV2, _SnapKeyring_isMinimumPlatformVersion, _SnapKeyring_assertAccountCanBeUsed, _SnapKeyring_getExistingAccount, _SnapKeyring_getInternalOptions, _SnapKeyring_handleAccountCreated, _SnapKeyring_handleAccountUpdated, _SnapKeyring_handleAccountDeleted, _SnapKeyring_handleGetSelectedAccounts, _SnapKeyring_handleRequestApproved, _SnapKeyring_handleRequestRejected, _SnapKeyring_rePublishAccountEvent, _SnapKeyring_handleAccountBalancesUpdated, _SnapKeyring_handleAccountAssetListUpdated, _SnapKeyring_handleAccountTransactionsUpdated, _SnapKeyring_getAccount, _SnapKeyring_submitRequest, _SnapKeyring_submitSnapRequest, _SnapKeyring_hasMethod, _SnapKeyring_createRequestPromise, _SnapKeyring_clearRequestPromise, _SnapKeyring_handleSyncResponse, _SnapKeyring_handleAsyncResponse, _SnapKeyring_sanitizeRedirectUrl, _SnapKeyring_validateRedirectUrl, _SnapKeyring_deleteAccount, _SnapKeyring_resolveAddress, _SnapKeyring_updateSelectedAccountsMap, _SnapKeyring_getSnap, _SnapKeyring_getSnapMetadata, _SnapKeyring_getSnapAllowedOrigins, _SnapKeyring_transformToInternalAccount;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.SnapKeyring = exports.SNAP_KEYRING_TYPE = void 0;
16
16
  const tx_1 = require("@ethereumjs/tx");
@@ -28,11 +28,11 @@ const account_1 = require("./account.cjs");
28
28
  const DeferredPromise_1 = require("./DeferredPromise.cjs");
29
29
  const events_1 = require("./events.cjs");
30
30
  const logger_1 = require("./logger.cjs");
31
- const migrations_1 = require("./migrations/index.cjs");
32
31
  const options_1 = require("./options.cjs");
33
32
  const platform_versions_1 = require("./platform-versions.cjs");
34
33
  const SnapIdMap_1 = require("./SnapIdMap.cjs");
35
34
  const SnapKeyringMessenger_1 = require("./SnapKeyringMessenger.cjs");
35
+ const SnapKeyringV2_1 = require("./SnapKeyringV2.cjs");
36
36
  const types_1 = require("./types.cjs");
37
37
  const util_1 = require("./util.cjs");
38
38
  exports.SNAP_KEYRING_TYPE = 'Snap Keyring';
@@ -77,10 +77,20 @@ class SnapKeyring {
77
77
  */
78
78
  _SnapKeyring_snapClient.set(this, void 0);
79
79
  /**
80
- * Mapping between account IDs and an object that contains the associated
81
- * account object and Snap ID.
80
+ * Per-snap keyring wrappers.
81
+ *
82
+ * Each entry owns the registry for a single Snap. Accounts are stored and
83
+ * looked up through these wrappers rather than through the old flat map.
84
+ */
85
+ _SnapKeyring_snapKeyrings.set(this, void 0);
86
+ /**
87
+ * Reverse index from account ID to the owning Snap ID.
88
+ *
89
+ * Populated and kept in sync via the `onRegister` / `onUnregister`
90
+ * callbacks injected into each `SnapKeyringV2`. Enables O(1) routing for
91
+ * any "which snap owns this account?" query.
82
92
  */
83
- _SnapKeyring_accounts.set(this, void 0);
93
+ _SnapKeyring_accountIndex.set(this, void 0);
84
94
  /**
85
95
  * Mapping between Snap IDs and the selected accounts.
86
96
  */
@@ -112,7 +122,8 @@ class SnapKeyring {
112
122
  __classPrivateFieldSet(this, _SnapKeyring_messenger, messenger, "f");
113
123
  __classPrivateFieldSet(this, _SnapKeyring_snapClient, new keyring_internal_snap_client_1.KeyringInternalSnapClient({ messenger }), "f");
114
124
  __classPrivateFieldSet(this, _SnapKeyring_requests, new SnapIdMap_1.SnapIdMap(), "f");
115
- __classPrivateFieldSet(this, _SnapKeyring_accounts, new SnapIdMap_1.SnapIdMap(), "f");
125
+ __classPrivateFieldSet(this, _SnapKeyring_snapKeyrings, new Map(), "f");
126
+ __classPrivateFieldSet(this, _SnapKeyring_accountIndex, new Map(), "f");
116
127
  __classPrivateFieldSet(this, _SnapKeyring_options, new SnapIdMap_1.SnapIdMap(), "f");
117
128
  __classPrivateFieldSet(this, _SnapKeyring_callbacks, callbacks, "f");
118
129
  __classPrivateFieldSet(this, _SnapKeyring_isAnyAccountTypeAllowed, isAnyAccountTypeAllowed, "f");
@@ -164,16 +175,27 @@ class SnapKeyring {
164
175
  /**
165
176
  * Serialize the keyring state.
166
177
  *
178
+ * Delegates to each per-snap wrapper and flattens back into the original
179
+ * external format so that `KeyringController` sees no change.
180
+ *
167
181
  * @returns Serialized keyring state.
168
182
  */
169
183
  async serialize() {
170
- return {
171
- accounts: __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").toObject(),
172
- };
184
+ const accounts = {};
185
+ for (const wrapper of __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").values()) {
186
+ for (const account of wrapper.accounts()) {
187
+ accounts[account.id] = { account, snapId: wrapper.snapId };
188
+ }
189
+ }
190
+ return { accounts };
173
191
  }
174
192
  /**
175
193
  * Deserialize the keyring state into this keyring.
176
194
  *
195
+ * Groups the flat persisted state by `snapId`, clears both indexes, then
196
+ * rebuilds each per-snap wrapper. The `onRegister` callbacks fired during
197
+ * `wrapper.deserialize()` automatically repopulate `#accountIndex`.
198
+ *
177
199
  * @param state - Serialized keyring state.
178
200
  */
179
201
  async deserialize(state) {
@@ -182,26 +204,23 @@ class SnapKeyring {
182
204
  if (state === undefined) {
183
205
  return;
184
206
  }
185
- // Running Snap keyring migrations. We might have some accounts that have a
186
- // different "version" than the one we expect.
187
- //
188
- // In this case, we "transform" then directly when deserializing to convert
189
- // them in the final account version.
190
- const accounts = {};
191
- for (const [snapId, entry] of Object.entries(state.accounts)) {
192
- // V1 accounts are missing the scopes.
193
- if ((0, migrations_1.isAccountV1)(entry.account)) {
194
- console.info(`SnapKeyring - Found a KeyringAccountV1, migrating to V2: ${entry.account.id}`);
195
- accounts[snapId] = {
196
- ...entry,
197
- account: (0, migrations_1.migrateAccountV1)(entry.account),
198
- };
199
- }
200
- else {
201
- accounts[snapId] = entry;
202
- }
207
+ // Group flat state by snapId. Migrations and migration logging are handled
208
+ // inside wrapper.deserialize().
209
+ const bySnap = new Map();
210
+ for (const entry of Object.values(state.accounts)) {
211
+ const snapAccounts = bySnap.get(entry.snapId) ?? {};
212
+ snapAccounts[entry.account.id] = entry.account;
213
+ bySnap.set(entry.snapId, snapAccounts);
214
+ }
215
+ // Clear both indexes before rebuilding — they must always be consistent.
216
+ __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").clear();
217
+ __classPrivateFieldGet(this, _SnapKeyring_accountIndex, "f").clear();
218
+ // Rebuild per-snap wrappers. Migrations run inside wrapper.deserialize().
219
+ for (const [snapId, accounts] of bySnap) {
220
+ const wrapper = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getOrCreateKeyringV2).call(this, snapId);
221
+ wrapper.deserialize({ snapId, accounts });
222
+ // onRegister callbacks fired above have repopulated #accountIndex.
203
223
  }
204
- __classPrivateFieldSet(this, _SnapKeyring_accounts, SnapIdMap_1.SnapIdMap.fromObject(accounts), "f");
205
224
  }
206
225
  /**
207
226
  * Get the addresses of the accounts in this keyring.
@@ -209,7 +228,13 @@ class SnapKeyring {
209
228
  * @returns The addresses of the accounts in this keyring.
210
229
  */
211
230
  async getAccounts() {
212
- return (0, util_1.unique)([...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()].map(({ account }) => normalizeAccountAddress(account)));
231
+ const addresses = [];
232
+ for (const wrapper of __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").values()) {
233
+ for (const account of wrapper.accounts()) {
234
+ addresses.push(normalizeAccountAddress(account));
235
+ }
236
+ }
237
+ return (0, util_1.unique)(addresses);
213
238
  }
214
239
  /**
215
240
  * Get the addresses of the accounts associated with a given Snap.
@@ -218,9 +243,7 @@ class SnapKeyring {
218
243
  * @returns The addresses of the accounts associated with the given Snap.
219
244
  */
220
245
  async getAccountsBySnapId(snapId) {
221
- return (0, util_1.unique)([...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()]
222
- .filter(({ snapId: accountSnapId }) => accountSnapId === snapId)
223
- .map(({ account }) => normalizeAccountAddress(account)));
246
+ return (0, util_1.unique)((__classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId)?.accounts() ?? []).map(normalizeAccountAddress));
224
247
  }
225
248
  /**
226
249
  * Create an account.
@@ -307,7 +330,7 @@ class SnapKeyring {
307
330
  account = existingAccount;
308
331
  }
309
332
  else {
310
- await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertAccountCanBeUsed).call(this, snapId, account);
333
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertAccountCanBeUsed).call(this, account);
311
334
  // Also check for transient accounts that are not yet part of the keyring
312
335
  // state.
313
336
  if (accountAddresses.has(address) || accountIds.has(account.id)) {
@@ -324,8 +347,9 @@ class SnapKeyring {
324
347
  }
325
348
  // We update the keyring state only if needed.
326
349
  if (newAccounts.length > 0) {
350
+ const wrapper = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getOrCreateKeyringV2).call(this, snapId);
327
351
  for (const account of newAccounts) {
328
- __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").set(account.id, { account, snapId });
352
+ wrapper.setAccount(account);
329
353
  }
330
354
  // NOTE: We assume this will never fail, thus, we don't need to rollback the
331
355
  // keyring state if anything goes wrong here.
@@ -352,7 +376,8 @@ class SnapKeyring {
352
376
  * @returns `true` if the Snap ID is known, `false` otherwise.
353
377
  */
354
378
  hasSnapId(snapId) {
355
- return __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").hasSnapId(snapId);
379
+ const wrapper = __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId);
380
+ return wrapper !== undefined && wrapper.accounts().length > 0;
356
381
  }
357
382
  /**
358
383
  * Resolve the Snap account's address associated from a signing request.
@@ -607,11 +632,13 @@ class SnapKeyring {
607
632
  * @returns An internal account object for the given address.
608
633
  */
609
634
  getAccountByAddress(address) {
610
- const accounts = [...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()];
611
- const account = accounts.find(({ account: { address: accountAddress } }) => (0, util_1.equalsIgnoreCase)(accountAddress, address));
612
- return account
613
- ? __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_transformToInternalAccount).call(this, account.account, account.snapId)
614
- : undefined;
635
+ for (const wrapper of __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").values()) {
636
+ const account = wrapper.lookupByAddress(address);
637
+ if (account) {
638
+ return __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_transformToInternalAccount).call(this, account, wrapper.snapId);
639
+ }
640
+ }
641
+ return undefined;
615
642
  }
616
643
  /**
617
644
  * List all Snap keyring accounts.
@@ -621,11 +648,17 @@ class SnapKeyring {
621
648
  * @returns An array containing all Snap keyring accounts.
622
649
  */
623
650
  listAccounts() {
624
- return [...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()].map(({ account, snapId }) => __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_transformToInternalAccount).call(this, account, snapId));
651
+ const accounts = [];
652
+ for (const wrapper of __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").values()) {
653
+ for (const account of wrapper.accounts()) {
654
+ accounts.push(__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_transformToInternalAccount).call(this, account, wrapper.snapId));
655
+ }
656
+ }
657
+ return accounts;
625
658
  }
626
659
  }
627
660
  exports.SnapKeyring = SnapKeyring;
628
- _SnapKeyring_messenger = new WeakMap(), _SnapKeyring_snapClient = new WeakMap(), _SnapKeyring_accounts = new WeakMap(), _SnapKeyring_selectedAccounts = new WeakMap(), _SnapKeyring_requests = new WeakMap(), _SnapKeyring_options = new WeakMap(), _SnapKeyring_callbacks = new WeakMap(), _SnapKeyring_isAnyAccountTypeAllowed = new WeakMap(), _SnapKeyring_lock = new WeakMap(), _SnapKeyring_instances = new WeakSet(), _SnapKeyring_withLock =
661
+ _SnapKeyring_messenger = new WeakMap(), _SnapKeyring_snapClient = new WeakMap(), _SnapKeyring_snapKeyrings = new WeakMap(), _SnapKeyring_accountIndex = new WeakMap(), _SnapKeyring_selectedAccounts = new WeakMap(), _SnapKeyring_requests = new WeakMap(), _SnapKeyring_options = new WeakMap(), _SnapKeyring_callbacks = new WeakMap(), _SnapKeyring_isAnyAccountTypeAllowed = new WeakMap(), _SnapKeyring_lock = new WeakMap(), _SnapKeyring_instances = new WeakSet(), _SnapKeyring_withLock =
629
662
  /**
630
663
  * Execute an operation behind a lock.
631
664
  *
@@ -634,6 +667,21 @@ _SnapKeyring_messenger = new WeakMap(), _SnapKeyring_snapClient = new WeakMap(),
634
667
  */
635
668
  async function _SnapKeyring_withLock(callback) {
636
669
  return __classPrivateFieldGet(this, _SnapKeyring_lock, "f").runExclusive(callback);
670
+ }, _SnapKeyring_getOrCreateKeyringV2 = function _SnapKeyring_getOrCreateKeyringV2(snapId) {
671
+ let keyring = __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId);
672
+ if (!keyring) {
673
+ keyring = new SnapKeyringV2_1.SnapKeyringV2({
674
+ snapId,
675
+ onRegister: (id) => {
676
+ __classPrivateFieldGet(this, _SnapKeyring_accountIndex, "f").set(id, snapId);
677
+ },
678
+ onUnregister: (id) => {
679
+ __classPrivateFieldGet(this, _SnapKeyring_accountIndex, "f").delete(id);
680
+ },
681
+ });
682
+ __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").set(snapId, keyring);
683
+ }
684
+ return keyring;
637
685
  }, _SnapKeyring_isMinimumPlatformVersion = function _SnapKeyring_isMinimumPlatformVersion(snapId, platformVersion) {
638
686
  return __classPrivateFieldGet(this, _SnapKeyring_messenger, "f").call('SnapController:isMinimumPlatformVersion', snapId, platformVersion);
639
687
  }, _SnapKeyring_assertAccountCanBeUsed =
@@ -641,11 +689,10 @@ async function _SnapKeyring_withLock(callback) {
641
689
  * Asserts that an account can be used within the Snap keyring. (e.g. generic accounts, unique
642
690
  * addresses, etc...).
643
691
  *
644
- * @param snapId - The account's Snap ID.
645
692
  * @param account - The account to check.
646
693
  * @throws If the account cannot be used.
647
694
  */
648
- async function _SnapKeyring_assertAccountCanBeUsed(snapId, account) {
695
+ async function _SnapKeyring_assertAccountCanBeUsed(account) {
649
696
  const address = normalizeAccountAddress(account);
650
697
  // The `AnyAccountType.Account` generic account type is allowed only during
651
698
  // development, so we check whether it's allowed before continuing.
@@ -655,7 +702,8 @@ async function _SnapKeyring_assertAccountCanBeUsed(snapId, account) {
655
702
  }
656
703
  // A Snap could try to create an account with a different address but with
657
704
  // an existing ID, so the above test only is not enough.
658
- if (__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").has(snapId, account.id)) {
705
+ // Account IDs are globally unique across all snaps.
706
+ if (__classPrivateFieldGet(this, _SnapKeyring_accountIndex, "f").has(account.id)) {
659
707
  throw new Error(`Account '${account.id}' already exists`);
660
708
  }
661
709
  // The UI still uses the account address to identify accounts, so we need
@@ -666,14 +714,15 @@ async function _SnapKeyring_assertAccountCanBeUsed(snapId, account) {
666
714
  }
667
715
  }, _SnapKeyring_getExistingAccount = function _SnapKeyring_getExistingAccount(snapId, account) {
668
716
  const address = normalizeAccountAddress(account);
669
- // Acount creation is idempotent, so we need to check whether the account already exists
670
- // and that the right Snap is trying to "create" it again.
717
+ // Account creation is idempotent, so we need to check whether the account
718
+ // already exists and that the right Snap is trying to "create" it again.
671
719
  // NOTE: We are not checking account object equality here. If a Snap
672
- // re-send this event with different account data, we will ignore it.
673
- const accountEntry = __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").get(snapId, account.id);
674
- if (accountEntry !== undefined &&
675
- normalizeAccountAddress(accountEntry.account) === address) {
676
- return accountEntry.account;
720
+ // re-sends this event with different account data, we will ignore it.
721
+ const wrapper = __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId);
722
+ const existing = wrapper?.lookupAccount(account.id);
723
+ if (existing !== undefined &&
724
+ normalizeAccountAddress(existing) === address) {
725
+ return existing;
677
726
  }
678
727
  return undefined; // Not a known account.
679
728
  }, _SnapKeyring_getInternalOptions = function _SnapKeyring_getInternalOptions(snapId, correlationId) {
@@ -716,7 +765,7 @@ async function _SnapKeyring_handleAccountCreated(snapId, message) {
716
765
  return null;
717
766
  }
718
767
  // Make sure this new account is valid.
719
- await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertAccountCanBeUsed).call(this, snapId, account);
768
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertAccountCanBeUsed).call(this, account);
720
769
  // A deferred promise that will be resolved once the Snap keyring has saved
721
770
  // its internal state.
722
771
  // This part of the flow is run asynchronously, so we have no other way of
@@ -737,7 +786,7 @@ async function _SnapKeyring_handleAccountCreated(snapId, message) {
737
786
  //
738
787
  // e.g The account creation dialog crashed on MetaMask, this callback
739
788
  // will never be called, but the Snap still has the account.
740
- __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").set(account.id, { account, snapId });
789
+ __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getOrCreateKeyringV2).call(this, snapId).setAccount(account);
741
790
  // This is the "true async part". We do not `await` for this call, mainly
742
791
  // because this callback will persist the account on the client side
743
792
  // (through the `AccountsController`).
@@ -786,7 +835,8 @@ async function _SnapKeyring_handleAccountCreated(snapId, message) {
786
835
  async function _SnapKeyring_handleAccountUpdated(snapId, message) {
787
836
  (0, superstruct_1.assert)(message, events_1.AccountUpdatedEventStruct);
788
837
  const { account: newAccountFromEvent } = message.params;
789
- const { account: oldAccount } = __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").get(snapId, newAccountFromEvent.id) ??
838
+ const wrapper = __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId);
839
+ const oldAccount = wrapper?.lookupAccount(newAccountFromEvent.id) ??
790
840
  (0, util_1.throwError)(`Account '${newAccountFromEvent.id}' not found`);
791
841
  // Potentially migrate the account.
792
842
  const newAccount = (0, account_1.transformAccount)(newAccountFromEvent);
@@ -806,7 +856,7 @@ async function _SnapKeyring_handleAccountUpdated(snapId, message) {
806
856
  if (!(0, util_1.equalsIgnoreCase)(oldAccount.address, newAccount.address)) {
807
857
  throw new Error(`Cannot change address of account '${newAccount.id}'`);
808
858
  }
809
- __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").set(newAccount.id, { account: newAccount, snapId });
859
+ __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getOrCreateKeyringV2).call(this, snapId).setAccount(newAccount);
810
860
  await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").saveState();
811
861
  return null;
812
862
  }, _SnapKeyring_handleAccountDeleted =
@@ -820,18 +870,15 @@ async function _SnapKeyring_handleAccountUpdated(snapId, message) {
820
870
  async function _SnapKeyring_handleAccountDeleted(snapId, message) {
821
871
  (0, superstruct_1.assert)(message, events_1.AccountDeletedEventStruct);
822
872
  const { id } = message.params;
823
- const entry = __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").get(snapId, id);
873
+ const account = __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId)?.lookupAccount(id);
824
874
  // We can ignore the case where the account was already removed from the
825
875
  // keyring, making the deletion idempotent.
826
876
  //
827
877
  // This happens when the keyring calls the Snap to delete an account, and
828
878
  // the Snap calls the keyring back with an `AccountDeleted` event.
829
- if (entry === undefined) {
879
+ if (account === undefined) {
830
880
  return null;
831
881
  }
832
- // At this point we know that the account exists, so we can safely
833
- // destructure it.
834
- const { account } = entry;
835
882
  await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").removeAccount(normalizeAccountAddress(account), snapId, async (accepted) => {
836
883
  if (accepted) {
837
884
  await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").saveState();
@@ -895,7 +942,7 @@ async function _SnapKeyring_rePublishAccountEvent(snapId, event, filteredEventCa
895
942
  // prevent other Snaps from updating accounts they do not own.
896
943
  const filter = (accountMapping) => {
897
944
  return Object.entries(accountMapping).reduce((filtered, [accountId, entry]) => {
898
- if (__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").has(snapId, accountId)) {
945
+ if (__classPrivateFieldGet(this, _SnapKeyring_accountIndex, "f").get(accountId) === snapId) {
899
946
  // If the Snap owns this account, we can use it.
900
947
  filtered[accountId] = entry;
901
948
  }
@@ -954,11 +1001,14 @@ async function _SnapKeyring_handleAccountTransactionsUpdated(snapId, message) {
954
1001
  return [event];
955
1002
  });
956
1003
  }, _SnapKeyring_getAccount = function _SnapKeyring_getAccount(id) {
957
- const found = [...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()].find((entry) => entry.account.id === id);
958
- if (!found) {
1004
+ const snapId = __classPrivateFieldGet(this, _SnapKeyring_accountIndex, "f").get(id);
1005
+ const account = snapId
1006
+ ? __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId)?.lookupAccount(id)
1007
+ : undefined;
1008
+ if (!snapId || !account) {
959
1009
  throw new Error(`Unable to get account: unknown account ID: '${id}'`);
960
1010
  }
961
- return found;
1011
+ return { account, snapId };
962
1012
  }, _SnapKeyring_submitRequest =
963
1013
  /**
964
1014
  * Submit a request to a Snap from an account address.
@@ -1113,8 +1163,8 @@ async function _SnapKeyring_handleAsyncResponse(redirect, snapId) {
1113
1163
  */
1114
1164
  async function _SnapKeyring_deleteAccount(snapId, account) {
1115
1165
  // Always remove the account from the maps, even if the Snap is going to
1116
- // fail to delete it.
1117
- __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").delete(snapId, account.id);
1166
+ // fail to delete it. removeAccount fires onUnregister to clean #accountIndex.
1167
+ __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").get(snapId)?.removeAccount(account.id);
1118
1168
  try {
1119
1169
  await __classPrivateFieldGet(this, _SnapKeyring_snapClient, "f").withSnapId(snapId).deleteAccount(account.id);
1120
1170
  }
@@ -1125,12 +1175,18 @@ async function _SnapKeyring_deleteAccount(snapId, account) {
1125
1175
  console.error(`Account '${account.address}' may not have been removed from snap '${snapId}':`, error);
1126
1176
  }
1127
1177
  }, _SnapKeyring_resolveAddress = function _SnapKeyring_resolveAddress(address) {
1128
- return ([...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()].find(({ account }) => (0, util_1.equalsIgnoreCase)(account.address, address)) ?? (0, util_1.throwError)(`Account '${address}' not found`));
1178
+ for (const wrapper of __classPrivateFieldGet(this, _SnapKeyring_snapKeyrings, "f").values()) {
1179
+ const account = wrapper.lookupByAddress(address);
1180
+ if (account) {
1181
+ return { account, snapId: wrapper.snapId };
1182
+ }
1183
+ }
1184
+ return (0, util_1.throwError)(`Account '${address}' not found`);
1129
1185
  }, _SnapKeyring_updateSelectedAccountsMap = function _SnapKeyring_updateSelectedAccountsMap(accounts) {
1130
1186
  const selectedAccounts = __classPrivateFieldGet(this, _SnapKeyring_selectedAccounts, "f");
1131
1187
  selectedAccounts.clear();
1132
1188
  for (const account of accounts) {
1133
- const snapId = __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").getSnapId(account);
1189
+ const snapId = __classPrivateFieldGet(this, _SnapKeyring_accountIndex, "f").get(account);
1134
1190
  if (!snapId) {
1135
1191
  continue;
1136
1192
  }
@@ -1139,7 +1195,7 @@ async function _SnapKeyring_deleteAccount(snapId, account) {
1139
1195
  selectedAccounts.set(snapId, snapAccounts);
1140
1196
  }
1141
1197
  }, _SnapKeyring_getSnap = function _SnapKeyring_getSnap(snapId) {
1142
- return __classPrivateFieldGet(this, _SnapKeyring_messenger, "f").call('SnapController:get', snapId);
1198
+ return __classPrivateFieldGet(this, _SnapKeyring_messenger, "f").call('SnapController:getSnap', snapId);
1143
1199
  }, _SnapKeyring_getSnapMetadata = function _SnapKeyring_getSnapMetadata(snapId) {
1144
1200
  const snap = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getSnap).call(this, snapId);
1145
1201
  return snap