@metamask-previews/eth-snap-keyring 9.0.0-9c70d70 → 10.0.0-41bce29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [10.0.0]
11
+
12
+ ### Changed
13
+
14
+ - **BREAKING:** Use `CaipAccountId` for `ResolvedAccountAddress.address` ([#186](https://github.com/MetaMask/accounts/pull/186))
15
+ - This was missing from SIP-26, but we expect this address to be CAIP-10 compliant.
16
+ - Bump `@metamask/keyring-api` from `^16.1.0` to `^17.0.0` ([#192](https://github.com/MetaMask/accounts/pull/192))
17
+ - Rename `ControllerMessenger` to `Messenger` ([#185](https://github.com/MetaMask/accounts/pull/185))
18
+ - Bump `@metamask/snaps-controllers` dependency from `^9.10.0` to `^9.18.0` ([#177](https://github.com/MetaMask/accounts/pull/177))
19
+ - Bump `@metamask/snaps-sdk` dependency from `^6.7.0` to `^6.16.0` ([#177](https://github.com/MetaMask/accounts/pull/177))
20
+ - Bump `@metamask/snaps-utils` dependency from `^8.3.0` to `^8.9.1` ([#177](https://github.com/MetaMask/accounts/pull/177))
21
+
22
+ ## [9.1.1]
23
+
24
+ ### Changed
25
+
26
+ - Bump `@metamask/keyring-api` from `^16.0.0` to `^16.1.0` ([#176](https://github.com/MetaMask/accounts/pull/176))
27
+
28
+ ## [9.1.0]
29
+
30
+ ### Added
31
+
32
+ - Add `submitRequest` and `resolveAccountAddress` methods ([#157](https://github.com/MetaMask/accounts/pull/157))
33
+ - Those new methods are required by the new signing request routing system (see SIP-26).
34
+
35
+ ### Changed
36
+
37
+ - Bump `@metamask/keyring-api` from `^15.0.0` to `^16.0.0` ([#172](https://github.com/MetaMask/accounts/pull/172))
38
+ - Bump `@metamask/utils` from `^11.0.1` to `^11.1.0` ([#167](https://github.com/MetaMask/accounts/pull/167))
39
+
40
+ ### Fixed
41
+
42
+ - Fix async Snap request leak ([#171](https://github.com/MetaMask/accounts/pull/171))
43
+ - There was a leak with the deferred promises when an asynchronous request was not allowed.
44
+
10
45
  ## [9.0.0]
11
46
 
12
47
  ### Added
@@ -429,7 +464,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
429
464
 
430
465
  - Initial release.
431
466
 
432
- [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@9.0.0...HEAD
467
+ [Unreleased]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@10.0.0...HEAD
468
+ [10.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@9.1.1...@metamask/eth-snap-keyring@10.0.0
469
+ [9.1.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@9.1.0...@metamask/eth-snap-keyring@9.1.1
470
+ [9.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@9.0.0...@metamask/eth-snap-keyring@9.1.0
433
471
  [9.0.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@8.1.1...@metamask/eth-snap-keyring@9.0.0
434
472
  [8.1.1]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@8.1.0...@metamask/eth-snap-keyring@8.1.1
435
473
  [8.1.0]: https://github.com/MetaMask/accounts/compare/@metamask/eth-snap-keyring@8.0.0...@metamask/eth-snap-keyring@8.1.0
@@ -151,6 +151,17 @@ class SnapIdMap {
151
151
  has(snapId, key) {
152
152
  return this.get(snapId, key) !== undefined;
153
153
  }
154
+ /**
155
+ * Checks if a snap ID exists in the map.
156
+ *
157
+ * @param snapId - Snap ID present in the value to check.
158
+ * @returns `true` if the snap ID is present in the map, `false` otherwise.
159
+ */
160
+ hasSnapId(snapId) {
161
+ // We could use a reverse-mapping to map Snap ID to their actual key too, but
162
+ // for now, this will do the trick.
163
+ return [...__classPrivateFieldGet(this, _SnapIdMap_map, "f").values()].some((value) => value.snapId === snapId);
164
+ }
154
165
  /**
155
166
  * Deletes a key from the map.
156
167
  *
@@ -1 +1 @@
1
- {"version":3,"file":"SnapIdMap.cjs","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAEA,iEAA0D;AAE1D;;GAEG;AACH,MAAa,kBAAmB,SAAQ,KAAK;IAW3C;;;;;OAKG;IACH,YAAY,MAAc,EAAE,GAAW;QACrC,KAAK,CAAC,SAAS,MAAM,4BAA4B,GAAG,GAAG,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAvBD,gDAuBC;AAED;;;;;;GAMG;AACH,MAAa,SAAS;IAGpB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,QAA6C;QAlBhD,iCAAgC;QAmBvC,uBAAA,IAAI,kBAAQ,IAAI,uCAAkB,CAAC,QAAQ,CAAC,MAAA,CAAC;IAC/C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ;QACN,OAAO,uBAAA,IAAI,sBAAK,CAAC,QAAQ,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,UAAU,CACf,GAA0B;QAE1B,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,GAAG,CAAC,MAAc,EAAE,GAAW;QAC7B,MAAM,KAAK,GAAG,uBAAA,IAAI,sBAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,OAAO,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,GAAG,CAAC,MAAc,EAAE,GAAW;QAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,SAAS,CAAC;IAC7C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,MAAc,EAAE,GAAW;QAChC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,uBAAA,IAAI,sBAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,4CAA4C;IAC5C;;;;;;;;;;;;;;;;;OAiBG;IACH,2CAA2C;IAC3C,GAAG,CAAC,GAAW,EAAE,KAAY;QAC3B,uEAAuE;QACvE,sEAAsE;QACtE,sCAAsC;QACtC,IAAI,uBAAA,IAAI,sBAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClD,CAAC;QACD,uBAAA,IAAI,sBAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM;QACJ,OAAO,uBAAA,IAAI,sBAAK,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,IAAI,IAAI;QACN,OAAO,uBAAA,IAAI,sBAAK,CAAC,IAAI,CAAC;IACxB,CAAC;CACF;AAhND,8BAgNC","sourcesContent":["import type { SnapId } from '@metamask/snaps-sdk';\n\nimport { CaseInsensitiveMap } from './CaseInsensitiveMap';\n\n/**\n * Error thrown when an invalid Snap ID is encountered.\n */\nexport class InvalidSnapIdError extends Error {\n /**\n * The ID of the Snap that caused the error.\n */\n snapId: SnapId;\n\n /**\n * The key of the element that caused the error.\n */\n key: string;\n\n /**\n * Creates an instance of `InvalidSnapIdError`.\n *\n * @param snapId - The invalid Snap ID.\n * @param key - The key associated with the invalid Snap ID.\n */\n constructor(snapId: SnapId, key: string) {\n super(`Snap \"${snapId}\" is not allowed to set \"${key}\"`);\n this.name = 'InvalidSnapIdError';\n this.snapId = snapId;\n this.key = key;\n }\n}\n\n/**\n * A map that associates a string key with a value that has a `snapId`\n * property. Note that the key is case-insensitive.\n *\n * The `snapId` property is used to ensure that only the Snap that added an\n * item to the map can modify or delete it.\n */\nexport class SnapIdMap<Value extends { snapId: SnapId }> {\n readonly #map: CaseInsensitiveMap<Value>;\n\n /**\n * Creates a new `SnapIdMap` object.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * ```\n *\n * @param iterable - An iterable object whose elements are key-value pairs.\n * Each key-value pair will be added to the new map.\n */\n constructor(iterable?: Iterable<readonly [string, Value]>) {\n this.#map = new CaseInsensitiveMap(iterable);\n }\n\n /**\n * Returns a plain object with the same key-value pairs as this map.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * map.toObject();\n * // Returns\n * // {\n * // foo: { snapId: '1', name: 'foo' },\n * // bar: { snapId: '1', name: 'bar' },\n * // }\n * ```\n *\n * @returns A plain object with the same key-value pairs as this map.\n */\n toObject(): Record<string, Value> {\n return this.#map.toObject();\n }\n\n /**\n * Returns a new `SnapIdMap` object from an plain object.\n *\n * Example:\n *\n * ```ts\n * const obj = {\n * foo: { snapId: '1', name: 'foo' },\n * bar: { snapId: '1', name: 'bar' },\n * };\n * const map = SnapIdMap.fromObject(obj);\n * ```\n *\n * @param obj - A plain object whose elements will be added to the new map.\n * @returns A new `SnapIdMap` containing the elements of the given object.\n */\n static fromObject<Value extends { snapId: SnapId }>(\n obj: Record<string, Value>,\n ): SnapIdMap<Value> {\n return new SnapIdMap(Object.entries(obj));\n }\n\n /**\n * Gets a value from the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `undefined`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.get('1', 'foo'); // Returns { snapId: '1', name: 'foo' }\n * map.get('2', 'foo'); // Returns `undefined`\n * map.get('1', 'bar'); // Returns `undefined`\n * ```\n *\n * @param snapId - Snap ID present in the value to get.\n * @param key - Key of the element to get.\n * @returns The value associated with the given key and Snap ID.\n */\n get(snapId: SnapId, key: string): Value | undefined {\n const value = this.#map.get(key);\n return value?.snapId === snapId ? value : undefined;\n }\n\n /**\n * Checks if a key is present in the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `false`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.has('1', 'foo'); // Returns `true`\n * map.has('2', 'foo'); // Returns `false`\n * map.has('1', 'bar'); // Returns `false`\n * ```\n *\n * @param snapId - Snap ID present in the value to check.\n * @param key - Key of the element to check.\n * @returns `true` if the key is present in the map and the Snap ID of the\n * value is equal to the given Snap ID, `false` otherwise.\n */\n has(snapId: SnapId, key: string): boolean {\n return this.get(snapId, key) !== undefined;\n }\n\n /**\n * Deletes a key from the map.\n *\n * If the given key is not present in the map or the Snap IDs don't match,\n * returns `false` and does nothing.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.delete('2', 'foo'); // Returns `false`\n * map.delete('1', 'bar'); // Returns `false`\n * map.delete('1', 'foo'); // Returns `true`\n * ```\n *\n * @param snapId - Snap ID present in the value to delete.\n * @param key - Key of the element to delete.\n * @returns `true` if the key was present in the map and the Snap ID of the\n * value was equal to the given Snap ID, `false` otherwise.\n */\n delete(snapId: SnapId, key: string): boolean {\n return this.has(snapId, key) && this.#map.delete(key);\n }\n\n /* eslint-disable jsdoc/check-indentation */\n /**\n * Adds or updates a key-value pair in the map.\n *\n * Note that this method has a different behavior from the `Map.set`.\n *\n * - If the given key is not already present in the map, this method adds the\n * key-value pair to the map.\n *\n * - If the given key is already present in the map and the Snap IDs match,\n * this method updates the value associated with the key.\n *\n * - However, if the given key is already present in the map but the Snap IDs\n * do not match, this method throws an error.\n *\n * @param key - Key of the element to add or update.\n * @param value - Value of the element to add or update.\n * @returns The map itself.\n */\n /* eslint-enable jsdoc/check-indentation */\n set(key: string, value: Value): this {\n // If the key is present in the map but isn't associated with the given\n // Snap ID, it means that the item was added to the map by a different\n // Snap. In this case, throw an error.\n if (this.#map.has(key) && !this.has(value.snapId, key)) {\n throw new InvalidSnapIdError(value.snapId, key);\n }\n this.#map.set(key, value);\n return this;\n }\n\n /**\n * Returns an iterable of the values in the map.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap([\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ]);\n * const values = [...map.values()];\n * // Returns\n * // [\n * // { snapId: '1', name: 'foo' },\n * // { snapId: '1', name: 'bar' },\n * // ]\n * ```\n *\n * @returns An iterable of the values in the map.\n */\n values(): IterableIterator<Value> {\n return this.#map.values();\n }\n\n /**\n * Returns the number of key-value pairs in the map.\n *\n * @returns The number of key-value pairs in the map.\n */\n get size(): number {\n return this.#map.size;\n }\n}\n"]}
1
+ {"version":3,"file":"SnapIdMap.cjs","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SnapId } from '@metamask/snaps-sdk';\n\nimport { CaseInsensitiveMap } from './CaseInsensitiveMap';\n\n/**\n * Error thrown when an invalid Snap ID is encountered.\n */\nexport class InvalidSnapIdError extends Error {\n /**\n * The ID of the Snap that caused the error.\n */\n snapId: SnapId;\n\n /**\n * The key of the element that caused the error.\n */\n key: string;\n\n /**\n * Creates an instance of `InvalidSnapIdError`.\n *\n * @param snapId - The invalid Snap ID.\n * @param key - The key associated with the invalid Snap ID.\n */\n constructor(snapId: SnapId, key: string) {\n super(`Snap \"${snapId}\" is not allowed to set \"${key}\"`);\n this.name = 'InvalidSnapIdError';\n this.snapId = snapId;\n this.key = key;\n }\n}\n\n/**\n * A map that associates a string key with a value that has a `snapId`\n * property. Note that the key is case-insensitive.\n *\n * The `snapId` property is used to ensure that only the Snap that added an\n * item to the map can modify or delete it.\n */\nexport class SnapIdMap<Value extends { snapId: SnapId }> {\n readonly #map: CaseInsensitiveMap<Value>;\n\n /**\n * Creates a new `SnapIdMap` object.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * ```\n *\n * @param iterable - An iterable object whose elements are key-value pairs.\n * Each key-value pair will be added to the new map.\n */\n constructor(iterable?: Iterable<readonly [string, Value]>) {\n this.#map = new CaseInsensitiveMap(iterable);\n }\n\n /**\n * Returns a plain object with the same key-value pairs as this map.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * map.toObject();\n * // Returns\n * // {\n * // foo: { snapId: '1', name: 'foo' },\n * // bar: { snapId: '1', name: 'bar' },\n * // }\n * ```\n *\n * @returns A plain object with the same key-value pairs as this map.\n */\n toObject(): Record<string, Value> {\n return this.#map.toObject();\n }\n\n /**\n * Returns a new `SnapIdMap` object from an plain object.\n *\n * Example:\n *\n * ```ts\n * const obj = {\n * foo: { snapId: '1', name: 'foo' },\n * bar: { snapId: '1', name: 'bar' },\n * };\n * const map = SnapIdMap.fromObject(obj);\n * ```\n *\n * @param obj - A plain object whose elements will be added to the new map.\n * @returns A new `SnapIdMap` containing the elements of the given object.\n */\n static fromObject<Value extends { snapId: SnapId }>(\n obj: Record<string, Value>,\n ): SnapIdMap<Value> {\n return new SnapIdMap(Object.entries(obj));\n }\n\n /**\n * Gets a value from the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `undefined`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.get('1', 'foo'); // Returns { snapId: '1', name: 'foo' }\n * map.get('2', 'foo'); // Returns `undefined`\n * map.get('1', 'bar'); // Returns `undefined`\n * ```\n *\n * @param snapId - Snap ID present in the value to get.\n * @param key - Key of the element to get.\n * @returns The value associated with the given key and Snap ID.\n */\n get(snapId: SnapId, key: string): Value | undefined {\n const value = this.#map.get(key);\n return value?.snapId === snapId ? value : undefined;\n }\n\n /**\n * Checks if a key is present in the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `false`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.has('1', 'foo'); // Returns `true`\n * map.has('2', 'foo'); // Returns `false`\n * map.has('1', 'bar'); // Returns `false`\n * ```\n *\n * @param snapId - Snap ID present in the value to check.\n * @param key - Key of the element to check.\n * @returns `true` if the key is present in the map and the Snap ID of the\n * value is equal to the given Snap ID, `false` otherwise.\n */\n has(snapId: SnapId, key: string): boolean {\n return this.get(snapId, key) !== undefined;\n }\n\n /**\n * Checks if a snap ID exists in the map.\n *\n * @param snapId - Snap ID present in the value to check.\n * @returns `true` if the snap ID is present in the map, `false` otherwise.\n */\n hasSnapId(snapId: SnapId): boolean {\n // We could use a reverse-mapping to map Snap ID to their actual key too, but\n // for now, this will do the trick.\n return [...this.#map.values()].some((value) => value.snapId === snapId);\n }\n\n /**\n * Deletes a key from the map.\n *\n * If the given key is not present in the map or the Snap IDs don't match,\n * returns `false` and does nothing.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.delete('2', 'foo'); // Returns `false`\n * map.delete('1', 'bar'); // Returns `false`\n * map.delete('1', 'foo'); // Returns `true`\n * ```\n *\n * @param snapId - Snap ID present in the value to delete.\n * @param key - Key of the element to delete.\n * @returns `true` if the key was present in the map and the Snap ID of the\n * value was equal to the given Snap ID, `false` otherwise.\n */\n delete(snapId: SnapId, key: string): boolean {\n return this.has(snapId, key) && this.#map.delete(key);\n }\n\n /* eslint-disable jsdoc/check-indentation */\n /**\n * Adds or updates a key-value pair in the map.\n *\n * Note that this method has a different behavior from the `Map.set`.\n *\n * - If the given key is not already present in the map, this method adds the\n * key-value pair to the map.\n *\n * - If the given key is already present in the map and the Snap IDs match,\n * this method updates the value associated with the key.\n *\n * - However, if the given key is already present in the map but the Snap IDs\n * do not match, this method throws an error.\n *\n * @param key - Key of the element to add or update.\n * @param value - Value of the element to add or update.\n * @returns The map itself.\n */\n /* eslint-enable jsdoc/check-indentation */\n set(key: string, value: Value): this {\n // If the key is present in the map but isn't associated with the given\n // Snap ID, it means that the item was added to the map by a different\n // Snap. In this case, throw an error.\n if (this.#map.has(key) && !this.has(value.snapId, key)) {\n throw new InvalidSnapIdError(value.snapId, key);\n }\n this.#map.set(key, value);\n return this;\n }\n\n /**\n * Returns an iterable of the values in the map.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap([\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ]);\n * const values = [...map.values()];\n * // Returns\n * // [\n * // { snapId: '1', name: 'foo' },\n * // { snapId: '1', name: 'bar' },\n * // ]\n * ```\n *\n * @returns An iterable of the values in the map.\n */\n values(): IterableIterator<Value> {\n return this.#map.values();\n }\n\n /**\n * Returns the number of key-value pairs in the map.\n *\n * @returns The number of key-value pairs in the map.\n */\n get size(): number {\n return this.#map.size;\n }\n}\n"]}
@@ -131,6 +131,13 @@ export declare class SnapIdMap<Value extends {
131
131
  * value is equal to the given Snap ID, `false` otherwise.
132
132
  */
133
133
  has(snapId: SnapId, key: string): boolean;
134
+ /**
135
+ * Checks if a snap ID exists in the map.
136
+ *
137
+ * @param snapId - Snap ID present in the value to check.
138
+ * @returns `true` if the snap ID is present in the map, `false` otherwise.
139
+ */
140
+ hasSnapId(snapId: SnapId): boolean;
134
141
  /**
135
142
  * Deletes a key from the map.
136
143
  *
@@ -1 +1 @@
1
- {"version":3,"file":"SnapIdMap.d.cts","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,4BAA4B;AAIlD;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;OAKG;gBACS,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAMxC;AAED;;;;;;GAMG;AACH,qBAAa,SAAS,CAAC,KAAK,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;;IAGrD;;;;;;;;;;;;;;;OAeG;gBACS,QAAQ,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAIzD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAIjC;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,UAAU,CAAC,KAAK,SAAS;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAChD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACzB,SAAS,CAAC,KAAK,CAAC;IAInB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAKnD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAK5C;;;;;;;;;;;;;;;;;OAiBG;IAEH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAWpC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,IAAI,gBAAgB,CAAC,KAAK,CAAC;IAIjC;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
1
+ {"version":3,"file":"SnapIdMap.d.cts","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,4BAA4B;AAIlD;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;OAKG;gBACS,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAMxC;AAED;;;;;;GAMG;AACH,qBAAa,SAAS,CAAC,KAAK,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;;IAGrD;;;;;;;;;;;;;;;OAeG;gBACS,QAAQ,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAIzD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAIjC;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,UAAU,CAAC,KAAK,SAAS;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAChD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACzB,SAAS,CAAC,KAAK,CAAC;IAInB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAKnD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAMlC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAK5C;;;;;;;;;;;;;;;;;OAiBG;IAEH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAWpC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,IAAI,gBAAgB,CAAC,KAAK,CAAC;IAIjC;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -131,6 +131,13 @@ export declare class SnapIdMap<Value extends {
131
131
  * value is equal to the given Snap ID, `false` otherwise.
132
132
  */
133
133
  has(snapId: SnapId, key: string): boolean;
134
+ /**
135
+ * Checks if a snap ID exists in the map.
136
+ *
137
+ * @param snapId - Snap ID present in the value to check.
138
+ * @returns `true` if the snap ID is present in the map, `false` otherwise.
139
+ */
140
+ hasSnapId(snapId: SnapId): boolean;
134
141
  /**
135
142
  * Deletes a key from the map.
136
143
  *
@@ -1 +1 @@
1
- {"version":3,"file":"SnapIdMap.d.mts","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,4BAA4B;AAIlD;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;OAKG;gBACS,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAMxC;AAED;;;;;;GAMG;AACH,qBAAa,SAAS,CAAC,KAAK,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;;IAGrD;;;;;;;;;;;;;;;OAeG;gBACS,QAAQ,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAIzD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAIjC;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,UAAU,CAAC,KAAK,SAAS;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAChD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACzB,SAAS,CAAC,KAAK,CAAC;IAInB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAKnD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAK5C;;;;;;;;;;;;;;;;;OAiBG;IAEH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAWpC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,IAAI,gBAAgB,CAAC,KAAK,CAAC;IAIjC;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
1
+ {"version":3,"file":"SnapIdMap.d.mts","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,4BAA4B;AAIlD;;GAEG;AACH,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;OAKG;gBACS,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;CAMxC;AAED;;;;;;GAMG;AACH,qBAAa,SAAS,CAAC,KAAK,SAAS;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;;IAGrD;;;;;;;;;;;;;;;OAeG;gBACS,QAAQ,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAIzD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;IAIjC;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,UAAU,CAAC,KAAK,SAAS;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,EAChD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACzB,SAAS,CAAC,KAAK,CAAC;IAInB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS;IAKnD;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzC;;;;;OAKG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAMlC;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO;IAK5C;;;;;;;;;;;;;;;;;OAiBG;IAEH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI;IAWpC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM,IAAI,gBAAgB,CAAC,KAAK,CAAC;IAIjC;;;;OAIG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -147,6 +147,17 @@ export class SnapIdMap {
147
147
  has(snapId, key) {
148
148
  return this.get(snapId, key) !== undefined;
149
149
  }
150
+ /**
151
+ * Checks if a snap ID exists in the map.
152
+ *
153
+ * @param snapId - Snap ID present in the value to check.
154
+ * @returns `true` if the snap ID is present in the map, `false` otherwise.
155
+ */
156
+ hasSnapId(snapId) {
157
+ // We could use a reverse-mapping to map Snap ID to their actual key too, but
158
+ // for now, this will do the trick.
159
+ return [...__classPrivateFieldGet(this, _SnapIdMap_map, "f").values()].some((value) => value.snapId === snapId);
160
+ }
150
161
  /**
151
162
  * Deletes a key from the map.
152
163
  *
@@ -1 +1 @@
1
- {"version":3,"file":"SnapIdMap.mjs","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,OAAO,EAAE,kBAAkB,EAAE,iCAA6B;AAE1D;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAW3C;;;;;OAKG;IACH,YAAY,MAAc,EAAE,GAAW;QACrC,KAAK,CAAC,SAAS,MAAM,4BAA4B,GAAG,GAAG,CAAC,CAAC;QACzD,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;IACjB,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,OAAO,SAAS;IAGpB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,QAA6C;QAlBhD,iCAAgC;QAmBvC,uBAAA,IAAI,kBAAQ,IAAI,kBAAkB,CAAC,QAAQ,CAAC,MAAA,CAAC;IAC/C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ;QACN,OAAO,uBAAA,IAAI,sBAAK,CAAC,QAAQ,EAAE,CAAC;IAC9B,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,UAAU,CACf,GAA0B;QAE1B,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,GAAG,CAAC,MAAc,EAAE,GAAW;QAC7B,MAAM,KAAK,GAAG,uBAAA,IAAI,sBAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,OAAO,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,GAAG,CAAC,MAAc,EAAE,GAAW;QAC7B,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,SAAS,CAAC;IAC7C,CAAC;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,MAAc,EAAE,GAAW;QAChC,OAAO,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,uBAAA,IAAI,sBAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACxD,CAAC;IAED,4CAA4C;IAC5C;;;;;;;;;;;;;;;;;OAiBG;IACH,2CAA2C;IAC3C,GAAG,CAAC,GAAW,EAAE,KAAY;QAC3B,uEAAuE;QACvE,sEAAsE;QACtE,sCAAsC;QACtC,IAAI,uBAAA,IAAI,sBAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,CAAC;YACvD,MAAM,IAAI,kBAAkB,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClD,CAAC;QACD,uBAAA,IAAI,sBAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,MAAM;QACJ,OAAO,uBAAA,IAAI,sBAAK,CAAC,MAAM,EAAE,CAAC;IAC5B,CAAC;IAED;;;;OAIG;IACH,IAAI,IAAI;QACN,OAAO,uBAAA,IAAI,sBAAK,CAAC,IAAI,CAAC;IACxB,CAAC;CACF","sourcesContent":["import type { SnapId } from '@metamask/snaps-sdk';\n\nimport { CaseInsensitiveMap } from './CaseInsensitiveMap';\n\n/**\n * Error thrown when an invalid Snap ID is encountered.\n */\nexport class InvalidSnapIdError extends Error {\n /**\n * The ID of the Snap that caused the error.\n */\n snapId: SnapId;\n\n /**\n * The key of the element that caused the error.\n */\n key: string;\n\n /**\n * Creates an instance of `InvalidSnapIdError`.\n *\n * @param snapId - The invalid Snap ID.\n * @param key - The key associated with the invalid Snap ID.\n */\n constructor(snapId: SnapId, key: string) {\n super(`Snap \"${snapId}\" is not allowed to set \"${key}\"`);\n this.name = 'InvalidSnapIdError';\n this.snapId = snapId;\n this.key = key;\n }\n}\n\n/**\n * A map that associates a string key with a value that has a `snapId`\n * property. Note that the key is case-insensitive.\n *\n * The `snapId` property is used to ensure that only the Snap that added an\n * item to the map can modify or delete it.\n */\nexport class SnapIdMap<Value extends { snapId: SnapId }> {\n readonly #map: CaseInsensitiveMap<Value>;\n\n /**\n * Creates a new `SnapIdMap` object.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * ```\n *\n * @param iterable - An iterable object whose elements are key-value pairs.\n * Each key-value pair will be added to the new map.\n */\n constructor(iterable?: Iterable<readonly [string, Value]>) {\n this.#map = new CaseInsensitiveMap(iterable);\n }\n\n /**\n * Returns a plain object with the same key-value pairs as this map.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * map.toObject();\n * // Returns\n * // {\n * // foo: { snapId: '1', name: 'foo' },\n * // bar: { snapId: '1', name: 'bar' },\n * // }\n * ```\n *\n * @returns A plain object with the same key-value pairs as this map.\n */\n toObject(): Record<string, Value> {\n return this.#map.toObject();\n }\n\n /**\n * Returns a new `SnapIdMap` object from an plain object.\n *\n * Example:\n *\n * ```ts\n * const obj = {\n * foo: { snapId: '1', name: 'foo' },\n * bar: { snapId: '1', name: 'bar' },\n * };\n * const map = SnapIdMap.fromObject(obj);\n * ```\n *\n * @param obj - A plain object whose elements will be added to the new map.\n * @returns A new `SnapIdMap` containing the elements of the given object.\n */\n static fromObject<Value extends { snapId: SnapId }>(\n obj: Record<string, Value>,\n ): SnapIdMap<Value> {\n return new SnapIdMap(Object.entries(obj));\n }\n\n /**\n * Gets a value from the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `undefined`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.get('1', 'foo'); // Returns { snapId: '1', name: 'foo' }\n * map.get('2', 'foo'); // Returns `undefined`\n * map.get('1', 'bar'); // Returns `undefined`\n * ```\n *\n * @param snapId - Snap ID present in the value to get.\n * @param key - Key of the element to get.\n * @returns The value associated with the given key and Snap ID.\n */\n get(snapId: SnapId, key: string): Value | undefined {\n const value = this.#map.get(key);\n return value?.snapId === snapId ? value : undefined;\n }\n\n /**\n * Checks if a key is present in the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `false`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.has('1', 'foo'); // Returns `true`\n * map.has('2', 'foo'); // Returns `false`\n * map.has('1', 'bar'); // Returns `false`\n * ```\n *\n * @param snapId - Snap ID present in the value to check.\n * @param key - Key of the element to check.\n * @returns `true` if the key is present in the map and the Snap ID of the\n * value is equal to the given Snap ID, `false` otherwise.\n */\n has(snapId: SnapId, key: string): boolean {\n return this.get(snapId, key) !== undefined;\n }\n\n /**\n * Deletes a key from the map.\n *\n * If the given key is not present in the map or the Snap IDs don't match,\n * returns `false` and does nothing.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.delete('2', 'foo'); // Returns `false`\n * map.delete('1', 'bar'); // Returns `false`\n * map.delete('1', 'foo'); // Returns `true`\n * ```\n *\n * @param snapId - Snap ID present in the value to delete.\n * @param key - Key of the element to delete.\n * @returns `true` if the key was present in the map and the Snap ID of the\n * value was equal to the given Snap ID, `false` otherwise.\n */\n delete(snapId: SnapId, key: string): boolean {\n return this.has(snapId, key) && this.#map.delete(key);\n }\n\n /* eslint-disable jsdoc/check-indentation */\n /**\n * Adds or updates a key-value pair in the map.\n *\n * Note that this method has a different behavior from the `Map.set`.\n *\n * - If the given key is not already present in the map, this method adds the\n * key-value pair to the map.\n *\n * - If the given key is already present in the map and the Snap IDs match,\n * this method updates the value associated with the key.\n *\n * - However, if the given key is already present in the map but the Snap IDs\n * do not match, this method throws an error.\n *\n * @param key - Key of the element to add or update.\n * @param value - Value of the element to add or update.\n * @returns The map itself.\n */\n /* eslint-enable jsdoc/check-indentation */\n set(key: string, value: Value): this {\n // If the key is present in the map but isn't associated with the given\n // Snap ID, it means that the item was added to the map by a different\n // Snap. In this case, throw an error.\n if (this.#map.has(key) && !this.has(value.snapId, key)) {\n throw new InvalidSnapIdError(value.snapId, key);\n }\n this.#map.set(key, value);\n return this;\n }\n\n /**\n * Returns an iterable of the values in the map.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap([\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ]);\n * const values = [...map.values()];\n * // Returns\n * // [\n * // { snapId: '1', name: 'foo' },\n * // { snapId: '1', name: 'bar' },\n * // ]\n * ```\n *\n * @returns An iterable of the values in the map.\n */\n values(): IterableIterator<Value> {\n return this.#map.values();\n }\n\n /**\n * Returns the number of key-value pairs in the map.\n *\n * @returns The number of key-value pairs in the map.\n */\n get size(): number {\n return this.#map.size;\n }\n}\n"]}
1
+ {"version":3,"file":"SnapIdMap.mjs","sourceRoot":"","sources":["../src/SnapIdMap.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SnapId } from '@metamask/snaps-sdk';\n\nimport { CaseInsensitiveMap } from './CaseInsensitiveMap';\n\n/**\n * Error thrown when an invalid Snap ID is encountered.\n */\nexport class InvalidSnapIdError extends Error {\n /**\n * The ID of the Snap that caused the error.\n */\n snapId: SnapId;\n\n /**\n * The key of the element that caused the error.\n */\n key: string;\n\n /**\n * Creates an instance of `InvalidSnapIdError`.\n *\n * @param snapId - The invalid Snap ID.\n * @param key - The key associated with the invalid Snap ID.\n */\n constructor(snapId: SnapId, key: string) {\n super(`Snap \"${snapId}\" is not allowed to set \"${key}\"`);\n this.name = 'InvalidSnapIdError';\n this.snapId = snapId;\n this.key = key;\n }\n}\n\n/**\n * A map that associates a string key with a value that has a `snapId`\n * property. Note that the key is case-insensitive.\n *\n * The `snapId` property is used to ensure that only the Snap that added an\n * item to the map can modify or delete it.\n */\nexport class SnapIdMap<Value extends { snapId: SnapId }> {\n readonly #map: CaseInsensitiveMap<Value>;\n\n /**\n * Creates a new `SnapIdMap` object.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * ```\n *\n * @param iterable - An iterable object whose elements are key-value pairs.\n * Each key-value pair will be added to the new map.\n */\n constructor(iterable?: Iterable<readonly [string, Value]>) {\n this.#map = new CaseInsensitiveMap(iterable);\n }\n\n /**\n * Returns a plain object with the same key-value pairs as this map.\n *\n * Example:\n *\n * ```ts\n * const items = [\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ];\n * const map = new SnapIdMap(items);\n * map.toObject();\n * // Returns\n * // {\n * // foo: { snapId: '1', name: 'foo' },\n * // bar: { snapId: '1', name: 'bar' },\n * // }\n * ```\n *\n * @returns A plain object with the same key-value pairs as this map.\n */\n toObject(): Record<string, Value> {\n return this.#map.toObject();\n }\n\n /**\n * Returns a new `SnapIdMap` object from an plain object.\n *\n * Example:\n *\n * ```ts\n * const obj = {\n * foo: { snapId: '1', name: 'foo' },\n * bar: { snapId: '1', name: 'bar' },\n * };\n * const map = SnapIdMap.fromObject(obj);\n * ```\n *\n * @param obj - A plain object whose elements will be added to the new map.\n * @returns A new `SnapIdMap` containing the elements of the given object.\n */\n static fromObject<Value extends { snapId: SnapId }>(\n obj: Record<string, Value>,\n ): SnapIdMap<Value> {\n return new SnapIdMap(Object.entries(obj));\n }\n\n /**\n * Gets a value from the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `undefined`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.get('1', 'foo'); // Returns { snapId: '1', name: 'foo' }\n * map.get('2', 'foo'); // Returns `undefined`\n * map.get('1', 'bar'); // Returns `undefined`\n * ```\n *\n * @param snapId - Snap ID present in the value to get.\n * @param key - Key of the element to get.\n * @returns The value associated with the given key and Snap ID.\n */\n get(snapId: SnapId, key: string): Value | undefined {\n const value = this.#map.get(key);\n return value?.snapId === snapId ? value : undefined;\n }\n\n /**\n * Checks if a key is present in the map.\n *\n * If the given key is not present in the map or the Snap ID of the value is\n * different from the given Snap ID, returns `false`.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.has('1', 'foo'); // Returns `true`\n * map.has('2', 'foo'); // Returns `false`\n * map.has('1', 'bar'); // Returns `false`\n * ```\n *\n * @param snapId - Snap ID present in the value to check.\n * @param key - Key of the element to check.\n * @returns `true` if the key is present in the map and the Snap ID of the\n * value is equal to the given Snap ID, `false` otherwise.\n */\n has(snapId: SnapId, key: string): boolean {\n return this.get(snapId, key) !== undefined;\n }\n\n /**\n * Checks if a snap ID exists in the map.\n *\n * @param snapId - Snap ID present in the value to check.\n * @returns `true` if the snap ID is present in the map, `false` otherwise.\n */\n hasSnapId(snapId: SnapId): boolean {\n // We could use a reverse-mapping to map Snap ID to their actual key too, but\n // for now, this will do the trick.\n return [...this.#map.values()].some((value) => value.snapId === snapId);\n }\n\n /**\n * Deletes a key from the map.\n *\n * If the given key is not present in the map or the Snap IDs don't match,\n * returns `false` and does nothing.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap();\n * map.set('foo', { snapId: '1', name: 'foo' });\n * map.delete('2', 'foo'); // Returns `false`\n * map.delete('1', 'bar'); // Returns `false`\n * map.delete('1', 'foo'); // Returns `true`\n * ```\n *\n * @param snapId - Snap ID present in the value to delete.\n * @param key - Key of the element to delete.\n * @returns `true` if the key was present in the map and the Snap ID of the\n * value was equal to the given Snap ID, `false` otherwise.\n */\n delete(snapId: SnapId, key: string): boolean {\n return this.has(snapId, key) && this.#map.delete(key);\n }\n\n /* eslint-disable jsdoc/check-indentation */\n /**\n * Adds or updates a key-value pair in the map.\n *\n * Note that this method has a different behavior from the `Map.set`.\n *\n * - If the given key is not already present in the map, this method adds the\n * key-value pair to the map.\n *\n * - If the given key is already present in the map and the Snap IDs match,\n * this method updates the value associated with the key.\n *\n * - However, if the given key is already present in the map but the Snap IDs\n * do not match, this method throws an error.\n *\n * @param key - Key of the element to add or update.\n * @param value - Value of the element to add or update.\n * @returns The map itself.\n */\n /* eslint-enable jsdoc/check-indentation */\n set(key: string, value: Value): this {\n // If the key is present in the map but isn't associated with the given\n // Snap ID, it means that the item was added to the map by a different\n // Snap. In this case, throw an error.\n if (this.#map.has(key) && !this.has(value.snapId, key)) {\n throw new InvalidSnapIdError(value.snapId, key);\n }\n this.#map.set(key, value);\n return this;\n }\n\n /**\n * Returns an iterable of the values in the map.\n *\n * Example:\n *\n * ```ts\n * const map = new SnapIdMap([\n * ['foo', { snapId: '1', name: 'foo' }],\n * ['bar', { snapId: '1', name: 'bar' }],\n * ]);\n * const values = [...map.values()];\n * // Returns\n * // [\n * // { snapId: '1', name: 'foo' },\n * // { snapId: '1', name: 'bar' },\n * // ]\n * ```\n *\n * @returns An iterable of the values in the map.\n */\n values(): IterableIterator<Value> {\n return this.#map.values();\n }\n\n /**\n * Returns the number of key-value pairs in the map.\n *\n * @returns The number of key-value pairs in the map.\n */\n get size(): number {\n return this.#map.size;\n }\n}\n"]}
@@ -13,7 +13,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
13
13
  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");
14
14
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
15
15
  };
16
- var _SnapKeyring_instances, _SnapKeyring_messenger, _SnapKeyring_snapClient, _SnapKeyring_accounts, _SnapKeyring_requests, _SnapKeyring_callbacks, _SnapKeyring_handleAccountCreated, _SnapKeyring_handleAccountUpdated, _SnapKeyring_handleAccountDeleted, _SnapKeyring_handleRequestApproved, _SnapKeyring_handleRequestRejected, _SnapKeyring_rePublishAccountEvent, _SnapKeyring_handleAccountBalancesUpdated, _SnapKeyring_handleAccountAssetListUpdated, _SnapKeyring_handleAccountTransactionsUpdated, _SnapKeyring_submitRequest, _SnapKeyring_hasMethod, _SnapKeyring_createRequestPromise, _SnapKeyring_submitSnapRequest, _SnapKeyring_handleSyncResponse, _SnapKeyring_handleAsyncResponse, _SnapKeyring_sanitizeRedirectUrl, _SnapKeyring_validateRedirectUrl, _SnapKeyring_resolveAddress, _SnapKeyring_getSnap, _SnapKeyring_getSnapMetadata, _SnapKeyring_getSnapAllowedOrigins;
16
+ var _SnapKeyring_instances, _SnapKeyring_messenger, _SnapKeyring_snapClient, _SnapKeyring_accounts, _SnapKeyring_requests, _SnapKeyring_callbacks, _SnapKeyring_handleAccountCreated, _SnapKeyring_handleAccountUpdated, _SnapKeyring_handleAccountDeleted, _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_getSnap, _SnapKeyring_getSnapMetadata, _SnapKeyring_getSnapAllowedOrigins;
17
17
  Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.SnapKeyring = exports.SNAP_KEYRING_TYPE = void 0;
19
19
  const tx_1 = require("@ethereumjs/tx");
@@ -189,6 +189,58 @@ class SnapKeyring extends events_1.EventEmitter {
189
189
  .filter(({ snapId: accountSnapId }) => accountSnapId === snapId)
190
190
  .map(({ account }) => normalizeAccountAddress(account)));
191
191
  }
192
+ /**
193
+ * Checks if a Snap ID is known from the keyring.
194
+ *
195
+ * @param snapId - Snap ID.
196
+ * @returns `true` if the Snap ID is known, `false` otherwise.
197
+ */
198
+ hasSnapId(snapId) {
199
+ return __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").hasSnapId(snapId);
200
+ }
201
+ /**
202
+ * Resolve the Snap account's address associated from a signing request.
203
+ *
204
+ * @param snapId - Snap ID.
205
+ * @param scope - CAIP-2 chain ID.
206
+ * @param request - Signing request object.
207
+ * @throws An error if the Snap ID is not known from the keyring.
208
+ * @returns The resolved address if found, `null` otherwise.
209
+ */
210
+ async resolveAccountAddress(snapId, scope, request) {
211
+ // We do check that the incoming Snap ID is known by the keyring.
212
+ if (!this.hasSnapId(snapId)) {
213
+ throw new Error(`Unable to resolve account address: unknown Snap ID: ${snapId}`);
214
+ }
215
+ return await __classPrivateFieldGet(this, _SnapKeyring_snapClient, "f")
216
+ .withSnapId(snapId)
217
+ .resolveAccountAddress(scope, request);
218
+ }
219
+ /**
220
+ * Submit a request to a Snap from an account ID.
221
+ *
222
+ * This request cannot be an asynchronous keyring request.
223
+ *
224
+ * @param opts - Request options.
225
+ * @param opts.account - Account ID.
226
+ * @param opts.method - Method to call.
227
+ * @param opts.params - Method parameters.
228
+ * @param opts.scope - Selected chain ID (CAIP-2).
229
+ * @returns Promise that resolves to the result of the method call.
230
+ */
231
+ async submitRequest({ account: accountId, method, params, scope, }) {
232
+ const { account, snapId } = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getAccount).call(this, accountId);
233
+ return await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_submitSnapRequest).call(this, {
234
+ snapId,
235
+ account,
236
+ method: method,
237
+ params,
238
+ scope,
239
+ // For non-EVM (in the context of the multichain API and SIP-26), we enforce responses
240
+ // to be synchronous.
241
+ noPending: true,
242
+ });
243
+ }
192
244
  /**
193
245
  * Sign a transaction.
194
246
  *
@@ -209,7 +261,7 @@ class SnapKeyring extends events_1.EventEmitter {
209
261
  address,
210
262
  method: keyring_api_1.EthMethod.SignTransaction,
211
263
  params: [tx],
212
- chainId: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, `${chainId}`),
264
+ scope: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, `${chainId}`),
213
265
  });
214
266
  // ! It's *** CRITICAL *** that we mask the signature here, otherwise the
215
267
  // ! Snap could overwrite the transaction.
@@ -252,7 +304,7 @@ class SnapKeyring extends events_1.EventEmitter {
252
304
  ...(chainId === undefined
253
305
  ? {}
254
306
  : {
255
- chainId: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, `${chainId}`),
307
+ scope: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, `${chainId}`),
256
308
  }),
257
309
  }), keyring_api_1.EthBytesStruct);
258
310
  }
@@ -302,7 +354,7 @@ class SnapKeyring extends events_1.EventEmitter {
302
354
  params: (0, util_1.toJson)(transactions),
303
355
  noPending: true,
304
356
  // We assume the chain ID is already well formatted
305
- chainId: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, context.chainId),
357
+ scope: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, context.chainId),
306
358
  }), keyring_api_1.EthBaseUserOperationStruct);
307
359
  }
308
360
  /**
@@ -321,7 +373,7 @@ class SnapKeyring extends events_1.EventEmitter {
321
373
  params: (0, util_1.toJson)([userOp]),
322
374
  noPending: true,
323
375
  // We assume the chain ID is already well formatted
324
- chainId: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, context.chainId),
376
+ scope: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, context.chainId),
325
377
  }), keyring_api_1.EthUserOperationPatchStruct);
326
378
  }
327
379
  /**
@@ -338,7 +390,7 @@ class SnapKeyring extends events_1.EventEmitter {
338
390
  method: keyring_api_1.EthMethod.SignUserOperation,
339
391
  params: (0, util_1.toJson)([userOp]),
340
392
  // We assume the chain ID is already well formatted
341
- chainId: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, context.chainId),
393
+ scope: (0, utils_1.toCaipChainId)(utils_1.KnownCaipNamespace.Eip155, context.chainId),
342
394
  }), keyring_api_1.EthBytesStruct);
343
395
  }
344
396
  /**
@@ -362,18 +414,7 @@ class SnapKeyring extends events_1.EventEmitter {
362
414
  */
363
415
  async removeAccount(address) {
364
416
  const { account, snapId } = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_resolveAddress).call(this, address);
365
- // Always remove the account from the maps, even if the Snap is going to
366
- // fail to delete it.
367
- __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").delete(snapId, account.id);
368
- try {
369
- await __classPrivateFieldGet(this, _SnapKeyring_snapClient, "f").withSnapId(snapId).deleteAccount(account.id);
370
- }
371
- catch (error) {
372
- // If the Snap failed to delete the account, log the error and continue
373
- // with the account deletion, otherwise the account will be stuck in the
374
- // keyring.
375
- console.error(`Account '${address}' may not have been removed from snap '${snapId}':`, error);
376
- }
417
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_deleteAccount).call(this, snapId, account);
377
418
  }
378
419
  /**
379
420
  * Return an internal account object for a given address.
@@ -429,6 +470,10 @@ _SnapKeyring_messenger = new WeakMap(), _SnapKeyring_snapClient = new WeakMap(),
429
470
  async function _SnapKeyring_handleAccountCreated(snapId, message) {
430
471
  (0, superstruct_1.assert)(message, events_2.AccountCreatedEventStruct);
431
472
  const { account: newAccountFromEvent, accountNameSuggestion, displayConfirmation, } = message.params;
473
+ // READ THIS CAREFULLY:
474
+ // ------------------------------------------------------------------------
475
+ // The account creation flow is now asynchronous. We expect the Snap to
476
+ // first create the account data and then fire the "AccountCreated" event.
432
477
  // Potentially migrate the account.
433
478
  const account = (0, account_1.transformAccount)(newAccountFromEvent);
434
479
  // The UI still uses the account address to identify accounts, so we need
@@ -443,12 +488,55 @@ async function _SnapKeyring_handleAccountCreated(snapId, message) {
443
488
  if (__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").has(snapId, account.id)) {
444
489
  throw new Error(`Account '${account.id}' already exists`);
445
490
  }
446
- await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").addAccount(address, snapId, async (accepted) => {
491
+ // A deferred promise that will be resolved once the Snap keyring has saved
492
+ // its internal state.
493
+ // This part of the flow is run asynchronously, so we have no other way of
494
+ // letting the MetaMask client that this "save" has been run.
495
+ // NOTE: Another way of fixing that could be to rely on events through the
496
+ // messenger maybe?
497
+ const onceSaved = new DeferredPromise_1.DeferredPromise();
498
+ // Add the account to the keyring, but wait for the MetaMask client to
499
+ // approve the account creation first.
500
+ await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").addAccount(address, snapId,
501
+ // This callback is passed to the MetaMask client, it will be called whenever
502
+ // the end user will accept or not the account creation.
503
+ async (accepted) => {
447
504
  if (accepted) {
505
+ // We consider the account to be created on the Snap keyring only if
506
+ // the user accepted it. Meaning that the Snap MIGHT HAVE created the
507
+ // account on its own state, but the Snap keyring MIGHT NOT HAVE it yet.
508
+ //
509
+ // e.g The account creation dialog crashed on MetaMask, this callback
510
+ // will never be called, but the Snap still has the account.
448
511
  __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").set(account.id, { account, snapId });
449
- await __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").saveState();
512
+ // This is the "true async part". We do not `await` for this call, mainly
513
+ // because this callback will persist the account on the client side
514
+ // (through the `AccountsController`).
515
+ //
516
+ // Since this will happen after the Snap account creation and Snap
517
+ // event, if anything goes wrong, we will delete the account by
518
+ // calling `deleteAccount` on the Snap.
519
+ // eslint-disable-next-line no-void
520
+ void __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f")
521
+ .saveState()
522
+ .then(() => {
523
+ // This allows the MetaMask client to be "notified" when then
524
+ // Snap keyring has truly persisted its state. From there, we should
525
+ // be able to use the account (e.g. to display account creation
526
+ // confirmation dialogs).
527
+ onceSaved.resolve(account.id);
528
+ })
529
+ .catch(async (error) => {
530
+ // FIXME: There's a potential race condition here, if the Snap did
531
+ // not persist the account yet (this should mostly be for older Snaps).
532
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_deleteAccount).call(this, snapId, account);
533
+ // This allows the MetaMask client to be "notified" that something went
534
+ // wrong with the Snap keyring. (e.g. useful to display account creation
535
+ // error dialogs).
536
+ onceSaved.reject(error);
537
+ });
450
538
  }
451
- }, accountNameSuggestion, displayConfirmation);
539
+ }, onceSaved.promise, accountNameSuggestion, displayConfirmation);
452
540
  return null;
453
541
  }, _SnapKeyring_handleAccountUpdated =
454
542
  /**
@@ -576,76 +664,61 @@ async function _SnapKeyring_handleAccountAssetListUpdated(message) {
576
664
  async function _SnapKeyring_handleAccountTransactionsUpdated(message) {
577
665
  (0, superstruct_1.assert)(message, keyring_api_1.AccountTransactionsUpdatedEventStruct);
578
666
  return __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_rePublishAccountEvent).call(this, 'SnapKeyring:accountTransactionsUpdated', message.params);
667
+ }, _SnapKeyring_getAccount = function _SnapKeyring_getAccount(id) {
668
+ const found = [...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()].find((entry) => entry.account.id === id);
669
+ if (!found) {
670
+ throw new Error(`Unable to get account: unknown account ID: '${id}'`);
671
+ }
672
+ return found;
579
673
  }, _SnapKeyring_submitRequest =
580
674
  /**
581
- * Submit a request to a Snap.
675
+ * Submit a request to a Snap from an account address.
582
676
  *
583
677
  * @param opts - Request options.
584
678
  * @param opts.address - Account address.
585
679
  * @param opts.method - Method to call.
586
680
  * @param opts.params - Method parameters.
587
- * @param opts.chainId - Selected chain ID (CAIP-2).
681
+ * @param opts.scope - Selected chain ID (CAIP-2).
588
682
  * @param opts.noPending - Whether the response is allowed to be pending.
589
683
  * @returns Promise that resolves to the result of the method call.
590
684
  */
591
- async function _SnapKeyring_submitRequest({ address, method, params, chainId = '', noPending = false, }) {
685
+ async function _SnapKeyring_submitRequest({ address, method, params, scope = '', noPending = false, }) {
592
686
  const { account, snapId } = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_resolveAddress).call(this, address);
593
- if (!__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_hasMethod).call(this, account, method)) {
594
- throw new Error(`Method '${method}' not supported for account ${account.address}`);
595
- }
596
- const requestId = (0, uuid_1.v4)();
597
- // Create the promise before calling the Snap to prevent a race condition
598
- // where the Snap responds before we have a chance to create it.
599
- const promise = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_createRequestPromise).call(this, requestId, snapId);
600
- const response = await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_submitSnapRequest).call(this, {
687
+ return await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_submitSnapRequest).call(this, {
601
688
  snapId,
602
- requestId,
603
689
  account,
604
690
  method: method,
605
691
  params,
606
- chainId,
692
+ scope,
693
+ noPending,
607
694
  });
608
- // Some methods, like the ones used to prepare and patch user operations,
609
- // require the Snap to answer synchronously in order to work with the
610
- // confirmation flow. This check lets the caller enforce this behavior.
611
- if (noPending && response.pending) {
612
- throw new Error(`Request '${requestId}' to snap '${snapId}' is pending and noPending is true.`);
613
- }
614
- // If the Snap answers synchronously, the promise must be removed from the
615
- // map to prevent a leak.
616
- if (!response.pending) {
617
- return __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_handleSyncResponse).call(this, response, requestId, snapId);
618
- }
619
- // If the Snap answers asynchronously, we will inform the user with a redirect
620
- if (response.redirect?.message || response.redirect?.url) {
621
- await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_handleAsyncResponse).call(this, response.redirect, snapId);
622
- }
623
- return promise.promise;
624
- }, _SnapKeyring_hasMethod = function _SnapKeyring_hasMethod(account, method) {
625
- return account.methods.includes(method);
626
- }, _SnapKeyring_createRequestPromise = function _SnapKeyring_createRequestPromise(requestId, snapId) {
627
- const promise = new DeferredPromise_1.DeferredPromise();
628
- __classPrivateFieldGet(this, _SnapKeyring_requests, "f").set(requestId, { promise, snapId });
629
- return promise;
630
695
  }, _SnapKeyring_submitSnapRequest =
631
696
  /**
632
697
  * Submits a request to a Snap.
633
698
  *
634
699
  * @param options - The options for the Snap request.
635
700
  * @param options.snapId - The Snap ID to submit the request to.
636
- * @param options.requestId - The unique identifier for the request.
637
701
  * @param options.account - The account to use for the request.
638
702
  * @param options.method - The Ethereum method to call.
639
703
  * @param options.params - The parameters to pass to the method, can be undefined.
640
- * @param options.chainId - The chain ID to use for the request, can be an empty string.
704
+ * @param options.scope - The chain ID to use for the request, can be an empty string.
705
+ * @param options.noPending - Whether the response is allowed to be pending.
641
706
  * @returns A promise that resolves to the keyring response from the Snap.
642
707
  * @throws An error if the Snap fails to respond or if there's an issue with the request submission.
643
708
  */
644
- async function _SnapKeyring_submitSnapRequest({ snapId, requestId, account, method, params, chainId, }) {
709
+ async function _SnapKeyring_submitSnapRequest({ snapId, account, method, params, scope, noPending, }) {
710
+ if (!__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_hasMethod).call(this, account, method)) {
711
+ throw new Error(`Method '${method}' not supported for account ${account.address}`);
712
+ }
713
+ // Generate a new random request ID to keep track of the request execution flow.
714
+ const requestId = (0, uuid_1.v4)();
715
+ // Create the promise before calling the Snap to prevent a race condition
716
+ // where the Snap responds before we have a chance to create it.
717
+ const requestPromise = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_createRequestPromise).call(this, requestId, snapId);
645
718
  try {
646
719
  const request = {
647
720
  id: requestId,
648
- scope: chainId,
721
+ scope,
649
722
  account: account.id,
650
723
  request: {
651
724
  method,
@@ -653,16 +726,43 @@ async function _SnapKeyring_submitSnapRequest({ snapId, requestId, account, meth
653
726
  },
654
727
  };
655
728
  (0, logger_1.projectLogger)('Submit Snap request: ', request);
656
- return await __classPrivateFieldGet(this, _SnapKeyring_snapClient, "f").withSnapId(snapId).submitRequest(request);
729
+ const response = await __classPrivateFieldGet(this, _SnapKeyring_snapClient, "f")
730
+ .withSnapId(snapId)
731
+ .submitRequest(request);
732
+ // Some methods, like the ones used to prepare and patch user operations,
733
+ // require the Snap to answer synchronously in order to work with the
734
+ // confirmation flow. This check lets the caller enforce this behavior.
735
+ if (noPending && response.pending) {
736
+ throw new Error(`Request '${requestId}' to Snap '${snapId}' is pending and noPending is true.`);
737
+ }
738
+ // If the Snap answers synchronously, the promise must be removed from the
739
+ // map to prevent a leak.
740
+ if (!response.pending) {
741
+ return __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_handleSyncResponse).call(this, response, requestId, snapId);
742
+ }
743
+ // If the Snap answers asynchronously, we will inform the user with a redirect
744
+ if (response.redirect?.message || response.redirect?.url) {
745
+ await __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_handleAsyncResponse).call(this, response.redirect, snapId);
746
+ }
747
+ return requestPromise.promise;
657
748
  }
658
749
  catch (error) {
659
750
  (0, logger_1.projectLogger)('Snap Request failed: ', { requestId });
660
751
  // If the Snap failed to respond, delete the promise to prevent a leak.
661
- __classPrivateFieldGet(this, _SnapKeyring_requests, "f").delete(snapId, requestId);
752
+ __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_clearRequestPromise).call(this, requestId, snapId);
662
753
  throw error;
663
754
  }
755
+ }, _SnapKeyring_hasMethod = function _SnapKeyring_hasMethod(account, method) {
756
+ return account.methods.includes(method);
757
+ }, _SnapKeyring_createRequestPromise = function _SnapKeyring_createRequestPromise(requestId, snapId) {
758
+ const promise = new DeferredPromise_1.DeferredPromise();
759
+ __classPrivateFieldGet(this, _SnapKeyring_requests, "f").set(requestId, { promise, snapId });
760
+ return promise;
761
+ }, _SnapKeyring_clearRequestPromise = function _SnapKeyring_clearRequestPromise(requestId, snapId) {
762
+ __classPrivateFieldGet(this, _SnapKeyring_requests, "f").delete(snapId, requestId);
664
763
  }, _SnapKeyring_handleSyncResponse = function _SnapKeyring_handleSyncResponse(response, requestId, snapId) {
665
764
  __classPrivateFieldGet(this, _SnapKeyring_requests, "f").delete(snapId, requestId);
765
+ // We consider `Response` to be compatible with `result` here.
666
766
  return response.result;
667
767
  }, _SnapKeyring_handleAsyncResponse =
668
768
  /**
@@ -694,6 +794,26 @@ async function _SnapKeyring_handleAsyncResponse(redirect, snapId) {
694
794
  if (!allowedOrigins.includes(origin)) {
695
795
  throw new Error(`Redirect URL domain '${origin}' is not an allowed origin by snap '${snapId}'`);
696
796
  }
797
+ }, _SnapKeyring_deleteAccount =
798
+ /**
799
+ * Removes an account.
800
+ *
801
+ * @param snapId - Snap ID.
802
+ * @param account - Account to delete.
803
+ */
804
+ async function _SnapKeyring_deleteAccount(snapId, account) {
805
+ // Always remove the account from the maps, even if the Snap is going to
806
+ // fail to delete it.
807
+ __classPrivateFieldGet(this, _SnapKeyring_accounts, "f").delete(snapId, account.id);
808
+ try {
809
+ await __classPrivateFieldGet(this, _SnapKeyring_snapClient, "f").withSnapId(snapId).deleteAccount(account.id);
810
+ }
811
+ catch (error) {
812
+ // If the Snap failed to delete the account, log the error and continue
813
+ // with the account deletion, otherwise the account will be stuck in the
814
+ // keyring.
815
+ console.error(`Account '${account.address}' may not have been removed from snap '${snapId}':`, error);
816
+ }
697
817
  }, _SnapKeyring_resolveAddress = function _SnapKeyring_resolveAddress(address) {
698
818
  return ([...__classPrivateFieldGet(this, _SnapKeyring_accounts, "f").values()].find(({ account }) => (0, util_1.equalsIgnoreCase)(account.address, address)) ?? (0, util_1.throwError)(`Account '${address}' not found`));
699
819
  }, _SnapKeyring_getSnap = function _SnapKeyring_getSnap(snapId) {