@metamask-previews/multichain-account-service 0.7.0-preview-ea6406b3 → 0.7.0-preview-1f2cfed

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/MultichainAccountService.cjs +2 -11
  3. package/dist/MultichainAccountService.cjs.map +1 -1
  4. package/dist/MultichainAccountService.d.cts +0 -6
  5. package/dist/MultichainAccountService.d.cts.map +1 -1
  6. package/dist/MultichainAccountService.d.mts +0 -6
  7. package/dist/MultichainAccountService.d.mts.map +1 -1
  8. package/dist/MultichainAccountService.mjs +2 -11
  9. package/dist/MultichainAccountService.mjs.map +1 -1
  10. package/dist/MultichainAccountWallet.cjs +183 -156
  11. package/dist/MultichainAccountWallet.cjs.map +1 -1
  12. package/dist/MultichainAccountWallet.d.cts +17 -15
  13. package/dist/MultichainAccountWallet.d.cts.map +1 -1
  14. package/dist/MultichainAccountWallet.d.mts +17 -15
  15. package/dist/MultichainAccountWallet.d.mts.map +1 -1
  16. package/dist/MultichainAccountWallet.mjs +183 -156
  17. package/dist/MultichainAccountWallet.mjs.map +1 -1
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +1 -1
  20. package/dist/index.d.cts.map +1 -1
  21. package/dist/index.d.mts +1 -1
  22. package/dist/index.d.mts.map +1 -1
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/providers/AccountProviderWrapper.cjs +2 -2
  25. package/dist/providers/AccountProviderWrapper.cjs.map +1 -1
  26. package/dist/providers/AccountProviderWrapper.d.cts +1 -1
  27. package/dist/providers/AccountProviderWrapper.d.cts.map +1 -1
  28. package/dist/providers/AccountProviderWrapper.d.mts +1 -1
  29. package/dist/providers/AccountProviderWrapper.d.mts.map +1 -1
  30. package/dist/providers/AccountProviderWrapper.mjs +2 -2
  31. package/dist/providers/AccountProviderWrapper.mjs.map +1 -1
  32. package/dist/providers/BaseBip44AccountProvider.cjs.map +1 -1
  33. package/dist/providers/BaseBip44AccountProvider.d.cts +1 -1
  34. package/dist/providers/BaseBip44AccountProvider.d.cts.map +1 -1
  35. package/dist/providers/BaseBip44AccountProvider.d.mts +1 -1
  36. package/dist/providers/BaseBip44AccountProvider.d.mts.map +1 -1
  37. package/dist/providers/BaseBip44AccountProvider.mjs.map +1 -1
  38. package/dist/providers/EvmAccountProvider.cjs +1 -1
  39. package/dist/providers/EvmAccountProvider.cjs.map +1 -1
  40. package/dist/providers/EvmAccountProvider.d.cts +1 -1
  41. package/dist/providers/EvmAccountProvider.d.cts.map +1 -1
  42. package/dist/providers/EvmAccountProvider.d.mts +1 -1
  43. package/dist/providers/EvmAccountProvider.d.mts.map +1 -1
  44. package/dist/providers/EvmAccountProvider.mjs +1 -1
  45. package/dist/providers/EvmAccountProvider.mjs.map +1 -1
  46. package/dist/providers/SnapAccountProvider.cjs.map +1 -1
  47. package/dist/providers/SnapAccountProvider.d.cts +1 -1
  48. package/dist/providers/SnapAccountProvider.d.cts.map +1 -1
  49. package/dist/providers/SnapAccountProvider.d.mts +1 -1
  50. package/dist/providers/SnapAccountProvider.d.mts.map +1 -1
  51. package/dist/providers/SnapAccountProvider.mjs.map +1 -1
  52. package/dist/providers/SolAccountProvider.cjs +1 -1
  53. package/dist/providers/SolAccountProvider.cjs.map +1 -1
  54. package/dist/providers/SolAccountProvider.d.cts +1 -1
  55. package/dist/providers/SolAccountProvider.d.cts.map +1 -1
  56. package/dist/providers/SolAccountProvider.d.mts +1 -1
  57. package/dist/providers/SolAccountProvider.d.mts.map +1 -1
  58. package/dist/providers/SolAccountProvider.mjs +1 -1
  59. package/dist/providers/SolAccountProvider.mjs.map +1 -1
  60. package/dist/tests/accounts.cjs +2 -2
  61. package/dist/tests/accounts.cjs.map +1 -1
  62. package/dist/tests/accounts.mjs +2 -2
  63. package/dist/tests/accounts.mjs.map +1 -1
  64. package/dist/tests/providers.cjs +5 -4
  65. package/dist/tests/providers.cjs.map +1 -1
  66. package/dist/tests/providers.d.cts +3 -2
  67. package/dist/tests/providers.d.cts.map +1 -1
  68. package/dist/tests/providers.d.mts +3 -2
  69. package/dist/tests/providers.d.mts.map +1 -1
  70. package/dist/tests/providers.mjs +3 -2
  71. package/dist/tests/providers.mjs.map +1 -1
  72. package/dist/types.cjs.map +1 -1
  73. package/dist/types.d.cts +7 -7
  74. package/dist/types.d.cts.map +1 -1
  75. package/dist/types.d.mts +7 -7
  76. package/dist/types.d.mts.map +1 -1
  77. package/dist/types.mjs.map +1 -1
  78. package/package.json +9 -8
@@ -10,13 +10,14 @@ 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 _MultichainAccountWallet_id, _MultichainAccountWallet_providers, _MultichainAccountWallet_entropySource, _MultichainAccountWallet_accountGroups, _MultichainAccountWallet_messenger, _MultichainAccountWallet_initialized, _MultichainAccountWallet_isAlignmentInProgress;
13
+ var _MultichainAccountWallet_instances, _MultichainAccountWallet_lock, _MultichainAccountWallet_id, _MultichainAccountWallet_providers, _MultichainAccountWallet_entropySource, _MultichainAccountWallet_accountGroups, _MultichainAccountWallet_messenger, _MultichainAccountWallet_initialized, _MultichainAccountWallet_status, _MultichainAccountWallet_withLock, _MultichainAccountWallet_alignAccounts;
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.MultichainAccountWallet = void 0;
16
16
  const account_api_1 = require("@metamask/account-api");
17
17
  const account_api_2 = require("@metamask/account-api");
18
18
  const account_api_3 = require("@metamask/account-api");
19
19
  const utils_1 = require("@metamask/utils");
20
+ const async_mutex_1 = require("async-mutex");
20
21
  const MultichainAccountGroup_1 = require("./MultichainAccountGroup.cjs");
21
22
  const log = (0, utils_1.createProjectLogger)('multichain-account-service');
22
23
  /**
@@ -25,6 +26,8 @@ const log = (0, utils_1.createProjectLogger)('multichain-account-service');
25
26
  */
26
27
  class MultichainAccountWallet {
27
28
  constructor({ providers, entropySource, messenger, }) {
29
+ _MultichainAccountWallet_instances.add(this);
30
+ _MultichainAccountWallet_lock.set(this, new async_mutex_1.Mutex());
28
31
  _MultichainAccountWallet_id.set(this, void 0);
29
32
  _MultichainAccountWallet_providers.set(this, void 0);
30
33
  _MultichainAccountWallet_entropySource.set(this, void 0);
@@ -32,15 +35,17 @@ class MultichainAccountWallet {
32
35
  _MultichainAccountWallet_messenger.set(this, void 0);
33
36
  // eslint-disable-next-line @typescript-eslint/prefer-readonly
34
37
  _MultichainAccountWallet_initialized.set(this, false);
35
- _MultichainAccountWallet_isAlignmentInProgress.set(this, false);
38
+ _MultichainAccountWallet_status.set(this, void 0);
36
39
  __classPrivateFieldSet(this, _MultichainAccountWallet_id, (0, account_api_1.toMultichainAccountWalletId)(entropySource), "f");
37
40
  __classPrivateFieldSet(this, _MultichainAccountWallet_providers, providers, "f");
38
41
  __classPrivateFieldSet(this, _MultichainAccountWallet_entropySource, entropySource, "f");
39
42
  __classPrivateFieldSet(this, _MultichainAccountWallet_messenger, messenger, "f");
40
43
  __classPrivateFieldSet(this, _MultichainAccountWallet_accountGroups, new Map(), "f");
41
44
  // Initial synchronization (don't emit events during initialization).
45
+ __classPrivateFieldSet(this, _MultichainAccountWallet_status, 'uninitialized', "f");
42
46
  this.sync();
43
47
  __classPrivateFieldSet(this, _MultichainAccountWallet_initialized, true, "f");
48
+ __classPrivateFieldSet(this, _MultichainAccountWallet_status, 'ready', "f");
44
49
  }
45
50
  /**
46
51
  * Force wallet synchronization.
@@ -111,6 +116,14 @@ class MultichainAccountWallet {
111
116
  get entropySource() {
112
117
  return __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f");
113
118
  }
119
+ /**
120
+ * Gets the multichain account wallet current status.
121
+ *
122
+ * @returns The multichain account wallet current status.
123
+ */
124
+ get status() {
125
+ return __classPrivateFieldGet(this, _MultichainAccountWallet_status, "f");
126
+ }
114
127
  /**
115
128
  * Gets multichain account for a given ID.
116
129
  * The default group ID will default to the multichain account with index 0.
@@ -169,80 +182,84 @@ class MultichainAccountWallet {
169
182
  /**
170
183
  * Creates a multichain account group for a given group index.
171
184
  *
185
+ * NOTE: This operation WILL lock the wallet's mutex.
186
+ *
172
187
  * @param groupIndex - The group index to use.
173
188
  * @throws If any of the account providers fails to create their accounts.
174
189
  * @returns The multichain account group for this group index.
175
190
  */
176
191
  async createMultichainAccountGroup(groupIndex) {
177
- const nextGroupIndex = this.getNextGroupIndex();
178
- if (groupIndex > nextGroupIndex) {
179
- throw new Error(`You cannot use a group index that is higher than the next available one: expected <=${nextGroupIndex}, got ${groupIndex}`);
180
- }
181
- let group = this.getMultichainAccountGroup(groupIndex);
182
- if (group) {
183
- // If the group already exists, we just `sync` it and returns the same
184
- // reference.
185
- group.sync();
186
- return group;
187
- }
188
- const results = await Promise.allSettled(__classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f").map((provider) => provider.createAccounts({
189
- entropySource: __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"),
190
- groupIndex,
191
- })));
192
- // --------------------------------------------------------------------------------
193
- // READ THIS CAREFULLY:
194
- //
195
- // Since we're not "fully supporting multichain" for now, we still rely on single
196
- // :accountCreated events to sync multichain account groups and wallets. Which means
197
- // that even if of the provider fails, some accounts will still be created on some
198
- // other providers and will become "available" on the `AccountsController`, like:
199
- //
200
- // 1. Creating a multichain account group for index 1
201
- // 2. EvmAccountProvider.createAccounts returns the EVM account for index 1
202
- // * AccountsController WILL fire :accountCreated for this account
203
- // * This account WILL BE "available" on the AccountsController state
204
- // 3. SolAccountProvider.createAccounts fails to create a Solana account for index 1
205
- // * AccountsController WON't fire :accountCreated for this account
206
- // * This account WON'T be "available" on the Account
207
- // 4. MultichainAccountService will receive a :accountCreated for the EVM account from
208
- // step 2 and will create a new multichain account group for index 1, but it won't
209
- // receive any event for the Solana account of this group. Thus, this group won't be
210
- // "aligned" (missing "blockchain account" on this group).
211
- //
212
- // --------------------------------------------------------------------------------
213
- // If any of the provider failed to create their accounts, then we consider the
214
- // multichain account group to have failed too.
215
- if (results.some((result) => result.status === 'rejected')) {
216
- // NOTE: Some accounts might still have been created on other account providers. We
217
- // don't rollback them.
218
- const error = `Unable to create multichain account group for index: ${groupIndex}`;
219
- let warn = `${error}:`;
220
- for (const result of results) {
221
- if (result.status === 'rejected') {
222
- warn += `\n- ${result.reason}`;
223
- }
192
+ return await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:create-accounts', async () => {
193
+ const nextGroupIndex = this.getNextGroupIndex();
194
+ if (groupIndex > nextGroupIndex) {
195
+ throw new Error(`You cannot use a group index that is higher than the next available one: expected <=${nextGroupIndex}, got ${groupIndex}`);
224
196
  }
225
- console.warn(warn);
226
- throw new Error(error);
227
- }
228
- // Because of the :accountAdded automatic sync, we might already have created the
229
- // group, so we first try to get it.
230
- group = this.getMultichainAccountGroup(groupIndex);
231
- if (!group) {
232
- // If for some reason it's still not created, we're creating it explicitly now:
233
- group = new MultichainAccountGroup_1.MultichainAccountGroup({
234
- wallet: this,
235
- providers: __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"),
197
+ let group = this.getMultichainAccountGroup(groupIndex);
198
+ if (group) {
199
+ // If the group already exists, we just `sync` it and returns the same
200
+ // reference.
201
+ group.sync();
202
+ return group;
203
+ }
204
+ const results = await Promise.allSettled(__classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f").map((provider) => provider.createAccounts({
205
+ entropySource: __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"),
236
206
  groupIndex,
237
- messenger: __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f"),
238
- });
239
- }
240
- // Register the account to our internal map.
241
- __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").set(groupIndex, group); // `group` cannot be undefined here.
242
- if (__classPrivateFieldGet(this, _MultichainAccountWallet_initialized, "f")) {
243
- __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f").publish('MultichainAccountService:multichainAccountGroupCreated', group);
244
- }
245
- return group;
207
+ })));
208
+ // --------------------------------------------------------------------------------
209
+ // READ THIS CAREFULLY:
210
+ //
211
+ // Since we're not "fully supporting multichain" for now, we still rely on single
212
+ // :accountCreated events to sync multichain account groups and wallets. Which means
213
+ // that even if of the provider fails, some accounts will still be created on some
214
+ // other providers and will become "available" on the `AccountsController`, like:
215
+ //
216
+ // 1. Creating a multichain account group for index 1
217
+ // 2. EvmAccountProvider.createAccounts returns the EVM account for index 1
218
+ // * AccountsController WILL fire :accountCreated for this account
219
+ // * This account WILL BE "available" on the AccountsController state
220
+ // 3. SolAccountProvider.createAccounts fails to create a Solana account for index 1
221
+ // * AccountsController WON't fire :accountCreated for this account
222
+ // * This account WON'T be "available" on the Account
223
+ // 4. MultichainAccountService will receive a :accountCreated for the EVM account from
224
+ // step 2 and will create a new multichain account group for index 1, but it won't
225
+ // receive any event for the Solana account of this group. Thus, this group won't be
226
+ // "aligned" (missing "blockchain account" on this group).
227
+ //
228
+ // --------------------------------------------------------------------------------
229
+ // If any of the provider failed to create their accounts, then we consider the
230
+ // multichain account group to have failed too.
231
+ if (results.some((result) => result.status === 'rejected')) {
232
+ // NOTE: Some accounts might still have been created on other account providers. We
233
+ // don't rollback them.
234
+ const error = `Unable to create multichain account group for index: ${groupIndex}`;
235
+ let warn = `${error}:`;
236
+ for (const result of results) {
237
+ if (result.status === 'rejected') {
238
+ warn += `\n- ${result.reason}`;
239
+ }
240
+ }
241
+ console.warn(warn);
242
+ throw new Error(error);
243
+ }
244
+ // Because of the :accountAdded automatic sync, we might already have created the
245
+ // group, so we first try to get it.
246
+ group = this.getMultichainAccountGroup(groupIndex);
247
+ if (!group) {
248
+ // If for some reason it's still not created, we're creating it explicitly now:
249
+ group = new MultichainAccountGroup_1.MultichainAccountGroup({
250
+ wallet: this,
251
+ providers: __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f"),
252
+ groupIndex,
253
+ messenger: __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f"),
254
+ });
255
+ }
256
+ // Register the account to our internal map.
257
+ __classPrivateFieldGet(this, _MultichainAccountWallet_accountGroups, "f").set(groupIndex, group); // `group` cannot be undefined here.
258
+ if (__classPrivateFieldGet(this, _MultichainAccountWallet_initialized, "f")) {
259
+ __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f").publish('MultichainAccountService:multichainAccountGroupCreated', group);
260
+ }
261
+ return group;
262
+ });
246
263
  }
247
264
  /**
248
265
  * Creates the next multichain account group.
@@ -254,114 +271,124 @@ class MultichainAccountWallet {
254
271
  return this.createMultichainAccountGroup(this.getNextGroupIndex());
255
272
  }
256
273
  /**
257
- * Gets whether alignment is currently in progress for this wallet.
274
+ * Align all accounts from each existing multichain account groups.
258
275
  *
259
- * @returns True if alignment is in progress, false otherwise.
260
- */
261
- getIsAlignmentInProgress() {
262
- return __classPrivateFieldGet(this, _MultichainAccountWallet_isAlignmentInProgress, "f");
263
- }
264
- /**
265
- * Align all multichain account groups.
276
+ * NOTE: This operation WILL lock the wallet's mutex.
266
277
  */
267
- async alignGroups() {
268
- if (__classPrivateFieldGet(this, _MultichainAccountWallet_isAlignmentInProgress, "f")) {
269
- return; // Prevent concurrent alignments
270
- }
271
- __classPrivateFieldSet(this, _MultichainAccountWallet_isAlignmentInProgress, true, "f");
272
- try {
273
- const groups = this.getMultichainAccountGroups();
274
- await Promise.all(groups.map((g) => g.align()));
275
- }
276
- finally {
277
- __classPrivateFieldSet(this, _MultichainAccountWallet_isAlignmentInProgress, false, "f");
278
- }
278
+ async alignAccounts() {
279
+ await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:alignment', async () => {
280
+ await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_alignAccounts).call(this);
281
+ });
279
282
  }
280
283
  /**
281
284
  * Align a specific multichain account group.
282
285
  *
286
+ * NOTE: This operation WILL lock the wallet's mutex.
287
+ *
283
288
  * @param groupIndex - The group index to align.
284
289
  */
285
290
  async alignGroup(groupIndex) {
286
- if (__classPrivateFieldGet(this, _MultichainAccountWallet_isAlignmentInProgress, "f")) {
287
- return; // Prevent concurrent alignments
288
- }
289
- __classPrivateFieldSet(this, _MultichainAccountWallet_isAlignmentInProgress, true, "f");
290
- try {
291
+ await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:alignment', async () => {
291
292
  const group = this.getMultichainAccountGroup(groupIndex);
292
293
  if (group) {
293
294
  await group.align();
294
295
  }
295
- }
296
- finally {
297
- __classPrivateFieldSet(this, _MultichainAccountWallet_isAlignmentInProgress, false, "f");
298
- }
296
+ });
299
297
  }
300
298
  /**
301
299
  * Discover and create accounts for all providers.
302
300
  *
301
+ * NOTE: This operation WILL lock the wallet's mutex.
302
+ *
303
303
  * @returns The discovered accounts for each provider.
304
304
  */
305
- async discoverAndCreateAccounts() {
306
- // Start with the next available group index (so we can resume the discovery
307
- // from there).
308
- let maxGroupIndex = this.getNextGroupIndex();
309
- // One serialized loop per provider; all run concurrently
310
- const runProviderDiscovery = async (context) => {
311
- const message = (stepName, groupIndex) => `[${context.provider.getName()}] Discovery ${stepName} (groupIndex=${groupIndex})`;
312
- while (!context.stopped) {
313
- // Fast‑forward to current high‑water mark
314
- const targetGroupIndex = Math.max(context.groupIndex, maxGroupIndex);
315
- log(message('STARTED', targetGroupIndex));
316
- let accounts = [];
317
- try {
318
- accounts = await context.provider.discoverAndCreateAccounts({
319
- entropySource: __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"),
320
- groupIndex: targetGroupIndex,
321
- });
322
- }
323
- catch (error) {
324
- context.stopped = true;
325
- console.error(error);
326
- log(message('FAILED', targetGroupIndex), error);
327
- break;
328
- }
329
- if (!accounts.length) {
330
- log(message('STOPPED', targetGroupIndex));
331
- context.stopped = true;
332
- break;
305
+ async discoverAccounts() {
306
+ return __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_withLock).call(this, 'in-progress:discovery', async () => {
307
+ // Start with the next available group index (so we can resume the discovery
308
+ // from there).
309
+ let maxGroupIndex = this.getNextGroupIndex();
310
+ // One serialized loop per provider; all run concurrently
311
+ const runProviderDiscovery = async (context) => {
312
+ const message = (stepName, groupIndex) => `[${context.provider.getName()}] Discovery ${stepName} (groupIndex=${groupIndex})`;
313
+ while (!context.stopped) {
314
+ // Fast‑forward to current high‑water mark
315
+ const targetGroupIndex = Math.max(context.groupIndex, maxGroupIndex);
316
+ log(message('STARTED', targetGroupIndex));
317
+ let accounts = [];
318
+ try {
319
+ accounts = await context.provider.discoverAccounts({
320
+ entropySource: __classPrivateFieldGet(this, _MultichainAccountWallet_entropySource, "f"),
321
+ groupIndex: targetGroupIndex,
322
+ });
323
+ }
324
+ catch (error) {
325
+ context.stopped = true;
326
+ console.error(error);
327
+ log(message('FAILED', targetGroupIndex), error);
328
+ break;
329
+ }
330
+ if (!accounts.length) {
331
+ log(message('STOPPED', targetGroupIndex));
332
+ context.stopped = true;
333
+ break;
334
+ }
335
+ log(message('SUCCEEDED', targetGroupIndex));
336
+ context.accounts = context.accounts.concat(accounts);
337
+ const nextGroupIndex = targetGroupIndex + 1;
338
+ context.groupIndex = nextGroupIndex;
339
+ if (nextGroupIndex > maxGroupIndex) {
340
+ maxGroupIndex = nextGroupIndex;
341
+ }
333
342
  }
334
- log(message('SUCCEEDED', targetGroupIndex));
335
- context.count += accounts.length;
336
- const nextGroupIndex = targetGroupIndex + 1;
337
- context.groupIndex = nextGroupIndex;
338
- if (nextGroupIndex > maxGroupIndex) {
339
- maxGroupIndex = nextGroupIndex;
340
- }
341
- }
342
- };
343
- const providerContexts = __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f").map((provider) => ({
344
- provider,
345
- stopped: false,
346
- groupIndex: maxGroupIndex,
347
- count: 0,
348
- }));
349
- // Start discovery for each providers.
350
- await Promise.all(providerContexts.map(runProviderDiscovery));
351
- // Sync the wallet after discovery to ensure that the newly added accounts are added into their groups.
352
- // We can potentially remove this if we know that this race condition is not an issue in practice.
353
- this.sync();
354
- // Align missing accounts from group. This is required to create missing account from non-discovered
355
- // indexes for some providers.
356
- await this.alignGroups();
357
- const discoveredAccounts = {};
358
- for (const context of providerContexts) {
359
- const providerName = context.provider.getName();
360
- discoveredAccounts[providerName] = context.count;
361
- }
362
- return discoveredAccounts;
343
+ };
344
+ const providerContexts = __classPrivateFieldGet(this, _MultichainAccountWallet_providers, "f").map((provider) => ({
345
+ provider,
346
+ stopped: false,
347
+ groupIndex: maxGroupIndex,
348
+ accounts: [],
349
+ }));
350
+ // Start discovery for each providers.
351
+ await Promise.all(providerContexts.map(runProviderDiscovery));
352
+ // Sync the wallet after discovery to ensure that the newly added accounts are added into their groups.
353
+ // We can potentially remove this if we know that this race condition is not an issue in practice.
354
+ this.sync();
355
+ // Align missing accounts from group. This is required to create missing account from non-discovered
356
+ // indexes for some providers.
357
+ await __classPrivateFieldGet(this, _MultichainAccountWallet_instances, "m", _MultichainAccountWallet_alignAccounts).call(this);
358
+ return providerContexts.flatMap((context) => context.accounts);
359
+ });
363
360
  }
364
361
  }
365
362
  exports.MultichainAccountWallet = MultichainAccountWallet;
366
- _MultichainAccountWallet_id = new WeakMap(), _MultichainAccountWallet_providers = new WeakMap(), _MultichainAccountWallet_entropySource = new WeakMap(), _MultichainAccountWallet_accountGroups = new WeakMap(), _MultichainAccountWallet_messenger = new WeakMap(), _MultichainAccountWallet_initialized = new WeakMap(), _MultichainAccountWallet_isAlignmentInProgress = new WeakMap();
363
+ _MultichainAccountWallet_lock = new WeakMap(), _MultichainAccountWallet_id = new WeakMap(), _MultichainAccountWallet_providers = new WeakMap(), _MultichainAccountWallet_entropySource = new WeakMap(), _MultichainAccountWallet_accountGroups = new WeakMap(), _MultichainAccountWallet_messenger = new WeakMap(), _MultichainAccountWallet_initialized = new WeakMap(), _MultichainAccountWallet_status = new WeakMap(), _MultichainAccountWallet_instances = new WeakSet(), _MultichainAccountWallet_withLock =
364
+ /**
365
+ * Set the wallet status and run the associated operation callback.
366
+ *
367
+ * @param status - Wallet status associated with this operation.
368
+ * @param operation - Operation to run.
369
+ * @returns The operation's result.
370
+ * @throws {Error} If the wallet is already running a mutable operation.
371
+ */
372
+ async function _MultichainAccountWallet_withLock(status, operation) {
373
+ const release = await __classPrivateFieldGet(this, _MultichainAccountWallet_lock, "f").acquire();
374
+ try {
375
+ __classPrivateFieldSet(this, _MultichainAccountWallet_status, status, "f");
376
+ __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f").publish('MultichainAccountService:walletStatusChange', this.id, __classPrivateFieldGet(this, _MultichainAccountWallet_status, "f"));
377
+ return await operation();
378
+ }
379
+ finally {
380
+ __classPrivateFieldSet(this, _MultichainAccountWallet_status, 'ready', "f");
381
+ __classPrivateFieldGet(this, _MultichainAccountWallet_messenger, "f").publish('MultichainAccountService:walletStatusChange', this.id, __classPrivateFieldGet(this, _MultichainAccountWallet_status, "f"));
382
+ release();
383
+ }
384
+ }, _MultichainAccountWallet_alignAccounts =
385
+ /**
386
+ * Align all multichain account groups.
387
+ *
388
+ * NOTE: This operation WILL NOT lock the wallet's mutex.
389
+ */
390
+ async function _MultichainAccountWallet_alignAccounts() {
391
+ const groups = this.getMultichainAccountGroups();
392
+ await Promise.all(groups.map((group) => group.align()));
393
+ };
367
394
  //# sourceMappingURL=MultichainAccountWallet.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"MultichainAccountWallet.cjs","sourceRoot":"","sources":["../src/MultichainAccountWallet.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getGroupIndexFromMultichainAccountGroupId,\n isMultichainAccountGroupId,\n toMultichainAccountWalletId,\n} from '@metamask/account-api';\nimport { toDefaultAccountGroupId } from '@metamask/account-api';\nimport { AccountWalletType } from '@metamask/account-api';\nimport type {\n Bip44Account,\n MultichainAccountWalletId,\n MultichainAccountWallet as MultichainAccountWalletDefinition,\n} from '@metamask/account-api';\nimport type { AccountGroupId } from '@metamask/account-api';\nimport {\n type EntropySourceId,\n type KeyringAccount,\n} from '@metamask/keyring-api';\nimport { createProjectLogger } from '@metamask/utils';\n\nimport { MultichainAccountGroup } from './MultichainAccountGroup';\nimport type { NamedAccountProvider } from './providers';\nimport type { MultichainAccountServiceMessenger } from './types';\n\n/**\n * The context for a provider discovery.\n */\ntype AccountProviderDiscoveryContext = {\n provider: NamedAccountProvider;\n stopped: boolean;\n groupIndex: number;\n count: number;\n};\n\n/**\n * The metrics resulting from account discovery.\n */\nexport type AccountDiscoveryMetrics = {\n [providerName: string]: number;\n};\n\nconst log = createProjectLogger('multichain-account-service');\n\n/**\n * A multichain account wallet that holds multiple multichain accounts (one multichain account per\n * group index).\n */\nexport class MultichainAccountWallet<\n Account extends Bip44Account<KeyringAccount>,\n> implements MultichainAccountWalletDefinition<Account>\n{\n readonly #id: MultichainAccountWalletId;\n\n readonly #providers: NamedAccountProvider<Account>[];\n\n readonly #entropySource: EntropySourceId;\n\n readonly #accountGroups: Map<number, MultichainAccountGroup<Account>>;\n\n readonly #messenger: MultichainAccountServiceMessenger;\n\n // eslint-disable-next-line @typescript-eslint/prefer-readonly\n #initialized = false;\n\n #isAlignmentInProgress: boolean = false;\n\n constructor({\n providers,\n entropySource,\n messenger,\n }: {\n providers: NamedAccountProvider<Account>[];\n entropySource: EntropySourceId;\n messenger: MultichainAccountServiceMessenger;\n }) {\n this.#id = toMultichainAccountWalletId(entropySource);\n this.#providers = providers;\n this.#entropySource = entropySource;\n this.#messenger = messenger;\n this.#accountGroups = new Map();\n\n // Initial synchronization (don't emit events during initialization).\n this.sync();\n this.#initialized = true;\n }\n\n /**\n * Force wallet synchronization.\n *\n * This can be used if account providers got new accounts that the wallet\n * doesn't know about.\n */\n sync(): void {\n for (const provider of this.#providers) {\n for (const account of provider.getAccounts()) {\n const { entropy } = account.options;\n\n // Filter for this wallet only.\n if (entropy.id !== this.entropySource) {\n continue;\n }\n\n // This multichain account might exists already.\n let multichainAccount = this.#accountGroups.get(entropy.groupIndex);\n if (!multichainAccount) {\n multichainAccount = new MultichainAccountGroup<Account>({\n groupIndex: entropy.groupIndex,\n wallet: this,\n providers: this.#providers,\n messenger: this.#messenger,\n });\n\n // This existing multichain account group might differ from the\n // `createMultichainAccountGroup` behavior. When creating a new\n // group, we expect the providers to all succeed. But here, we're\n // just fetching the account lists from them, so this group might\n // not be \"aligned\" yet (e.g having a missing Solana account).\n //\n // Since \"aligning\" is an async operation, it would have to be run\n // after the first-sync.\n // TODO: Implement align mechanism to create \"missing\" accounts.\n\n this.#accountGroups.set(entropy.groupIndex, multichainAccount);\n }\n }\n }\n\n // Now force-sync all remaining multichain accounts.\n for (const [\n groupIndex,\n multichainAccount,\n ] of this.#accountGroups.entries()) {\n multichainAccount.sync();\n\n // Clean up old multichain accounts.\n if (!multichainAccount.hasAccounts()) {\n this.#accountGroups.delete(groupIndex);\n }\n }\n }\n\n /**\n * Gets the multichain account wallet ID.\n *\n * @returns The multichain account wallet ID.\n */\n get id(): MultichainAccountWalletId {\n return this.#id;\n }\n\n /**\n * Gets the multichain account wallet type, which is always {@link AccountWalletType.Entropy}.\n *\n * @returns The multichain account wallet type.\n */\n get type(): AccountWalletType.Entropy {\n return AccountWalletType.Entropy;\n }\n\n /**\n * Gets the multichain account wallet entropy source.\n *\n * @returns The multichain account wallet entropy source.\n */\n get entropySource(): EntropySourceId {\n return this.#entropySource;\n }\n\n /**\n * Gets multichain account for a given ID.\n * The default group ID will default to the multichain account with index 0.\n *\n * @param id - Account group ID.\n * @returns Account group.\n */\n getAccountGroup(\n id: AccountGroupId,\n ): MultichainAccountGroup<Account> | undefined {\n // We consider the \"default case\" to be mapped to index 0.\n if (id === toDefaultAccountGroupId(this.id)) {\n return this.#accountGroups.get(0);\n }\n\n // If it is not a valid ID, we cannot extract the group index\n // from it, so we fail fast.\n if (!isMultichainAccountGroupId(id)) {\n return undefined;\n }\n\n const groupIndex = getGroupIndexFromMultichainAccountGroupId(id);\n return this.#accountGroups.get(groupIndex);\n }\n\n /**\n * Gets all multichain accounts. Similar to {@link MultichainAccountWallet.getMultichainAccountGroups}.\n *\n * @returns The multichain accounts.\n */\n getAccountGroups(): MultichainAccountGroup<Account>[] {\n return this.getMultichainAccountGroups();\n }\n\n /**\n * Gets multichain account group for a given index.\n *\n * @param groupIndex - Multichain account index.\n * @returns The multichain account associated with the given index.\n */\n getMultichainAccountGroup(\n groupIndex: number,\n ): MultichainAccountGroup<Account> | undefined {\n return this.#accountGroups.get(groupIndex);\n }\n\n /**\n * Gets all multichain account groups.\n *\n * @returns The multichain accounts.\n */\n getMultichainAccountGroups(): MultichainAccountGroup<Account>[] {\n return Array.from(this.#accountGroups.values()); // TODO: Prevent copy here.\n }\n\n /**\n * Gets next group index for this wallet.\n *\n * @returns The next group index of this wallet.\n */\n getNextGroupIndex(): number {\n // We do not check for gaps.\n return (\n Math.max(\n -1, // So it will default to 0 if no groups.\n ...this.#accountGroups.keys(),\n ) + 1\n );\n }\n\n /**\n * Creates a multichain account group for a given group index.\n *\n * @param groupIndex - The group index to use.\n * @throws If any of the account providers fails to create their accounts.\n * @returns The multichain account group for this group index.\n */\n async createMultichainAccountGroup(\n groupIndex: number,\n ): Promise<MultichainAccountGroup<Account>> {\n const nextGroupIndex = this.getNextGroupIndex();\n if (groupIndex > nextGroupIndex) {\n throw new Error(\n `You cannot use a group index that is higher than the next available one: expected <=${nextGroupIndex}, got ${groupIndex}`,\n );\n }\n\n let group = this.getMultichainAccountGroup(groupIndex);\n if (group) {\n // If the group already exists, we just `sync` it and returns the same\n // reference.\n group.sync();\n\n return group;\n }\n\n const results = await Promise.allSettled(\n this.#providers.map((provider) =>\n provider.createAccounts({\n entropySource: this.#entropySource,\n groupIndex,\n }),\n ),\n );\n\n // --------------------------------------------------------------------------------\n // READ THIS CAREFULLY:\n //\n // Since we're not \"fully supporting multichain\" for now, we still rely on single\n // :accountCreated events to sync multichain account groups and wallets. Which means\n // that even if of the provider fails, some accounts will still be created on some\n // other providers and will become \"available\" on the `AccountsController`, like:\n //\n // 1. Creating a multichain account group for index 1\n // 2. EvmAccountProvider.createAccounts returns the EVM account for index 1\n // * AccountsController WILL fire :accountCreated for this account\n // * This account WILL BE \"available\" on the AccountsController state\n // 3. SolAccountProvider.createAccounts fails to create a Solana account for index 1\n // * AccountsController WON't fire :accountCreated for this account\n // * This account WON'T be \"available\" on the Account\n // 4. MultichainAccountService will receive a :accountCreated for the EVM account from\n // step 2 and will create a new multichain account group for index 1, but it won't\n // receive any event for the Solana account of this group. Thus, this group won't be\n // \"aligned\" (missing \"blockchain account\" on this group).\n //\n // --------------------------------------------------------------------------------\n\n // If any of the provider failed to create their accounts, then we consider the\n // multichain account group to have failed too.\n if (results.some((result) => result.status === 'rejected')) {\n // NOTE: Some accounts might still have been created on other account providers. We\n // don't rollback them.\n const error = `Unable to create multichain account group for index: ${groupIndex}`;\n\n let warn = `${error}:`;\n for (const result of results) {\n if (result.status === 'rejected') {\n warn += `\\n- ${result.reason}`;\n }\n }\n console.warn(warn);\n\n throw new Error(error);\n }\n\n // Because of the :accountAdded automatic sync, we might already have created the\n // group, so we first try to get it.\n group = this.getMultichainAccountGroup(groupIndex);\n if (!group) {\n // If for some reason it's still not created, we're creating it explicitly now:\n group = new MultichainAccountGroup({\n wallet: this,\n providers: this.#providers,\n groupIndex,\n messenger: this.#messenger,\n });\n }\n\n // Register the account to our internal map.\n this.#accountGroups.set(groupIndex, group); // `group` cannot be undefined here.\n\n if (this.#initialized) {\n this.#messenger.publish(\n 'MultichainAccountService:multichainAccountGroupCreated',\n group,\n );\n }\n\n return group;\n }\n\n /**\n * Creates the next multichain account group.\n *\n * @throws If any of the account providers fails to create their accounts.\n * @returns The multichain account group for the next group index available.\n */\n async createNextMultichainAccountGroup(): Promise<\n MultichainAccountGroup<Account>\n > {\n return this.createMultichainAccountGroup(this.getNextGroupIndex());\n }\n\n /**\n * Gets whether alignment is currently in progress for this wallet.\n *\n * @returns True if alignment is in progress, false otherwise.\n */\n getIsAlignmentInProgress(): boolean {\n return this.#isAlignmentInProgress;\n }\n\n /**\n * Align all multichain account groups.\n */\n async alignGroups(): Promise<void> {\n if (this.#isAlignmentInProgress) {\n return; // Prevent concurrent alignments\n }\n\n this.#isAlignmentInProgress = true;\n try {\n const groups = this.getMultichainAccountGroups();\n await Promise.all(groups.map((g) => g.align()));\n } finally {\n this.#isAlignmentInProgress = false;\n }\n }\n\n /**\n * Align a specific multichain account group.\n *\n * @param groupIndex - The group index to align.\n */\n async alignGroup(groupIndex: number): Promise<void> {\n if (this.#isAlignmentInProgress) {\n return; // Prevent concurrent alignments\n }\n\n this.#isAlignmentInProgress = true;\n try {\n const group = this.getMultichainAccountGroup(groupIndex);\n if (group) {\n await group.align();\n }\n } finally {\n this.#isAlignmentInProgress = false;\n }\n }\n\n /**\n * Discover and create accounts for all providers.\n *\n * @returns The discovered accounts for each provider.\n */\n async discoverAndCreateAccounts(): Promise<AccountDiscoveryMetrics> {\n // Start with the next available group index (so we can resume the discovery\n // from there).\n let maxGroupIndex = this.getNextGroupIndex();\n\n // One serialized loop per provider; all run concurrently\n const runProviderDiscovery = async (\n context: AccountProviderDiscoveryContext,\n ) => {\n const message = (stepName: string, groupIndex: number) =>\n `[${context.provider.getName()}] Discovery ${stepName} (groupIndex=${groupIndex})`;\n\n while (!context.stopped) {\n // Fast‑forward to current high‑water mark\n const targetGroupIndex = Math.max(context.groupIndex, maxGroupIndex);\n\n log(message('STARTED', targetGroupIndex));\n\n let accounts: Bip44Account<KeyringAccount>[] = [];\n try {\n accounts = await context.provider.discoverAndCreateAccounts({\n entropySource: this.#entropySource,\n groupIndex: targetGroupIndex,\n });\n } catch (error) {\n context.stopped = true;\n console.error(error);\n log(message('FAILED', targetGroupIndex), error);\n break;\n }\n\n if (!accounts.length) {\n log(message('STOPPED', targetGroupIndex));\n context.stopped = true;\n break;\n }\n\n log(message('SUCCEEDED', targetGroupIndex));\n\n context.count += accounts.length;\n\n const nextGroupIndex = targetGroupIndex + 1;\n context.groupIndex = nextGroupIndex;\n\n if (nextGroupIndex > maxGroupIndex) {\n maxGroupIndex = nextGroupIndex;\n }\n }\n };\n\n const providerContexts: AccountProviderDiscoveryContext[] =\n this.#providers.map((provider) => ({\n provider,\n stopped: false,\n groupIndex: maxGroupIndex,\n count: 0,\n }));\n\n // Start discovery for each providers.\n await Promise.all(providerContexts.map(runProviderDiscovery));\n\n // Sync the wallet after discovery to ensure that the newly added accounts are added into their groups.\n // We can potentially remove this if we know that this race condition is not an issue in practice.\n this.sync();\n\n // Align missing accounts from group. This is required to create missing account from non-discovered\n // indexes for some providers.\n await this.alignGroups();\n\n const discoveredAccounts: Record<string, number> = {};\n for (const context of providerContexts) {\n const providerName = context.provider.getName();\n discoveredAccounts[providerName] = context.count;\n }\n\n return discoveredAccounts;\n }\n}\n"]}
1
+ {"version":3,"file":"MultichainAccountWallet.cjs","sourceRoot":"","sources":["../src/MultichainAccountWallet.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n getGroupIndexFromMultichainAccountGroupId,\n isMultichainAccountGroupId,\n toMultichainAccountWalletId,\n} from '@metamask/account-api';\nimport { toDefaultAccountGroupId } from '@metamask/account-api';\nimport { AccountWalletType } from '@metamask/account-api';\nimport type {\n Bip44Account,\n MultichainAccountWalletId,\n MultichainAccountWallet as MultichainAccountWalletDefinition,\n MultichainAccountWalletStatus,\n} from '@metamask/account-api';\nimport type { AccountGroupId } from '@metamask/account-api';\nimport {\n type EntropySourceId,\n type KeyringAccount,\n} from '@metamask/keyring-api';\nimport { createProjectLogger } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\n\nimport { MultichainAccountGroup } from './MultichainAccountGroup';\nimport type { NamedAccountProvider } from './providers';\nimport type { MultichainAccountServiceMessenger } from './types';\n\n/**\n * The context for a provider discovery.\n */\ntype AccountProviderDiscoveryContext<\n Account extends Bip44Account<KeyringAccount>,\n> = {\n provider: NamedAccountProvider<Account>;\n stopped: boolean;\n groupIndex: number;\n accounts: Account[];\n};\n\nconst log = createProjectLogger('multichain-account-service');\n\n/**\n * A multichain account wallet that holds multiple multichain accounts (one multichain account per\n * group index).\n */\nexport class MultichainAccountWallet<\n Account extends Bip44Account<KeyringAccount>,\n> implements MultichainAccountWalletDefinition<Account>\n{\n readonly #lock = new Mutex();\n\n readonly #id: MultichainAccountWalletId;\n\n readonly #providers: NamedAccountProvider<Account>[];\n\n readonly #entropySource: EntropySourceId;\n\n readonly #accountGroups: Map<number, MultichainAccountGroup<Account>>;\n\n readonly #messenger: MultichainAccountServiceMessenger;\n\n // eslint-disable-next-line @typescript-eslint/prefer-readonly\n #initialized = false;\n\n #status: MultichainAccountWalletStatus;\n\n constructor({\n providers,\n entropySource,\n messenger,\n }: {\n providers: NamedAccountProvider<Account>[];\n entropySource: EntropySourceId;\n messenger: MultichainAccountServiceMessenger;\n }) {\n this.#id = toMultichainAccountWalletId(entropySource);\n this.#providers = providers;\n this.#entropySource = entropySource;\n this.#messenger = messenger;\n this.#accountGroups = new Map();\n\n // Initial synchronization (don't emit events during initialization).\n this.#status = 'uninitialized';\n this.sync();\n this.#initialized = true;\n this.#status = 'ready';\n }\n\n /**\n * Force wallet synchronization.\n *\n * This can be used if account providers got new accounts that the wallet\n * doesn't know about.\n */\n sync(): void {\n for (const provider of this.#providers) {\n for (const account of provider.getAccounts()) {\n const { entropy } = account.options;\n\n // Filter for this wallet only.\n if (entropy.id !== this.entropySource) {\n continue;\n }\n\n // This multichain account might exists already.\n let multichainAccount = this.#accountGroups.get(entropy.groupIndex);\n if (!multichainAccount) {\n multichainAccount = new MultichainAccountGroup<Account>({\n groupIndex: entropy.groupIndex,\n wallet: this,\n providers: this.#providers,\n messenger: this.#messenger,\n });\n\n // This existing multichain account group might differ from the\n // `createMultichainAccountGroup` behavior. When creating a new\n // group, we expect the providers to all succeed. But here, we're\n // just fetching the account lists from them, so this group might\n // not be \"aligned\" yet (e.g having a missing Solana account).\n //\n // Since \"aligning\" is an async operation, it would have to be run\n // after the first-sync.\n // TODO: Implement align mechanism to create \"missing\" accounts.\n\n this.#accountGroups.set(entropy.groupIndex, multichainAccount);\n }\n }\n }\n\n // Now force-sync all remaining multichain accounts.\n for (const [\n groupIndex,\n multichainAccount,\n ] of this.#accountGroups.entries()) {\n multichainAccount.sync();\n\n // Clean up old multichain accounts.\n if (!multichainAccount.hasAccounts()) {\n this.#accountGroups.delete(groupIndex);\n }\n }\n }\n\n /**\n * Gets the multichain account wallet ID.\n *\n * @returns The multichain account wallet ID.\n */\n get id(): MultichainAccountWalletId {\n return this.#id;\n }\n\n /**\n * Gets the multichain account wallet type, which is always {@link AccountWalletType.Entropy}.\n *\n * @returns The multichain account wallet type.\n */\n get type(): AccountWalletType.Entropy {\n return AccountWalletType.Entropy;\n }\n\n /**\n * Gets the multichain account wallet entropy source.\n *\n * @returns The multichain account wallet entropy source.\n */\n get entropySource(): EntropySourceId {\n return this.#entropySource;\n }\n\n /**\n * Gets the multichain account wallet current status.\n *\n * @returns The multichain account wallet current status.\n */\n get status(): MultichainAccountWalletStatus {\n return this.#status;\n }\n\n /**\n * Set the wallet status and run the associated operation callback.\n *\n * @param status - Wallet status associated with this operation.\n * @param operation - Operation to run.\n * @returns The operation's result.\n * @throws {Error} If the wallet is already running a mutable operation.\n */\n async #withLock<Return>(\n status: MultichainAccountWalletStatus,\n operation: () => Promise<Return>,\n ) {\n const release = await this.#lock.acquire();\n try {\n this.#status = status;\n this.#messenger.publish(\n 'MultichainAccountService:walletStatusChange',\n this.id,\n this.#status,\n );\n return await operation();\n } finally {\n this.#status = 'ready';\n this.#messenger.publish(\n 'MultichainAccountService:walletStatusChange',\n this.id,\n this.#status,\n );\n release();\n }\n }\n\n /**\n * Gets multichain account for a given ID.\n * The default group ID will default to the multichain account with index 0.\n *\n * @param id - Account group ID.\n * @returns Account group.\n */\n getAccountGroup(\n id: AccountGroupId,\n ): MultichainAccountGroup<Account> | undefined {\n // We consider the \"default case\" to be mapped to index 0.\n if (id === toDefaultAccountGroupId(this.id)) {\n return this.#accountGroups.get(0);\n }\n\n // If it is not a valid ID, we cannot extract the group index\n // from it, so we fail fast.\n if (!isMultichainAccountGroupId(id)) {\n return undefined;\n }\n\n const groupIndex = getGroupIndexFromMultichainAccountGroupId(id);\n return this.#accountGroups.get(groupIndex);\n }\n\n /**\n * Gets all multichain accounts. Similar to {@link MultichainAccountWallet.getMultichainAccountGroups}.\n *\n * @returns The multichain accounts.\n */\n getAccountGroups(): MultichainAccountGroup<Account>[] {\n return this.getMultichainAccountGroups();\n }\n\n /**\n * Gets multichain account group for a given index.\n *\n * @param groupIndex - Multichain account index.\n * @returns The multichain account associated with the given index.\n */\n getMultichainAccountGroup(\n groupIndex: number,\n ): MultichainAccountGroup<Account> | undefined {\n return this.#accountGroups.get(groupIndex);\n }\n\n /**\n * Gets all multichain account groups.\n *\n * @returns The multichain accounts.\n */\n getMultichainAccountGroups(): MultichainAccountGroup<Account>[] {\n return Array.from(this.#accountGroups.values()); // TODO: Prevent copy here.\n }\n\n /**\n * Gets next group index for this wallet.\n *\n * @returns The next group index of this wallet.\n */\n getNextGroupIndex(): number {\n // We do not check for gaps.\n return (\n Math.max(\n -1, // So it will default to 0 if no groups.\n ...this.#accountGroups.keys(),\n ) + 1\n );\n }\n\n /**\n * Creates a multichain account group for a given group index.\n *\n * NOTE: This operation WILL lock the wallet's mutex.\n *\n * @param groupIndex - The group index to use.\n * @throws If any of the account providers fails to create their accounts.\n * @returns The multichain account group for this group index.\n */\n async createMultichainAccountGroup(\n groupIndex: number,\n ): Promise<MultichainAccountGroup<Account>> {\n return await this.#withLock('in-progress:create-accounts', async () => {\n const nextGroupIndex = this.getNextGroupIndex();\n if (groupIndex > nextGroupIndex) {\n throw new Error(\n `You cannot use a group index that is higher than the next available one: expected <=${nextGroupIndex}, got ${groupIndex}`,\n );\n }\n\n let group = this.getMultichainAccountGroup(groupIndex);\n if (group) {\n // If the group already exists, we just `sync` it and returns the same\n // reference.\n group.sync();\n\n return group;\n }\n\n const results = await Promise.allSettled(\n this.#providers.map((provider) =>\n provider.createAccounts({\n entropySource: this.#entropySource,\n groupIndex,\n }),\n ),\n );\n\n // --------------------------------------------------------------------------------\n // READ THIS CAREFULLY:\n //\n // Since we're not \"fully supporting multichain\" for now, we still rely on single\n // :accountCreated events to sync multichain account groups and wallets. Which means\n // that even if of the provider fails, some accounts will still be created on some\n // other providers and will become \"available\" on the `AccountsController`, like:\n //\n // 1. Creating a multichain account group for index 1\n // 2. EvmAccountProvider.createAccounts returns the EVM account for index 1\n // * AccountsController WILL fire :accountCreated for this account\n // * This account WILL BE \"available\" on the AccountsController state\n // 3. SolAccountProvider.createAccounts fails to create a Solana account for index 1\n // * AccountsController WON't fire :accountCreated for this account\n // * This account WON'T be \"available\" on the Account\n // 4. MultichainAccountService will receive a :accountCreated for the EVM account from\n // step 2 and will create a new multichain account group for index 1, but it won't\n // receive any event for the Solana account of this group. Thus, this group won't be\n // \"aligned\" (missing \"blockchain account\" on this group).\n //\n // --------------------------------------------------------------------------------\n\n // If any of the provider failed to create their accounts, then we consider the\n // multichain account group to have failed too.\n if (results.some((result) => result.status === 'rejected')) {\n // NOTE: Some accounts might still have been created on other account providers. We\n // don't rollback them.\n const error = `Unable to create multichain account group for index: ${groupIndex}`;\n\n let warn = `${error}:`;\n for (const result of results) {\n if (result.status === 'rejected') {\n warn += `\\n- ${result.reason}`;\n }\n }\n console.warn(warn);\n\n throw new Error(error);\n }\n\n // Because of the :accountAdded automatic sync, we might already have created the\n // group, so we first try to get it.\n group = this.getMultichainAccountGroup(groupIndex);\n if (!group) {\n // If for some reason it's still not created, we're creating it explicitly now:\n group = new MultichainAccountGroup({\n wallet: this,\n providers: this.#providers,\n groupIndex,\n messenger: this.#messenger,\n });\n }\n\n // Register the account to our internal map.\n this.#accountGroups.set(groupIndex, group); // `group` cannot be undefined here.\n\n if (this.#initialized) {\n this.#messenger.publish(\n 'MultichainAccountService:multichainAccountGroupCreated',\n group,\n );\n }\n\n return group;\n });\n }\n\n /**\n * Creates the next multichain account group.\n *\n * @throws If any of the account providers fails to create their accounts.\n * @returns The multichain account group for the next group index available.\n */\n async createNextMultichainAccountGroup(): Promise<\n MultichainAccountGroup<Account>\n > {\n return this.createMultichainAccountGroup(this.getNextGroupIndex());\n }\n\n /**\n * Align all multichain account groups.\n *\n * NOTE: This operation WILL NOT lock the wallet's mutex.\n */\n async #alignAccounts(): Promise<void> {\n const groups = this.getMultichainAccountGroups();\n await Promise.all(groups.map((group) => group.align()));\n }\n\n /**\n * Align all accounts from each existing multichain account groups.\n *\n * NOTE: This operation WILL lock the wallet's mutex.\n */\n async alignAccounts(): Promise<void> {\n await this.#withLock('in-progress:alignment', async () => {\n await this.#alignAccounts();\n });\n }\n\n /**\n * Align a specific multichain account group.\n *\n * NOTE: This operation WILL lock the wallet's mutex.\n *\n * @param groupIndex - The group index to align.\n */\n async alignGroup(groupIndex: number): Promise<void> {\n await this.#withLock('in-progress:alignment', async () => {\n const group = this.getMultichainAccountGroup(groupIndex);\n if (group) {\n await group.align();\n }\n });\n }\n\n /**\n * Discover and create accounts for all providers.\n *\n * NOTE: This operation WILL lock the wallet's mutex.\n *\n * @returns The discovered accounts for each provider.\n */\n async discoverAccounts(): Promise<Account[]> {\n return this.#withLock('in-progress:discovery', async () => {\n // Start with the next available group index (so we can resume the discovery\n // from there).\n let maxGroupIndex = this.getNextGroupIndex();\n\n // One serialized loop per provider; all run concurrently\n const runProviderDiscovery = async (\n context: AccountProviderDiscoveryContext<Account>,\n ) => {\n const message = (stepName: string, groupIndex: number) =>\n `[${context.provider.getName()}] Discovery ${stepName} (groupIndex=${groupIndex})`;\n\n while (!context.stopped) {\n // Fast‑forward to current high‑water mark\n const targetGroupIndex = Math.max(context.groupIndex, maxGroupIndex);\n\n log(message('STARTED', targetGroupIndex));\n\n let accounts: Account[] = [];\n try {\n accounts = await context.provider.discoverAccounts({\n entropySource: this.#entropySource,\n groupIndex: targetGroupIndex,\n });\n } catch (error) {\n context.stopped = true;\n console.error(error);\n log(message('FAILED', targetGroupIndex), error);\n break;\n }\n\n if (!accounts.length) {\n log(message('STOPPED', targetGroupIndex));\n context.stopped = true;\n break;\n }\n\n log(message('SUCCEEDED', targetGroupIndex));\n\n context.accounts = context.accounts.concat(accounts);\n\n const nextGroupIndex = targetGroupIndex + 1;\n context.groupIndex = nextGroupIndex;\n\n if (nextGroupIndex > maxGroupIndex) {\n maxGroupIndex = nextGroupIndex;\n }\n }\n };\n\n const providerContexts: AccountProviderDiscoveryContext<Account>[] =\n this.#providers.map((provider) => ({\n provider,\n stopped: false,\n groupIndex: maxGroupIndex,\n accounts: [],\n }));\n\n // Start discovery for each providers.\n await Promise.all(providerContexts.map(runProviderDiscovery));\n\n // Sync the wallet after discovery to ensure that the newly added accounts are added into their groups.\n // We can potentially remove this if we know that this race condition is not an issue in practice.\n this.sync();\n\n // Align missing accounts from group. This is required to create missing account from non-discovered\n // indexes for some providers.\n await this.#alignAccounts();\n\n return providerContexts.flatMap((context) => context.accounts);\n });\n }\n}\n"]}
@@ -1,16 +1,10 @@
1
1
  import { AccountWalletType } from "@metamask/account-api";
2
- import type { Bip44Account, MultichainAccountWalletId, MultichainAccountWallet as MultichainAccountWalletDefinition } from "@metamask/account-api";
2
+ import type { Bip44Account, MultichainAccountWalletId, MultichainAccountWallet as MultichainAccountWalletDefinition, MultichainAccountWalletStatus } from "@metamask/account-api";
3
3
  import type { AccountGroupId } from "@metamask/account-api";
4
4
  import { type EntropySourceId, type KeyringAccount } from "@metamask/keyring-api";
5
5
  import { MultichainAccountGroup } from "./MultichainAccountGroup.cjs";
6
6
  import type { NamedAccountProvider } from "./providers/index.cjs";
7
7
  import type { MultichainAccountServiceMessenger } from "./types.cjs";
8
- /**
9
- * The metrics resulting from account discovery.
10
- */
11
- export type AccountDiscoveryMetrics = {
12
- [providerName: string]: number;
13
- };
14
8
  /**
15
9
  * A multichain account wallet that holds multiple multichain accounts (one multichain account per
16
10
  * group index).
@@ -47,6 +41,12 @@ export declare class MultichainAccountWallet<Account extends Bip44Account<Keyrin
47
41
  * @returns The multichain account wallet entropy source.
48
42
  */
49
43
  get entropySource(): EntropySourceId;
44
+ /**
45
+ * Gets the multichain account wallet current status.
46
+ *
47
+ * @returns The multichain account wallet current status.
48
+ */
49
+ get status(): MultichainAccountWalletStatus;
50
50
  /**
51
51
  * Gets multichain account for a given ID.
52
52
  * The default group ID will default to the multichain account with index 0.
@@ -83,6 +83,8 @@ export declare class MultichainAccountWallet<Account extends Bip44Account<Keyrin
83
83
  /**
84
84
  * Creates a multichain account group for a given group index.
85
85
  *
86
+ * NOTE: This operation WILL lock the wallet's mutex.
87
+ *
86
88
  * @param groupIndex - The group index to use.
87
89
  * @throws If any of the account providers fails to create their accounts.
88
90
  * @returns The multichain account group for this group index.
@@ -96,26 +98,26 @@ export declare class MultichainAccountWallet<Account extends Bip44Account<Keyrin
96
98
  */
97
99
  createNextMultichainAccountGroup(): Promise<MultichainAccountGroup<Account>>;
98
100
  /**
99
- * Gets whether alignment is currently in progress for this wallet.
101
+ * Align all accounts from each existing multichain account groups.
100
102
  *
101
- * @returns True if alignment is in progress, false otherwise.
102
- */
103
- getIsAlignmentInProgress(): boolean;
104
- /**
105
- * Align all multichain account groups.
103
+ * NOTE: This operation WILL lock the wallet's mutex.
106
104
  */
107
- alignGroups(): Promise<void>;
105
+ alignAccounts(): Promise<void>;
108
106
  /**
109
107
  * Align a specific multichain account group.
110
108
  *
109
+ * NOTE: This operation WILL lock the wallet's mutex.
110
+ *
111
111
  * @param groupIndex - The group index to align.
112
112
  */
113
113
  alignGroup(groupIndex: number): Promise<void>;
114
114
  /**
115
115
  * Discover and create accounts for all providers.
116
116
  *
117
+ * NOTE: This operation WILL lock the wallet's mutex.
118
+ *
117
119
  * @returns The discovered accounts for each provider.
118
120
  */
119
- discoverAndCreateAccounts(): Promise<AccountDiscoveryMetrics>;
121
+ discoverAccounts(): Promise<Account[]>;
120
122
  }
121
123
  //# sourceMappingURL=MultichainAccountWallet.d.cts.map