@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 +16 -4
- package/dist/SnapKeyring.cjs +140 -26
- package/dist/SnapKeyring.cjs.map +1 -1
- package/dist/SnapKeyring.d.cts +18 -1
- package/dist/SnapKeyring.d.cts.map +1 -1
- package/dist/SnapKeyring.d.mts +18 -1
- package/dist/SnapKeyring.d.mts.map +1 -1
- package/dist/SnapKeyring.mjs +140 -26
- package/dist/SnapKeyring.mjs.map +1 -1
- package/package.json +10 -9
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/
|
|
13
|
-
- Bump `@metamask/
|
|
14
|
-
- Bump `@metamask/
|
|
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@
|
|
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
|
package/dist/SnapKeyring.cjs
CHANGED
|
@@ -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(),
|
|
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
|
-
|
|
581
|
-
|
|
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
|
-
//
|
|
596
|
-
|
|
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
|