@metamask-previews/eth-snap-keyring 18.0.2-feb98c5 → 19.0.0-fd40efd

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,11 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [19.0.0]
11
+
12
+ ### Added
13
+
14
+ - Add `SnapKeyring.createAccounts` method ([#448](https://github.com/MetaMask/accounts/pull/448))
15
+ - This method can be used to create one or more accounts using the new keyring v2 account creation typed options.
16
+ - Generic accounts will be filtered out if they are not allowed by the keyring configuration.
17
+
10
18
  ### Changed
11
19
 
12
- - Bump `@metamask/snaps-controllers` from `^14.0.1` to `^17.2.0` ([#422](https://github.com/MetaMask/accounts/pull/422))
13
- - Bump `@metamask/snaps-sdk` from `^9.0.0` to `^10.3.0` ([#422](https://github.com/MetaMask/accounts/pull/422))
14
- - Bump `@metamask/snaps-utils` from `^11.0.0` to `^11.7.0` ([#422](https://github.com/MetaMask/accounts/pull/422))
20
+ - **BREAKING:** Bump `@metamask/keyring-internal-api` from `^9.1.2` to `^10.0.0` ([#460](https://github.com/MetaMask/accounts/pull/460))
21
+ - **BREAKING:** Bump `@metamask/keyring-internal-snap-client` from `^8.0.2` to `^9.0.0` ([#460](https://github.com/MetaMask/accounts/pull/460))
22
+ - Bump `@metamask/keyring-api` from `^21.2.0` to `^21.4.0` ([#460](https://github.com/MetaMask/accounts/pull/460))
23
+ - Bump `@metamask/snaps-controllers` from `^14.0.1` to `^18.0.0` ([#422](https://github.com/MetaMask/accounts/pull/422)), ([#461](https://github.com/MetaMask/accounts/pull/461))
24
+ - Bump `@metamask/snaps-sdk` from `^9.0.0` to `^10.4.0` ([#422](https://github.com/MetaMask/accounts/pull/422)), ([#461](https://github.com/MetaMask/accounts/pull/461))
25
+ - Bump `@metamask/snaps-utils` from `^11.0.0` to `^12.1.0` ([#422](https://github.com/MetaMask/accounts/pull/422)), ([#424](https://github.com/MetaMask/accounts/pull/424)), ([#461](https://github.com/MetaMask/accounts/pull/461))
15
26
 
16
27
  ### Removed
17
28
 
@@ -621,7 +632,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
621
632
 
622
633
  - Initial release.
623
634
 
624
- [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@18.0.2...HEAD
635
+ [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@19.0.0...HEAD
636
+ [19.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@18.0.2...@metamask/eth-snap-keyring@19.0.0
625
637
  [18.0.2]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@18.0.1...@metamask/eth-snap-keyring@18.0.2
626
638
  [18.0.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@18.0.0...@metamask/eth-snap-keyring@18.0.1
627
639
  [18.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@17.3.0...@metamask/eth-snap-keyring@18.0.0
@@ -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_isMinimumPlatformVersion, _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_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;
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");
@@ -22,6 +22,7 @@ const keyring_snap_sdk_1 = require("@metamask/keyring-snap-sdk");
22
22
  const keyring_utils_1 = require("@metamask/keyring-utils");
23
23
  const superstruct_1 = require("@metamask/superstruct");
24
24
  const utils_1 = require("@metamask/utils");
25
+ const async_mutex_1 = require("async-mutex");
25
26
  const uuid_1 = require("uuid");
26
27
  const account_1 = require("./account.cjs");
27
28
  const DeferredPromise_1 = require("./DeferredPromise.cjs");
@@ -102,6 +103,11 @@ class SnapKeyring {
102
103
  * Account deletion is not affected by this flag and is always allowed.
103
104
  */
104
105
  _SnapKeyring_isAnyAccountTypeAllowed.set(this, void 0);
106
+ /**
107
+ * Mutex to ensure exclusive access to the inner keyring during
108
+ * operations that mutate its state.
109
+ */
110
+ _SnapKeyring_lock.set(this, void 0);
105
111
  this.type = SnapKeyring.type;
106
112
  __classPrivateFieldSet(this, _SnapKeyring_messenger, messenger, "f");
107
113
  __classPrivateFieldSet(this, _SnapKeyring_snapClient, new keyring_internal_snap_client_1.KeyringInternalSnapClient({ messenger }), "f");
@@ -111,6 +117,7 @@ class SnapKeyring {
111
117
  __classPrivateFieldSet(this, _SnapKeyring_callbacks, callbacks, "f");
112
118
  __classPrivateFieldSet(this, _SnapKeyring_isAnyAccountTypeAllowed, isAnyAccountTypeAllowed, "f");
113
119
  __classPrivateFieldSet(this, _SnapKeyring_selectedAccounts, new Map(), "f");
120
+ __classPrivateFieldSet(this, _SnapKeyring_lock, new async_mutex_1.Mutex(), "f");
114
121
  }
115
122
  /**
116
123
  * Handle a message from a Snap.
@@ -260,6 +267,84 @@ class SnapKeyring {
260
267
  },
261
268
  });
262
269
  }
270
+ /**
271
+ * Creates one or more new accounts according to the provided options.
272
+ *
273
+ * Deterministic account creation MUST be idempotent, meaning that for
274
+ * deterministic algorithms, like BIP-44, calling this method with the same
275
+ * options should always return the same accounts, even if the accounts
276
+ * already exist in the keyring.
277
+ *
278
+ * NOTE: If some accounts are not allowed (non-unique address, unsupported
279
+ * generic account), this method will skip their creation and ask the Snap
280
+ * to remove them from its state.
281
+ *
282
+ * @param snapId - Snap ID to create the account(s) for.
283
+ * @param options - Options describing how to create the account(s).
284
+ * @returns An array of the created account objects.
285
+ */
286
+ async createAccounts(snapId, options) {
287
+ return __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_withLock).call(this, async () => {
288
+ const client = new keyring_internal_snap_client_1.KeyringInternalSnapClient({
289
+ messenger: __classPrivateFieldGet(this, _SnapKeyring_messenger, "f"),
290
+ snapId,
291
+ });
292
+ // Keep track of address/account ID part of this batch, to avoid having duplicates.
293
+ const accountAddresses = new Set();
294
+ const accountIds = new Set();
295
+ const accounts = [];
296
+ const newAccounts = [];
297
+ const snapAccounts = await client.createAccounts(options);
298
+ try {
299
+ for (const snapAccount of snapAccounts) {
300
+ let account = (0, account_1.transformAccount)(snapAccount);
301
+ const address = normalizeAccountAddress(account);
302
+ // Check for idempotency.
303
+ const existingAccount = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getExistingAccount).call(this, snapId, account);
304
+ if (existingAccount) {
305
+ // NOTE: We re-use the account from the internal state to avoid having the Snap
306
+ // mutating the account object without updating the map.
307
+ account = existingAccount;
308
+ }
309
+ else {
310
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertAccountCanBeUsed).call(this, snapId, account);
311
+ // Also check for transient accounts that are not yet part of the keyring
312
+ // state.
313
+ if (accountAddresses.has(address) || accountIds.has(account.id)) {
314
+ throw new Error(`Account '${account.id}' is already part of this batch (same address or account ID)`);
315
+ }
316
+ accountAddresses.add(address);
317
+ accountIds.add(account.id);
318
+ // NOTE: This method does not rely on the `AccountCreated` event to add
319
+ // accounts to the keyring, so we have to add them to the state manually.
320
+ newAccounts.push(account);
321
+ }
322
+ // New AND existing accounts are returned to the caller no matter what.
323
+ accounts.push(account);
324
+ }
325
+ // We update the keyring state only if needed.
326
+ if (newAccounts.length > 0) {
327
+ for (const account of newAccounts) {
328
+ __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").set(account.id, { account, snapId });
329
+ }
330
+ // NOTE: We assume this will never fail, thus, we don't need to rollback the
331
+ // keyring state if anything goes wrong here.
332
+ await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").saveState();
333
+ }
334
+ return accounts;
335
+ }
336
+ catch (error) {
337
+ // Rollback Snap state.
338
+ for (const account of snapAccounts) {
339
+ // Make sure to only delete accounts that were not part of the keyring state.
340
+ if (!__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getExistingAccount).call(this, snapId, account)) {
341
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_deleteAccount).call(this, snapId, account);
342
+ }
343
+ }
344
+ throw error;
345
+ }
346
+ });
347
+ }
263
348
  /**
264
349
  * Checks if a Snap ID is known from the keyring.
265
350
  *
@@ -540,8 +625,57 @@ class SnapKeyring {
540
625
  }
541
626
  }
542
627
  exports.SnapKeyring = SnapKeyring;
543
- _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_instances = new WeakSet(), _SnapKeyring_isMinimumPlatformVersion = function _SnapKeyring_isMinimumPlatformVersion(snapId, platformVersion) {
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 =
629
+ /**
630
+ * Execute an operation behind a lock.
631
+ *
632
+ * @param callback - A function that performs the operation.
633
+ * @returns The result of the callback.
634
+ */
635
+ async function _SnapKeyring_withLock(callback) {
636
+ return __classPrivateFieldGet(this, _SnapKeyring_lock, "f").runExclusive(callback);
637
+ }, _SnapKeyring_isMinimumPlatformVersion = function _SnapKeyring_isMinimumPlatformVersion(snapId, platformVersion) {
544
638
  return __classPrivateFieldGet(this, _SnapKeyring_messenger, "f").call('SnapController:isMinimumPlatformVersion', snapId, platformVersion);
639
+ }, _SnapKeyring_assertAccountCanBeUsed =
640
+ /**
641
+ * Asserts that an account can be used within the Snap keyring. (e.g. generic accounts, unique
642
+ * addresses, etc...).
643
+ *
644
+ * @param snapId - The account's Snap ID.
645
+ * @param account - The account to check.
646
+ * @throws If the account cannot be used.
647
+ */
648
+ async function _SnapKeyring_assertAccountCanBeUsed(snapId, account) {
649
+ const address = normalizeAccountAddress(account);
650
+ // The `AnyAccountType.Account` generic account type is allowed only during
651
+ // development, so we check whether it's allowed before continuing.
652
+ if (!__classPrivateFieldGet(this, _SnapKeyring_isAnyAccountTypeAllowed, "f") &&
653
+ account.type === keyring_api_1.AnyAccountType.Account) {
654
+ throw new Error(`Cannot create generic account '${account.id}'`);
655
+ }
656
+ // A Snap could try to create an account with a different address but with
657
+ // an existing ID, so the above test only is not enough.
658
+ if (__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").has(snapId, account.id)) {
659
+ throw new Error(`Account '${account.id}' already exists`);
660
+ }
661
+ // The UI still uses the account address to identify accounts, so we need
662
+ // to block the creation of duplicate accounts for now to prevent accounts
663
+ // from being overwritten.
664
+ if (await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").addressExists(address)) {
665
+ throw new Error(`Account address '${address}' already exists`);
666
+ }
667
+ }, _SnapKeyring_getExistingAccount = function _SnapKeyring_getExistingAccount(snapId, account) {
668
+ 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.
671
+ // 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;
677
+ }
678
+ return undefined; // Not a known account.
545
679
  }, _SnapKeyring_getInternalOptions = function _SnapKeyring_getInternalOptions(snapId, correlationId) {
546
680
  if (correlationId) {
547
681
  // We still need to check if the correlation ID is valid and associated to
@@ -577,32 +711,12 @@ async function _SnapKeyring_handleAccountCreated(snapId, message) {
577
711
  // Potentially migrate the account.
578
712
  const account = (0, account_1.transformAccount)(newAccountFromEvent);
579
713
  const address = normalizeAccountAddress(account);
580
- // The `AnyAccountType.Account` generic account type is allowed only during
581
- // development, so we check whether it's allowed before continuing.
582
- if (!__classPrivateFieldGet(this, _SnapKeyring_isAnyAccountTypeAllowed, "f") &&
583
- account.type === keyring_api_1.AnyAccountType.Account) {
584
- throw new Error(`Cannot create generic account '${account.id}'`);
585
- }
586
- // This is idempotent, so we need to check whether the account already exists
587
- // and that the right Snap is trying to "create" it again.
588
- const accountEntry = __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").get(snapId, account.id);
589
- if (accountEntry &&
590
- normalizeAccountAddress(accountEntry.account) === address) {
591
- // NOTE: We are not checking account object equality here. If a Snap
592
- // re-send this event with different account data, we will ignore it.
714
+ if (__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getExistingAccount).call(this, snapId, account)) {
715
+ // If the account already exists, we skip it.
593
716
  return null;
594
717
  }
595
- // The UI still uses the account address to identify accounts, so we need
596
- // to block the creation of duplicate accounts for now to prevent accounts
597
- // from being overwritten.
598
- if (await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").addressExists(address)) {
599
- throw new Error(`Account address '${address}' already exists`);
600
- }
601
- // A Snap could try to create an account with a different address but with
602
- // an existing ID, so the above test only is not enough.
603
- if (__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").has(snapId, account.id)) {
604
- throw new Error(`Account '${account.id}' already exists`);
605
- }
718
+ // Make sure this new account is valid.
719
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertAccountCanBeUsed).call(this, snapId, account);
606
720
  // A deferred promise that will be resolved once the Snap keyring has saved
607
721
  // its internal state.
608
722
  // This part of the flow is run asynchronously, so we have no other way of