@metamask-previews/eth-snap-keyring 19.0.0-fd40efd → 20.0.0-5c366be

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +32 -1
  2. package/dist/SnapKeyring.cjs +169 -826
  3. package/dist/SnapKeyring.cjs.map +1 -1
  4. package/dist/SnapKeyring.d.cts +21 -11
  5. package/dist/SnapKeyring.d.cts.map +1 -1
  6. package/dist/SnapKeyring.d.mts +21 -11
  7. package/dist/SnapKeyring.d.mts.map +1 -1
  8. package/dist/SnapKeyring.mjs +169 -826
  9. package/dist/SnapKeyring.mjs.map +1 -1
  10. package/dist/SnapKeyringMessenger.cjs.map +1 -1
  11. package/dist/SnapKeyringMessenger.d.cts +2 -2
  12. package/dist/SnapKeyringMessenger.d.cts.map +1 -1
  13. package/dist/SnapKeyringMessenger.d.mts +2 -2
  14. package/dist/SnapKeyringMessenger.d.mts.map +1 -1
  15. package/dist/SnapKeyringMessenger.mjs.map +1 -1
  16. package/dist/SnapKeyringV1.cjs +736 -0
  17. package/dist/SnapKeyringV1.cjs.map +1 -0
  18. package/dist/SnapKeyringV1.d.cts +222 -0
  19. package/dist/SnapKeyringV1.d.cts.map +1 -0
  20. package/dist/SnapKeyringV1.d.mts +222 -0
  21. package/dist/SnapKeyringV1.d.mts.map +1 -0
  22. package/dist/SnapKeyringV1.mjs +732 -0
  23. package/dist/SnapKeyringV1.mjs.map +1 -0
  24. package/dist/SnapKeyringV2.cjs +375 -0
  25. package/dist/SnapKeyringV2.cjs.map +1 -0
  26. package/dist/SnapKeyringV2.d.cts +275 -0
  27. package/dist/SnapKeyringV2.d.cts.map +1 -0
  28. package/dist/SnapKeyringV2.d.mts +275 -0
  29. package/dist/SnapKeyringV2.d.mts.map +1 -0
  30. package/dist/SnapKeyringV2.mjs +371 -0
  31. package/dist/SnapKeyringV2.mjs.map +1 -0
  32. package/dist/events.d.cts +4 -4
  33. package/dist/events.d.mts +4 -4
  34. package/dist/index.cjs +2 -0
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +2 -0
  37. package/dist/index.d.cts.map +1 -1
  38. package/dist/index.d.mts +2 -0
  39. package/dist/index.d.mts.map +1 -1
  40. package/dist/index.mjs +2 -0
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/util.cjs +16 -0
  43. package/dist/util.cjs.map +1 -1
  44. package/dist/util.d.cts +11 -0
  45. package/dist/util.d.cts.map +1 -1
  46. package/dist/util.d.mts +11 -0
  47. package/dist/util.d.mts.map +1 -1
  48. package/dist/util.mjs +15 -0
  49. package/dist/util.mjs.map +1 -1
  50. package/package.json +11 -10
  51. package/dist/DeferredPromise.cjs +0 -30
  52. package/dist/DeferredPromise.cjs.map +0 -1
  53. package/dist/DeferredPromise.d.cts +0 -16
  54. package/dist/DeferredPromise.d.cts.map +0 -1
  55. package/dist/DeferredPromise.d.mts +0 -16
  56. package/dist/DeferredPromise.d.mts.map +0 -1
  57. package/dist/DeferredPromise.mjs +0 -26
  58. package/dist/DeferredPromise.mjs.map +0 -1
@@ -0,0 +1,732 @@
1
+ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
+ if (kind === "m") throw new TypeError("Private method is not writable");
3
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
+ };
7
+ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
+ 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");
10
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
+ };
12
+ var _SnapKeyringV1_instances, _SnapKeyringV1_callbacks, _SnapKeyringV1_isAnyAccountTypeAllowed, _SnapKeyringV1_requests, _SnapKeyringV1_options, _SnapKeyringV1_selectedAccounts, _SnapKeyringV1_handleAccountCreated, _SnapKeyringV1_handleAccountUpdated, _SnapKeyringV1_handleAccountDeleted, _SnapKeyringV1_handleRequestApproved, _SnapKeyringV1_handleRequestRejected, _SnapKeyringV1_handleGetSelectedAccounts, _SnapKeyringV1_handleAccountBalancesUpdated, _SnapKeyringV1_handleAccountAssetListUpdated, _SnapKeyringV1_handleAccountTransactionsUpdated, _SnapKeyringV1_rePublishAccountEvent, _SnapKeyringV1_createSnapAccount, _SnapKeyringV1_getInternalOptions, _SnapKeyringV1_getExistingAccount, _SnapKeyringV1_createRequestPromise, _SnapKeyringV1_handleAsyncRedirect, _SnapKeyringV1_validateRedirectUrl;
13
+ import { TransactionFactory } from "@ethereumjs/tx";
14
+ import { SignTypedDataVersion } from "@metamask/eth-sig-util";
15
+ import { AnyAccountType, EthMethod, KeyringEvent, EthBytesStruct, EthBaseUserOperationStruct, EthUserOperationPatchStruct, AccountBalancesUpdatedEventStruct, AccountAssetListUpdatedEventStruct, AccountTransactionsUpdatedEventStruct } from "@metamask/keyring-api";
16
+ import { toKeyringRequestWithoutOrigin } from "@metamask/keyring-internal-api";
17
+ import { KeyringInternalSnapClient } from "@metamask/keyring-internal-snap-client";
18
+ import { KeyringAccountRegistry } from "@metamask/keyring-sdk";
19
+ import { GetSelectedAccountsRequestStruct, SnapManageAccountsMethod } from "@metamask/keyring-snap-sdk";
20
+ import { strictMask } from "@metamask/keyring-utils";
21
+ import { assert, mask, object, string } from "@metamask/superstruct";
22
+ import { bigIntToHex, hasProperty, KnownCaipNamespace, toCaipChainId, createDeferredPromise } from "@metamask/utils";
23
+ import { v4 as uuid } from "uuid";
24
+ import { transformAccount } from "./account.mjs";
25
+ import { AccountCreatedEventStruct, AccountUpdatedEventStruct, AccountDeletedEventStruct, RequestApprovedEventStruct, RequestRejectedEventStruct } from "./events.mjs";
26
+ import { projectLogger as log } from "./logger.mjs";
27
+ import { getInternalOptionsOf } from "./options.mjs";
28
+ import { PLATFORM_VERSION_FOR_KEYRING_REQUEST_WITH_ORIGIN } from "./platform-versions.mjs";
29
+ import { SnapMessageStruct } from "./types.mjs";
30
+ import { equalsIgnoreCase, normalizeAccountAddress, sanitizeUrl, throwError, toJson } from "./util.mjs";
31
+ /**
32
+ * Per-snap coordinator for the v1 (event-driven) keyring flow.
33
+ *
34
+ * Each instance is scoped to exactly one `SnapId`. It handles all Snap
35
+ * keyring events (account lifecycle, request approval, asset updates) and
36
+ * owns the per-snap request / internal-options state.
37
+ *
38
+ * Designed to be subclassed: `SnapKeyringV2` extends this class and adds the
39
+ * `KeyringV2` interface on top, sharing the same registry and client.
40
+ */
41
+ export class SnapKeyringV1 {
42
+ constructor({ snapId, messenger, callbacks, isAnyAccountTypeAllowed = false, }) {
43
+ _SnapKeyringV1_instances.add(this);
44
+ /** Injected callbacks for parent-level coordination. */
45
+ _SnapKeyringV1_callbacks.set(this, void 0);
46
+ /** Whether to allow the creation and update of generic accounts. */
47
+ _SnapKeyringV1_isAnyAccountTypeAllowed.set(this, void 0);
48
+ /**
49
+ * Pending async request promises, keyed by request ID.
50
+ * Plain Map is sufficient here since this instance is already snap-scoped.
51
+ */
52
+ _SnapKeyringV1_requests.set(this, void 0);
53
+ /**
54
+ * Correlation ID → internal options, used to thread options from
55
+ * `createAccount` through to the `AccountCreated` event handler.
56
+ */
57
+ _SnapKeyringV1_options.set(this, void 0);
58
+ /**
59
+ * The currently selected account IDs for this snap.
60
+ */
61
+ _SnapKeyringV1_selectedAccounts.set(this, void 0);
62
+ this.snapId = snapId;
63
+ this.registry = new KeyringAccountRegistry();
64
+ this.messenger = messenger;
65
+ __classPrivateFieldSet(this, _SnapKeyringV1_callbacks, callbacks, "f");
66
+ __classPrivateFieldSet(this, _SnapKeyringV1_isAnyAccountTypeAllowed, isAnyAccountTypeAllowed, "f");
67
+ this.client = new KeyringInternalSnapClient({ messenger, snapId });
68
+ __classPrivateFieldSet(this, _SnapKeyringV1_requests, new Map(), "f");
69
+ __classPrivateFieldSet(this, _SnapKeyringV1_options, new Map(), "f");
70
+ __classPrivateFieldSet(this, _SnapKeyringV1_selectedAccounts, [], "f");
71
+ }
72
+ // ──────────────────────────────────────────────
73
+ // Public API
74
+ // ──────────────────────────────────────────────
75
+ /**
76
+ * Create a single account using the v1 event-driven flow.
77
+ *
78
+ * Handles the reserved 'metamask' field, correlation IDs, and internal
79
+ * options registration. The account is ultimately added to the registry
80
+ * via the `AccountCreated` event handler, not the return value.
81
+ *
82
+ * @param options - Account creation options.
83
+ * @param internalOptions - Internal Snap keyring options.
84
+ * @returns The account object returned by the snap.
85
+ */
86
+ async createAccount(options, internalOptions) {
87
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_createSnapAccount).call(this, options, internalOptions);
88
+ }
89
+ /**
90
+ * Handle an incoming message from the Snap.
91
+ *
92
+ * Routes the message to the appropriate private handler based on the method.
93
+ *
94
+ * @param message - The message sent by the Snap.
95
+ * @returns The execution result.
96
+ */
97
+ async handleKeyringSnapMessage(message) {
98
+ assert(message, SnapMessageStruct);
99
+ switch (message.method) {
100
+ case `${KeyringEvent.AccountCreated}`: {
101
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleAccountCreated).call(this, message);
102
+ }
103
+ case `${KeyringEvent.AccountUpdated}`: {
104
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleAccountUpdated).call(this, message);
105
+ }
106
+ case `${KeyringEvent.AccountDeleted}`: {
107
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleAccountDeleted).call(this, message);
108
+ }
109
+ case `${KeyringEvent.RequestApproved}`: {
110
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleRequestApproved).call(this, message);
111
+ }
112
+ case `${KeyringEvent.RequestRejected}`: {
113
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleRequestRejected).call(this, message);
114
+ }
115
+ case `${KeyringEvent.AccountBalancesUpdated}`: {
116
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleAccountBalancesUpdated).call(this, message);
117
+ }
118
+ case `${KeyringEvent.AccountAssetListUpdated}`: {
119
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleAccountAssetListUpdated).call(this, message);
120
+ }
121
+ case `${KeyringEvent.AccountTransactionsUpdated}`: {
122
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleAccountTransactionsUpdated).call(this, message);
123
+ }
124
+ case `${SnapManageAccountsMethod.GetSelectedAccounts}`: {
125
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleGetSelectedAccounts).call(this, message);
126
+ }
127
+ default:
128
+ throw new Error(`Method not supported: ${message.method}`);
129
+ }
130
+ }
131
+ /**
132
+ * Submit a snap request for the given account.
133
+ *
134
+ * Manages the request promise lifecycle (creation, resolution, cleanup) and
135
+ * handles both synchronous and asynchronous (redirect) responses.
136
+ *
137
+ * @param options - The request options.
138
+ * @param options.origin - The sender origin.
139
+ * @param options.account - The account to submit the request for.
140
+ * @param options.method - The method to call.
141
+ * @param options.params - The method parameters.
142
+ * @param options.scope - The CAIP-2 chain ID.
143
+ * @param options.noPending - Whether the response is allowed to be pending.
144
+ * @returns A promise that resolves to the keyring response.
145
+ */
146
+ async submitSnapRequest({ origin, account, method, params, scope, noPending, }) {
147
+ if (!account.methods.includes(method)) {
148
+ throw new Error(`Method '${method}' not supported for account ${account.address}`);
149
+ }
150
+ // Will both catch `undefined` and "empty" origins.
151
+ if (!origin?.trim()) {
152
+ throw new Error('An `origin` is required');
153
+ }
154
+ const requestId = uuid();
155
+ // Create the promise before calling the Snap to prevent a race condition
156
+ // where the Snap responds before we have a chance to create it.
157
+ const requestPromise = __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_createRequestPromise).call(this, requestId);
158
+ try {
159
+ const useOrigin = this.messenger.call('SnapController:isMinimumPlatformVersion', this.snapId, PLATFORM_VERSION_FOR_KEYRING_REQUEST_WITH_ORIGIN);
160
+ const request = {
161
+ id: requestId,
162
+ origin,
163
+ scope,
164
+ account: account.id,
165
+ request: {
166
+ method,
167
+ ...(params !== undefined && { params }),
168
+ },
169
+ };
170
+ log('Submit Snap request: ', request);
171
+ let response;
172
+ if (useOrigin) {
173
+ response = await this.client.submitRequest(request);
174
+ }
175
+ else {
176
+ response = await this.client.submitRequestWithoutOrigin(toKeyringRequestWithoutOrigin(request));
177
+ }
178
+ if (noPending && response.pending) {
179
+ throw new Error(`Request '${requestId}' to Snap '${this.snapId}' is pending and noPending is true.`);
180
+ }
181
+ if (!response.pending) {
182
+ __classPrivateFieldGet(this, _SnapKeyringV1_requests, "f").delete(requestId);
183
+ return response.result;
184
+ }
185
+ if (response.redirect?.message || response.redirect?.url) {
186
+ await __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_handleAsyncRedirect).call(this, response.redirect);
187
+ }
188
+ return requestPromise.promise;
189
+ }
190
+ catch (error) {
191
+ log('Snap Request failed: ', { requestId });
192
+ __classPrivateFieldGet(this, _SnapKeyringV1_requests, "f").delete(requestId);
193
+ throw error;
194
+ }
195
+ }
196
+ /**
197
+ * Update the selected accounts for this snap.
198
+ *
199
+ * @param accountIds - The account IDs to set as selected.
200
+ */
201
+ async setSelectedAccounts(accountIds) {
202
+ __classPrivateFieldSet(this, _SnapKeyringV1_selectedAccounts, accountIds, "f");
203
+ try {
204
+ await this.client.setSelectedAccounts(accountIds);
205
+ }
206
+ catch (error) {
207
+ console.error(`Failed to set selected accounts for ${this.snapId} snap: '${error.message}'`);
208
+ }
209
+ }
210
+ /**
211
+ * Resolve the account address associated with a signing request.
212
+ *
213
+ * Delegates directly to the snap-scoped client, which already has the
214
+ * snap ID baked in from construction time.
215
+ *
216
+ * @param scope - CAIP-2 chain ID.
217
+ * @param request - Signing request object.
218
+ * @returns The resolved address if found, `null` otherwise.
219
+ */
220
+ async resolveAccountAddress(scope, request) {
221
+ return this.client.resolveAccountAddress(scope, request);
222
+ }
223
+ /**
224
+ * Sign a transaction for the given account.
225
+ *
226
+ * @param account - The account to sign the transaction for.
227
+ * @param transaction - The transaction to sign.
228
+ * @param _opts - Transaction options (unused).
229
+ * @returns A promise that resolves to the signed transaction.
230
+ */
231
+ async signTransaction(account, transaction,
232
+ /* istanbul ignore next */
233
+ _opts = {}) {
234
+ const chainId = transaction.common.chainId();
235
+ const tx = toJson({
236
+ ...transaction.toJSON(),
237
+ from: normalizeAccountAddress(account),
238
+ type: `0x${transaction.type.toString(16)}`,
239
+ chainId: bigIntToHex(chainId),
240
+ });
241
+ const signedTx = await this.submitSnapRequest({
242
+ origin: 'metamask',
243
+ account,
244
+ method: EthMethod.SignTransaction,
245
+ params: [tx],
246
+ scope: toCaipChainId(KnownCaipNamespace.Eip155, `${chainId}`),
247
+ noPending: false,
248
+ });
249
+ // ! It's *** CRITICAL *** that we mask the signature here, otherwise the
250
+ // ! Snap could overwrite the transaction.
251
+ const signature = mask(signedTx, object({
252
+ r: string(),
253
+ s: string(),
254
+ v: string(),
255
+ }));
256
+ return TransactionFactory.fromTxData({
257
+ ...tx,
258
+ r: signature.r,
259
+ s: signature.s,
260
+ v: signature.v,
261
+ });
262
+ }
263
+ /**
264
+ * Sign a typed data message for the given account.
265
+ *
266
+ * @param account - The account to sign the typed data for.
267
+ * @param data - The typed data to sign.
268
+ * @param opts - Signing options.
269
+ * @param opts.version - The typed data version to use.
270
+ * @returns A promise that resolves to the signature.
271
+ */
272
+ async signTypedData(account, data, opts) {
273
+ const methods = {
274
+ [SignTypedDataVersion.V1]: EthMethod.SignTypedDataV1,
275
+ [SignTypedDataVersion.V3]: EthMethod.SignTypedDataV3,
276
+ [SignTypedDataVersion.V4]: EthMethod.SignTypedDataV4,
277
+ };
278
+ // Use 'V1' by default to match other keyring implementations. V1 will be
279
+ // used if the version is not specified or not supported.
280
+ const method = methods[opts.version] || EthMethod.SignTypedDataV1;
281
+ // Extract chain ID as if it was a typed message (as defined by EIP-712), if
282
+ // input is not a typed message, then chain ID will be undefined!
283
+ const chainId = data.domain?.chainId;
284
+ const address = normalizeAccountAddress(account);
285
+ return strictMask(await this.submitSnapRequest({
286
+ origin: 'metamask',
287
+ account,
288
+ method,
289
+ params: toJson([address, data]),
290
+ scope: chainId === undefined
291
+ ? ''
292
+ : toCaipChainId(KnownCaipNamespace.Eip155, `${chainId}`),
293
+ noPending: false,
294
+ }), EthBytesStruct);
295
+ }
296
+ /**
297
+ * Sign a message (eth_sign) for the given account.
298
+ *
299
+ * @param account - The account to sign the message for.
300
+ * @param hash - The data to sign.
301
+ * @returns A promise that resolves to the signature.
302
+ */
303
+ async signMessage(account, hash) {
304
+ const address = normalizeAccountAddress(account);
305
+ return strictMask(await this.submitSnapRequest({
306
+ origin: 'metamask',
307
+ account,
308
+ method: EthMethod.Sign,
309
+ params: toJson([address, hash]),
310
+ scope: '',
311
+ noPending: false,
312
+ }), EthBytesStruct);
313
+ }
314
+ /**
315
+ * Sign a personal message for the given account.
316
+ *
317
+ * Note: KeyringController says this should return a Buffer but it actually
318
+ * expects a string.
319
+ *
320
+ * @param account - The account to sign the message for.
321
+ * @param data - The data to sign.
322
+ * @returns A promise that resolves to the signature.
323
+ */
324
+ async signPersonalMessage(account, data) {
325
+ const address = normalizeAccountAddress(account);
326
+ return strictMask(await this.submitSnapRequest({
327
+ origin: 'metamask',
328
+ account,
329
+ method: EthMethod.PersonalSign,
330
+ params: toJson([data, address]),
331
+ scope: '',
332
+ noPending: false,
333
+ }), EthBytesStruct);
334
+ }
335
+ /**
336
+ * Convert a base transaction to a base UserOperation.
337
+ *
338
+ * @param account - The account to prepare the user operation for.
339
+ * @param transactions - Base transactions to include in the UserOperation.
340
+ * @param context - Keyring execution context.
341
+ * @returns A pseudo-UserOperation that can be used to construct a real one.
342
+ */
343
+ async prepareUserOperation(account, transactions, context) {
344
+ return strictMask(await this.submitSnapRequest({
345
+ origin: 'metamask',
346
+ account,
347
+ method: EthMethod.PrepareUserOperation,
348
+ params: toJson(transactions),
349
+ noPending: true,
350
+ scope: toCaipChainId(KnownCaipNamespace.Eip155, context.chainId),
351
+ }), EthBaseUserOperationStruct);
352
+ }
353
+ /**
354
+ * Patches properties of a UserOperation. Currently, only the
355
+ * `paymasterAndData` can be patched.
356
+ *
357
+ * @param account - The account to patch the user operation for.
358
+ * @param userOp - UserOperation to patch.
359
+ * @param context - Keyring execution context.
360
+ * @returns A patch to apply to the UserOperation.
361
+ */
362
+ async patchUserOperation(account, userOp, context) {
363
+ return strictMask(await this.submitSnapRequest({
364
+ origin: 'metamask',
365
+ account,
366
+ method: EthMethod.PatchUserOperation,
367
+ params: toJson([userOp]),
368
+ noPending: true,
369
+ scope: toCaipChainId(KnownCaipNamespace.Eip155, context.chainId),
370
+ }), EthUserOperationPatchStruct);
371
+ }
372
+ /**
373
+ * Signs a UserOperation.
374
+ *
375
+ * @param account - The account to sign the user operation for.
376
+ * @param userOp - UserOperation to sign.
377
+ * @param context - Keyring execution context.
378
+ * @returns A promise that resolves to the signature.
379
+ */
380
+ async signUserOperation(account, userOp, context) {
381
+ return strictMask(await this.submitSnapRequest({
382
+ origin: 'metamask',
383
+ account,
384
+ method: EthMethod.SignUserOperation,
385
+ params: toJson([userOp]),
386
+ noPending: false,
387
+ scope: toCaipChainId(KnownCaipNamespace.Eip155, context.chainId),
388
+ }), EthBytesStruct);
389
+ }
390
+ }
391
+ _SnapKeyringV1_callbacks = new WeakMap(), _SnapKeyringV1_isAnyAccountTypeAllowed = new WeakMap(), _SnapKeyringV1_requests = new WeakMap(), _SnapKeyringV1_options = new WeakMap(), _SnapKeyringV1_selectedAccounts = new WeakMap(), _SnapKeyringV1_instances = new WeakSet(), _SnapKeyringV1_handleAccountCreated =
392
+ // ──────────────────────────────────────────────
393
+ // Private handlers
394
+ // ──────────────────────────────────────────────
395
+ /**
396
+ * Handle an Account Created event from the Snap.
397
+ *
398
+ * @param message - Event message.
399
+ * @returns `null`.
400
+ */
401
+ async function _SnapKeyringV1_handleAccountCreated(message) {
402
+ assert(message, AccountCreatedEventStruct);
403
+ const { metamask, account: newAccountFromEvent, accountNameSuggestion, displayAccountNameSuggestion, displayConfirmation, } = message.params;
404
+ // READ THIS CAREFULLY:
405
+ // ------------------------------------------------------------------------
406
+ // The account creation flow is now asynchronous. We expect the Snap to
407
+ // first create the account data and then fire the "AccountCreated" event.
408
+ // Potentially migrate the account.
409
+ const account = transformAccount(newAccountFromEvent);
410
+ const address = normalizeAccountAddress(account);
411
+ if (__classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_getExistingAccount).call(this, account)) {
412
+ // If the account already exists, we skip it.
413
+ return null;
414
+ }
415
+ // Make sure this new account is valid.
416
+ await __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").assertAccountCanBeUsed(account);
417
+ // A deferred promise that will be resolved once the Snap keyring has saved
418
+ // its internal state.
419
+ // This part of the flow is run asynchronously, so we have no other way of
420
+ // letting the MetaMask client that this "save" has been run.
421
+ // NOTE: Another way of fixing that could be to rely on events through the
422
+ // messenger maybe?
423
+ const onceSaved = createDeferredPromise();
424
+ // Add the account to the keyring, but wait for the MetaMask client to
425
+ // approve the account creation first.
426
+ await __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").addAccount(address,
427
+ // This callback is passed to the MetaMask client, it will be called whenever
428
+ // the end user will accept or not the account creation.
429
+ async (accepted) => {
430
+ if (accepted) {
431
+ // We consider the account to be created on the Snap keyring only if
432
+ // the user accepted it. Meaning that the Snap MIGHT HAVE created the
433
+ // account on its own state, but the Snap keyring MIGHT NOT HAVE it yet.
434
+ //
435
+ // e.g The account creation dialog crashed on MetaMask, this callback
436
+ // will never be called, but the Snap still has the account.
437
+ this.registry.set(account);
438
+ __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").onRegister?.(account.id);
439
+ // This is the "true async part". We do not `await` for this call, mainly
440
+ // because this callback will persist the account on the client side
441
+ // (through the `AccountsController`).
442
+ //
443
+ // Since this will happen after the Snap account creation and Snap
444
+ // event, if anything goes wrong, we will delete the account by
445
+ // calling `deleteAccount` on the Snap.
446
+ // eslint-disable-next-line no-void
447
+ void __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f")
448
+ .saveState()
449
+ .then(() => {
450
+ // This allows the MetaMask client to be "notified" when then
451
+ // Snap keyring has truly persisted its state. From there, we should
452
+ // be able to use the account (e.g. to display account creation
453
+ // confirmation dialogs).
454
+ onceSaved.resolve(account.id);
455
+ })
456
+ .catch(
457
+ /* istanbul ignore next */
458
+ async (error) => {
459
+ // FIXME: There's a potential race condition here, if the Snap did
460
+ // not persist the account yet (this should mostly be for older Snaps).
461
+ this.registry.delete(account.id);
462
+ __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").onUnregister?.(account.id);
463
+ await this.client
464
+ .deleteAccount(account.id)
465
+ /* istanbul ignore next */
466
+ .catch(() => {
467
+ // Swallow — best-effort cleanup.
468
+ });
469
+ // This allows the MetaMask client to be "notified" that something went
470
+ // wrong with the Snap keyring. (e.g. useful to display account creation
471
+ // error dialogs).
472
+ onceSaved.reject(error);
473
+ });
474
+ }
475
+ }, onceSaved.promise, accountNameSuggestion, getInternalOptionsOf([
476
+ // 1. We use the internal options from the Snap keyring.
477
+ __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_getInternalOptions).call(this, metamask?.correlationId) ?? {},
478
+ // 2. We use the ones coming from the Snap.
479
+ {
480
+ displayConfirmation,
481
+ displayAccountNameSuggestion,
482
+ },
483
+ ]));
484
+ return null;
485
+ }, _SnapKeyringV1_handleAccountUpdated =
486
+ /**
487
+ * Handle an Account Updated event from the Snap.
488
+ *
489
+ * @param message - Event message.
490
+ * @returns `null`.
491
+ */
492
+ async function _SnapKeyringV1_handleAccountUpdated(message) {
493
+ assert(message, AccountUpdatedEventStruct);
494
+ const { account: newAccountFromEvent } = message.params;
495
+ const oldAccount = this.registry.get(newAccountFromEvent.id) ??
496
+ throwError(`Account '${newAccountFromEvent.id}' not found`);
497
+ // Potentially migrate the account.
498
+ const newAccount = transformAccount(newAccountFromEvent);
499
+ // The `AnyAccountType.Account` generic account type is allowed only during
500
+ // development, so we check whether it's allowed before continuing.
501
+ //
502
+ // An account cannot be updated if the `isAnyAccountTypeAllowed` flag is
503
+ // set to `false` and the new or old account is a generic account.
504
+ const isGenericAccountInvolved = newAccount.type === AnyAccountType.Account ||
505
+ oldAccount.type === AnyAccountType.Account;
506
+ if (!__classPrivateFieldGet(this, _SnapKeyringV1_isAnyAccountTypeAllowed, "f") && isGenericAccountInvolved) {
507
+ throw new Error(`Cannot update generic account '${newAccount.id}'`);
508
+ }
509
+ // The address of the account cannot be changed. In the future, we will
510
+ // support changing the address of an account since it will be required to
511
+ // support UTXO-based chains.
512
+ if (!equalsIgnoreCase(oldAccount.address, newAccount.address)) {
513
+ throw new Error(`Cannot change address of account '${newAccount.id}'`);
514
+ }
515
+ this.registry.set(newAccount);
516
+ await __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").saveState();
517
+ return null;
518
+ }, _SnapKeyringV1_handleAccountDeleted =
519
+ /**
520
+ * Handle an Account Deleted event from the Snap.
521
+ *
522
+ * @param message - Event message.
523
+ * @returns `null`.
524
+ */
525
+ async function _SnapKeyringV1_handleAccountDeleted(message) {
526
+ assert(message, AccountDeletedEventStruct);
527
+ const { id } = message.params;
528
+ const account = this.registry.get(id);
529
+ // We can ignore the case where the account was already removed from the
530
+ // keyring, making the deletion idempotent.
531
+ //
532
+ // This happens when the keyring calls the Snap to delete an account, and
533
+ // the Snap calls the keyring back with an `AccountDeleted` event.
534
+ if (account === undefined) {
535
+ return null;
536
+ }
537
+ await __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").removeAccount(normalizeAccountAddress(account), async (accepted) => {
538
+ if (accepted) {
539
+ await __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").saveState();
540
+ }
541
+ });
542
+ return null;
543
+ }, _SnapKeyringV1_handleRequestApproved =
544
+ /**
545
+ * Handle a Request Approved event from the Snap.
546
+ *
547
+ * @param message - Event message.
548
+ * @returns `null`.
549
+ */
550
+ async function _SnapKeyringV1_handleRequestApproved(message) {
551
+ assert(message, RequestApprovedEventStruct);
552
+ const { id, result } = message.params;
553
+ const promise = __classPrivateFieldGet(this, _SnapKeyringV1_requests, "f").get(id) ?? throwError(`Request '${id}' not found`);
554
+ __classPrivateFieldGet(this, _SnapKeyringV1_requests, "f").delete(id);
555
+ promise.resolve(result);
556
+ return null;
557
+ }, _SnapKeyringV1_handleRequestRejected =
558
+ /**
559
+ * Handle a Request Rejected event from the Snap.
560
+ *
561
+ * @param message - Event message.
562
+ * @returns `null`.
563
+ */
564
+ async function _SnapKeyringV1_handleRequestRejected(message) {
565
+ assert(message, RequestRejectedEventStruct);
566
+ const { id } = message.params;
567
+ const promise = __classPrivateFieldGet(this, _SnapKeyringV1_requests, "f").get(id) ?? throwError(`Request '${id}' not found`);
568
+ __classPrivateFieldGet(this, _SnapKeyringV1_requests, "f").delete(id);
569
+ promise.reject(new Error(`Request rejected by user or snap.`));
570
+ return null;
571
+ }, _SnapKeyringV1_handleGetSelectedAccounts =
572
+ /**
573
+ * Handle a Get Selected Accounts method call from the Snap.
574
+ *
575
+ * @param message - Method call message.
576
+ * @returns The selected accounts.
577
+ */
578
+ async function _SnapKeyringV1_handleGetSelectedAccounts(message) {
579
+ assert(message, GetSelectedAccountsRequestStruct);
580
+ return __classPrivateFieldGet(this, _SnapKeyringV1_selectedAccounts, "f");
581
+ }, _SnapKeyringV1_handleAccountBalancesUpdated =
582
+ /**
583
+ * Handle an Account Balances Updated event from the Snap.
584
+ *
585
+ * @param message - Event message.
586
+ * @returns `null`.
587
+ */
588
+ async function _SnapKeyringV1_handleAccountBalancesUpdated(message) {
589
+ assert(message, AccountBalancesUpdatedEventStruct);
590
+ const event = message.params;
591
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_rePublishAccountEvent).call(this, 'SnapKeyring:accountBalancesUpdated', (filter) => {
592
+ event.balances = filter(event.balances);
593
+ return [event];
594
+ });
595
+ }, _SnapKeyringV1_handleAccountAssetListUpdated =
596
+ /**
597
+ * Handle an Account Asset List Updated event from the Snap.
598
+ *
599
+ * @param message - Event message.
600
+ * @returns `null`.
601
+ */
602
+ async function _SnapKeyringV1_handleAccountAssetListUpdated(message) {
603
+ assert(message, AccountAssetListUpdatedEventStruct);
604
+ const event = message.params;
605
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_rePublishAccountEvent).call(this, 'SnapKeyring:accountAssetListUpdated', (filter) => {
606
+ event.assets = filter(event.assets);
607
+ return [event];
608
+ });
609
+ }, _SnapKeyringV1_handleAccountTransactionsUpdated =
610
+ /**
611
+ * Handle an Account Transactions Updated event from the Snap.
612
+ *
613
+ * @param message - Event message.
614
+ * @returns `null`.
615
+ */
616
+ async function _SnapKeyringV1_handleAccountTransactionsUpdated(message) {
617
+ assert(message, AccountTransactionsUpdatedEventStruct);
618
+ const event = message.params;
619
+ return __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_rePublishAccountEvent).call(this, 'SnapKeyring:accountTransactionsUpdated', (filter) => {
620
+ event.transactions = filter(event.transactions);
621
+ return [event];
622
+ });
623
+ }, _SnapKeyringV1_rePublishAccountEvent =
624
+ // ──────────────────────────────────────────────
625
+ // Private helpers
626
+ // ──────────────────────────────────────────────
627
+ /**
628
+ * Re-publish an account event after filtering out accounts that don't
629
+ * belong to this snap.
630
+ *
631
+ * Uses the registry as the source of truth: only accounts present in
632
+ * this snap's registry are forwarded. This prevents a snap from emitting
633
+ * updates for accounts it doesn't own.
634
+ *
635
+ * @param event - The event type to re-publish.
636
+ * @param filteredEventCallback - Callback that receives the filter function
637
+ * and returns the event payload to publish.
638
+ * @template EventType - A Snap keyring event type.
639
+ * @returns `null`.
640
+ */
641
+ async function _SnapKeyringV1_rePublishAccountEvent(event, filteredEventCallback) {
642
+ const filter = (accountMapping) => {
643
+ return Object.entries(accountMapping).reduce((filtered, [accountId, entry]) => {
644
+ if (this.registry.has(accountId)) {
645
+ filtered[accountId] = entry;
646
+ }
647
+ else {
648
+ console.warn(`SnapKeyring - ${event} - Found an unknown account ID "${accountId}" for Snap ID "${this.snapId}". Skipping.`);
649
+ }
650
+ return filtered;
651
+ }, {});
652
+ };
653
+ this.messenger.publish(event, ...filteredEventCallback(filter));
654
+ return null;
655
+ }, _SnapKeyringV1_createSnapAccount =
656
+ /**
657
+ * Core logic for the v1 `createAccount` flow.
658
+ *
659
+ * Handles the reserved 'metamask' field, correlation IDs, and internal
660
+ * options registration. The account is ultimately added to the registry
661
+ * via the `AccountCreated` event handler, not the return value.
662
+ *
663
+ * @param options - Account creation options.
664
+ * @param internalOptions - Internal Snap keyring options.
665
+ * @returns The account object returned by the snap.
666
+ */
667
+ async function _SnapKeyringV1_createSnapAccount(options, internalOptions) {
668
+ const reserved = 'metamask';
669
+ if (hasProperty(options, reserved)) {
670
+ throw new Error(`The '${reserved}' property is reserved for internal use`);
671
+ }
672
+ if (!internalOptions) {
673
+ return await this.client.createAccount(options);
674
+ }
675
+ const correlationId = uuid();
676
+ __classPrivateFieldGet(this, _SnapKeyringV1_options, "f").set(correlationId, internalOptions);
677
+ return await this.client.createAccount({
678
+ ...options,
679
+ metamask: {
680
+ correlationId,
681
+ },
682
+ });
683
+ }, _SnapKeyringV1_getInternalOptions = function _SnapKeyringV1_getInternalOptions(correlationId) {
684
+ if (!correlationId) {
685
+ return undefined;
686
+ }
687
+ const options = __classPrivateFieldGet(this, _SnapKeyringV1_options, "f").get(correlationId);
688
+ if (options) {
689
+ __classPrivateFieldGet(this, _SnapKeyringV1_options, "f").delete(correlationId);
690
+ return options;
691
+ }
692
+ console.warn(`SnapKeyring - Received unmapped correlation ID: "${correlationId}"`);
693
+ return undefined;
694
+ }, _SnapKeyringV1_getExistingAccount = function _SnapKeyringV1_getExistingAccount(account) {
695
+ const address = normalizeAccountAddress(account);
696
+ const existing = this.registry.get(account.id);
697
+ if (existing && normalizeAccountAddress(existing) === address) {
698
+ return existing;
699
+ }
700
+ return undefined;
701
+ }, _SnapKeyringV1_createRequestPromise = function _SnapKeyringV1_createRequestPromise(requestId) {
702
+ const promise = createDeferredPromise();
703
+ __classPrivateFieldGet(this, _SnapKeyringV1_requests, "f").set(requestId, promise);
704
+ return promise;
705
+ }, _SnapKeyringV1_handleAsyncRedirect =
706
+ /**
707
+ * Handle an async redirect response from the Snap.
708
+ *
709
+ * @param redirect - The redirect information.
710
+ * @param redirect.message - The message to show the user.
711
+ * @param redirect.url - The URL to redirect to.
712
+ */
713
+ async function _SnapKeyringV1_handleAsyncRedirect(redirect) {
714
+ const { message = '', url: redirectUrl = '' } = redirect;
715
+ const url = redirectUrl ? sanitizeUrl(redirectUrl) : redirectUrl;
716
+ if (url) {
717
+ __classPrivateFieldGet(this, _SnapKeyringV1_instances, "m", _SnapKeyringV1_validateRedirectUrl).call(this, url);
718
+ }
719
+ await __classPrivateFieldGet(this, _SnapKeyringV1_callbacks, "f").redirectUser(url, message);
720
+ }, _SnapKeyringV1_validateRedirectUrl = function _SnapKeyringV1_validateRedirectUrl(url) {
721
+ const { origin } = new URL(url);
722
+ const snap = this.messenger.call('SnapController:getSnap', this.snapId);
723
+ if (!snap) {
724
+ throw new Error(`Snap '${this.snapId}' not found.`);
725
+ }
726
+ const allowedOrigins = snap.manifest.initialPermissions['endowment:keyring']?.allowedOrigins ??
727
+ [];
728
+ if (!allowedOrigins.includes(origin)) {
729
+ throw new Error(`Redirect URL domain '${origin}' is not an allowed origin by snap '${this.snapId}'`);
730
+ }
731
+ };
732
+ //# sourceMappingURL=SnapKeyringV1.mjs.map