@metamask-previews/eth-snap-keyring 22.3.0-e51c895 → 22.4.0-5951828
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 +21 -1
- package/dist/SnapKeyring.cjs +31 -16
- package/dist/SnapKeyring.cjs.map +1 -1
- package/dist/SnapKeyring.d.cts +1 -1
- package/dist/SnapKeyring.d.cts.map +1 -1
- package/dist/SnapKeyring.d.mts +1 -1
- package/dist/SnapKeyring.d.mts.map +1 -1
- package/dist/SnapKeyring.mjs +31 -16
- package/dist/SnapKeyring.mjs.map +1 -1
- package/dist/SnapKeyringV1.cjs +16 -2
- package/dist/SnapKeyringV1.cjs.map +1 -1
- package/dist/SnapKeyringV1.d.cts +21 -2
- package/dist/SnapKeyringV1.d.cts.map +1 -1
- package/dist/SnapKeyringV1.d.mts +21 -2
- package/dist/SnapKeyringV1.d.mts.map +1 -1
- package/dist/SnapKeyringV1.mjs +16 -2
- package/dist/SnapKeyringV1.mjs.map +1 -1
- package/dist/events.cjs +4 -5
- package/dist/events.cjs.map +1 -1
- package/dist/events.d.cts +4 -4
- package/dist/events.d.cts.map +1 -1
- package/dist/events.d.mts +4 -4
- package/dist/events.d.mts.map +1 -1
- package/dist/events.mjs +1 -2
- package/dist/events.mjs.map +1 -1
- package/dist/v2/SnapKeyring.cjs +202 -58
- package/dist/v2/SnapKeyring.cjs.map +1 -1
- package/dist/v2/SnapKeyring.d.cts +67 -24
- package/dist/v2/SnapKeyring.d.cts.map +1 -1
- package/dist/v2/SnapKeyring.d.mts +67 -24
- package/dist/v2/SnapKeyring.d.mts.map +1 -1
- package/dist/v2/SnapKeyring.mjs +202 -58
- package/dist/v2/SnapKeyring.mjs.map +1 -1
- package/package.json +7 -7
package/dist/v2/SnapKeyring.mjs
CHANGED
|
@@ -9,15 +9,25 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
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
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _SnapKeyring_instances, _SnapKeyring_callbacks, _SnapKeyring_lock, _SnapKeyring_withLock;
|
|
12
|
+
var _SnapKeyring_instances, _SnapKeyring_registry, _SnapKeyring_messenger, _SnapKeyring_callbacks, _SnapKeyring_isAnyAccountTypeAllowed, _SnapKeyring_lock, _SnapKeyring_context, _SnapKeyring_v1, _SnapKeyring_client_get, _SnapKeyring_withLock, _SnapKeyring_assertInitialized, _SnapKeyring_resolveKeyringCapabilities, _SnapKeyring_getExistingAccount;
|
|
13
13
|
import { KeyringAccountStruct } from "@metamask/keyring-api";
|
|
14
14
|
import { KeyringType } from "@metamask/keyring-api/v2";
|
|
15
|
+
import { KeyringInternalSnapClient } from "@metamask/keyring-internal-snap-client/v2";
|
|
16
|
+
import { KeyringAccountRegistry } from "@metamask/keyring-sdk";
|
|
15
17
|
import { assert, object, record, string, union } from "@metamask/superstruct";
|
|
16
18
|
import { Mutex } from "async-mutex";
|
|
17
|
-
import { KeyringAccountV1Struct, normalizeAccount, transformAccount } from "../account.mjs";
|
|
19
|
+
import { KeyringAccountV1Struct, normalizeAccount, normalizeAccountAddress, transformAccount } from "../account.mjs";
|
|
18
20
|
import { isAccountV1, migrateAccountV1 } from "../migrations/index.mjs";
|
|
19
21
|
import { SnapKeyringV1 } from "../SnapKeyringV1.mjs";
|
|
20
22
|
import { equalsIgnoreCase } from "../util.mjs";
|
|
23
|
+
/**
|
|
24
|
+
* Default, empty capabilities used until the snap manifest is read on
|
|
25
|
+
* `deserialize`. Typed (no cast) so that adding a new required
|
|
26
|
+
* `KeyringCapabilities` field surfaces here as a compile error.
|
|
27
|
+
*/
|
|
28
|
+
export const EMPTY_CAPABILITIES = Object.freeze({
|
|
29
|
+
scopes: [],
|
|
30
|
+
});
|
|
21
31
|
/**
|
|
22
32
|
* Superstruct schema for {@link SnapKeyringState}.
|
|
23
33
|
*
|
|
@@ -38,50 +48,103 @@ export function isSnapKeyring(keyring) {
|
|
|
38
48
|
return keyring.type === KeyringType.Snap;
|
|
39
49
|
}
|
|
40
50
|
/**
|
|
41
|
-
* Per-snap keyring
|
|
51
|
+
* Per-snap keyring that implements `Keyring` (v2).
|
|
42
52
|
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* The `KeyringAccountRegistry`, snap client, and messenger are all inherited
|
|
49
|
-
* from `SnapKeyringV1` so there is no duplicated state.
|
|
53
|
+
* Owns the account registry and messenger. For v1 snaps (those that do not
|
|
54
|
+
* declare `endowment:keyring` capabilities in their manifest), a
|
|
55
|
+
* {@link SnapKeyringV1} instance is created on `deserialize` and held under
|
|
56
|
+
* {@link SnapKeyring.v1}. V2 snaps have `v1 === undefined` and communicate
|
|
57
|
+
* directly through the v2 client without the `{ pending, result }` envelope.
|
|
50
58
|
*/
|
|
51
|
-
export class SnapKeyring
|
|
52
|
-
constructor(
|
|
53
|
-
super(options);
|
|
59
|
+
export class SnapKeyring {
|
|
60
|
+
constructor({ messenger, callbacks, isAnyAccountTypeAllowed = false, }) {
|
|
54
61
|
_SnapKeyring_instances.add(this);
|
|
55
|
-
/**
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*/
|
|
62
|
+
/** Account registry — shared by reference with the v1 instance when present. */
|
|
63
|
+
_SnapKeyring_registry.set(this, void 0);
|
|
64
|
+
/** Messenger for snap controller calls and event publishing. */
|
|
65
|
+
_SnapKeyring_messenger.set(this, void 0);
|
|
60
66
|
_SnapKeyring_callbacks.set(this, void 0);
|
|
67
|
+
_SnapKeyring_isAnyAccountTypeAllowed.set(this, void 0);
|
|
61
68
|
/**
|
|
62
69
|
* Mutex that serializes `createAccounts` calls on this snap instance.
|
|
63
70
|
* Owned here so that each `SnapKeyring` is fully self-contained.
|
|
64
71
|
*/
|
|
65
72
|
_SnapKeyring_lock.set(this, void 0);
|
|
73
|
+
/** V2 snap client. Set via {@link bindSnapId}. */
|
|
74
|
+
_SnapKeyring_context.set(this, void 0);
|
|
75
|
+
/**
|
|
76
|
+
* V1 instance, present only when the snap does not declare v2 capabilities.
|
|
77
|
+
* Created on `deserialize()` after reading the snap manifest.
|
|
78
|
+
*/
|
|
79
|
+
_SnapKeyring_v1.set(this, void 0);
|
|
66
80
|
// ──────────────────────────────────────────────
|
|
67
81
|
// Keyring properties
|
|
68
82
|
// ──────────────────────────────────────────────
|
|
69
83
|
this.type = `${KeyringType.Snap}`;
|
|
70
|
-
__classPrivateFieldSet(this,
|
|
84
|
+
__classPrivateFieldSet(this, _SnapKeyring_registry, new KeyringAccountRegistry(), "f");
|
|
85
|
+
__classPrivateFieldSet(this, _SnapKeyring_messenger, messenger, "f");
|
|
86
|
+
__classPrivateFieldSet(this, _SnapKeyring_callbacks, callbacks, "f");
|
|
87
|
+
__classPrivateFieldSet(this, _SnapKeyring_isAnyAccountTypeAllowed, isAnyAccountTypeAllowed, "f");
|
|
71
88
|
__classPrivateFieldSet(this, _SnapKeyring_lock, new Mutex(), "f");
|
|
72
|
-
// Default capabilities
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
// Default capabilities; replaced from the snap manifest on `deserialize`.
|
|
90
|
+
this.capabilities = EMPTY_CAPABILITIES;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Gets the v1 instance for this snap, or `undefined` if the snap is v2-only.
|
|
94
|
+
*
|
|
95
|
+
* @returns The v1 instance, or `undefined` if the snap is v2-only.
|
|
96
|
+
*
|
|
97
|
+
* Use this to make v1 calls explicit:
|
|
98
|
+
* ```ts
|
|
99
|
+
* keyring.v1?.signTransaction(account, tx);
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
get v1() {
|
|
103
|
+
return __classPrivateFieldGet(this, _SnapKeyring_v1, "f");
|
|
76
104
|
}
|
|
77
105
|
/**
|
|
78
|
-
*
|
|
106
|
+
* The snap ID this instance is scoped to.
|
|
79
107
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
108
|
+
* @returns The snap ID.
|
|
109
|
+
* @throws If the keyring has not been initialized yet.
|
|
110
|
+
*/
|
|
111
|
+
get snapId() {
|
|
112
|
+
/* istanbul ignore next */
|
|
113
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_context, "f") === undefined) {
|
|
114
|
+
throw new Error('SnapKeyring has not been initialized: call deserialize() first');
|
|
115
|
+
}
|
|
116
|
+
return __classPrivateFieldGet(this, _SnapKeyring_context, "f").snapId;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Bind this keyring to a snap ID and initialize the v2 client.
|
|
120
|
+
*
|
|
121
|
+
* Idempotent for the same `snapId`; throws if called again with a different
|
|
122
|
+
* one to prevent accidentally swapping a keyring's identity.
|
|
123
|
+
*
|
|
124
|
+
* @param snapId - The snap ID to bind to.
|
|
125
|
+
*/
|
|
126
|
+
bindSnapId(snapId) {
|
|
127
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_context, "f") !== undefined && __classPrivateFieldGet(this, _SnapKeyring_context, "f").snapId !== snapId) {
|
|
128
|
+
throw new Error(`SnapKeyring bound to '${__classPrivateFieldGet(this, _SnapKeyring_context, "f").snapId}' cannot be rebound to '${snapId}'`);
|
|
129
|
+
}
|
|
130
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_context, "f") === undefined) {
|
|
131
|
+
__classPrivateFieldSet(this, _SnapKeyring_context, {
|
|
132
|
+
snapId,
|
|
133
|
+
client: new KeyringInternalSnapClient({
|
|
134
|
+
messenger: __classPrivateFieldGet(this, _SnapKeyring_messenger, "f"),
|
|
135
|
+
snapId,
|
|
136
|
+
}),
|
|
137
|
+
}, "f");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Destroy this keyring, rejecting any pending v1 requests.
|
|
142
|
+
*
|
|
143
|
+
* @returns A promise that resolves when the keyring is destroyed.
|
|
82
144
|
*/
|
|
83
145
|
async destroy() {
|
|
84
|
-
await
|
|
146
|
+
await __classPrivateFieldGet(this, _SnapKeyring_v1, "f")?.destroy();
|
|
147
|
+
__classPrivateFieldSet(this, _SnapKeyring_v1, undefined, "f");
|
|
85
148
|
}
|
|
86
149
|
// ──────────────────────────────────────────────
|
|
87
150
|
// Keyring methods
|
|
@@ -92,6 +155,7 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
92
155
|
* @returns A promise that resolves to an array of all accounts.
|
|
93
156
|
*/
|
|
94
157
|
async getAccounts() {
|
|
158
|
+
__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertInitialized).call(this);
|
|
95
159
|
return this.accounts();
|
|
96
160
|
}
|
|
97
161
|
/**
|
|
@@ -101,6 +165,7 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
101
165
|
* @returns A promise that resolves to the account.
|
|
102
166
|
*/
|
|
103
167
|
async getAccount(accountId) {
|
|
168
|
+
__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertInitialized).call(this);
|
|
104
169
|
const account = this.lookupAccount(accountId);
|
|
105
170
|
if (!account) {
|
|
106
171
|
throw new Error(`Account '${accountId}' not found in snap '${this.snapId}'`);
|
|
@@ -123,19 +188,30 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
123
188
|
* @returns A promise that resolves to an array of the created account objects.
|
|
124
189
|
*/
|
|
125
190
|
async createAccounts(options) {
|
|
191
|
+
__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertInitialized).call(this);
|
|
126
192
|
return __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_withLock).call(this, async () => {
|
|
127
193
|
// Keep track of address/account ID part of this batch, to avoid having duplicates.
|
|
128
194
|
const batchAddresses = new Set();
|
|
129
195
|
const batchIds = new Set();
|
|
130
196
|
const accounts = [];
|
|
131
197
|
const newAccounts = [];
|
|
132
|
-
|
|
198
|
+
let snapAccounts;
|
|
199
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_v1, "f")) {
|
|
200
|
+
// v1 snap: the v1 client wraps options in `{ options }` before sending
|
|
201
|
+
// `keyring_createAccounts`, while the v2 client sends flat options.
|
|
202
|
+
// Route through v1 so v1 snaps receive the format they expect.
|
|
203
|
+
snapAccounts = await __classPrivateFieldGet(this, _SnapKeyring_v1, "f").createAccounts(options);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// v2 snap: call snap directly with flat options.
|
|
207
|
+
snapAccounts = await __classPrivateFieldGet(this, _SnapKeyring_instances, "a", _SnapKeyring_client_get).createAccounts(options);
|
|
208
|
+
}
|
|
133
209
|
try {
|
|
134
210
|
for (const snapAccount of snapAccounts) {
|
|
135
211
|
let account = normalizeAccount(transformAccount(snapAccount));
|
|
136
212
|
const { address } = account;
|
|
137
213
|
// Check for idempotency.
|
|
138
|
-
const existingAccount = this.
|
|
214
|
+
const existingAccount = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getExistingAccount).call(this, account);
|
|
139
215
|
if (existingAccount) {
|
|
140
216
|
// NOTE: We re-use the account from the internal state to avoid having the Snap
|
|
141
217
|
// mutating the account object without updating the map.
|
|
@@ -172,9 +248,9 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
172
248
|
// Rollback Snap state.
|
|
173
249
|
for (const snapAccount of snapAccounts) {
|
|
174
250
|
// Make sure to only delete accounts that were not part of the keyring state.
|
|
175
|
-
if (!this.
|
|
251
|
+
if (!__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_getExistingAccount).call(this, snapAccount)) {
|
|
176
252
|
try {
|
|
177
|
-
await this.
|
|
253
|
+
await __classPrivateFieldGet(this, _SnapKeyring_instances, "a", _SnapKeyring_client_get).deleteAccount(snapAccount.id);
|
|
178
254
|
}
|
|
179
255
|
catch (rollbackError) {
|
|
180
256
|
// Best-effort rollback; log snap-side failures for observability.
|
|
@@ -195,11 +271,12 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
195
271
|
* @param accountId - ID of the account to delete.
|
|
196
272
|
*/
|
|
197
273
|
async deleteAccount(accountId) {
|
|
274
|
+
__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertInitialized).call(this);
|
|
198
275
|
// Always remove the account from the registry, even if the Snap is going to
|
|
199
276
|
// fail to delete it. removeAccount fires onUnregister to clean #accountIndex.
|
|
200
277
|
this.removeAccount(accountId);
|
|
201
278
|
try {
|
|
202
|
-
await this.
|
|
279
|
+
await __classPrivateFieldGet(this, _SnapKeyring_instances, "a", _SnapKeyring_client_get).deleteAccount(accountId);
|
|
203
280
|
}
|
|
204
281
|
catch (error) {
|
|
205
282
|
// If the Snap failed to delete the account, log the error and continue
|
|
@@ -211,12 +288,13 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
211
288
|
/**
|
|
212
289
|
* Submits a request to the keyring.
|
|
213
290
|
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
291
|
+
* For v1 snaps (those without declared capabilities), delegates to the v1
|
|
292
|
+
* event-driven flow that handles the `{ pending, result }` envelope.
|
|
293
|
+
* For v2 snaps, calls the snap directly via the v2 client which returns
|
|
294
|
+
* `Json` with no envelope.
|
|
217
295
|
*
|
|
218
296
|
* @param request - The keyring request to submit.
|
|
219
|
-
* @param request.id - The request ID
|
|
297
|
+
* @param request.id - The request ID.
|
|
220
298
|
* @param request.origin - The sender origin.
|
|
221
299
|
* @param request.scope - The CAIP-2 chain ID.
|
|
222
300
|
* @param request.account - The account ID.
|
|
@@ -226,17 +304,29 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
226
304
|
* @returns A promise that resolves to the response.
|
|
227
305
|
*/
|
|
228
306
|
async submitRequest(request) {
|
|
307
|
+
__classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_assertInitialized).call(this);
|
|
229
308
|
const account = this.lookupAccount(request.account);
|
|
230
309
|
if (!account) {
|
|
231
310
|
throw new Error(`Account '${request.account}' not found in snap '${this.snapId}'`);
|
|
232
311
|
}
|
|
233
|
-
|
|
312
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_v1, "f")) {
|
|
313
|
+
// v1 snap: use event-driven flow with { pending, result } envelope handling.
|
|
314
|
+
return __classPrivateFieldGet(this, _SnapKeyring_v1, "f").submitSnapRequest({
|
|
315
|
+
origin: request.origin,
|
|
316
|
+
account,
|
|
317
|
+
method: request.request.method,
|
|
318
|
+
params: request.request.params,
|
|
319
|
+
scope: request.scope,
|
|
320
|
+
noPending: false,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
// v2 snap: call snap directly, returns Json (no envelope).
|
|
324
|
+
return __classPrivateFieldGet(this, _SnapKeyring_instances, "a", _SnapKeyring_client_get).submitRequest({
|
|
325
|
+
id: request.id,
|
|
234
326
|
origin: request.origin,
|
|
235
|
-
account,
|
|
236
|
-
method: request.request.method,
|
|
237
|
-
params: request.request.params,
|
|
238
327
|
scope: request.scope,
|
|
239
|
-
|
|
328
|
+
account: request.account,
|
|
329
|
+
request: request.request,
|
|
240
330
|
});
|
|
241
331
|
}
|
|
242
332
|
// ──────────────────────────────────────────────
|
|
@@ -252,8 +342,8 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
252
342
|
* @param account - The account to add or update.
|
|
253
343
|
*/
|
|
254
344
|
setAccount(account) {
|
|
255
|
-
const isNew = !this.
|
|
256
|
-
this.
|
|
345
|
+
const isNew = !__classPrivateFieldGet(this, _SnapKeyring_registry, "f").has(account.id);
|
|
346
|
+
__classPrivateFieldGet(this, _SnapKeyring_registry, "f").set(account);
|
|
257
347
|
if (isNew) {
|
|
258
348
|
__classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").onRegister?.(account.id);
|
|
259
349
|
}
|
|
@@ -267,10 +357,10 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
267
357
|
* @returns `true` if the account was removed, `false` if it was not found.
|
|
268
358
|
*/
|
|
269
359
|
removeAccount(id) {
|
|
270
|
-
if (!this.
|
|
360
|
+
if (!__classPrivateFieldGet(this, _SnapKeyring_registry, "f").has(id)) {
|
|
271
361
|
return false;
|
|
272
362
|
}
|
|
273
|
-
this.
|
|
363
|
+
__classPrivateFieldGet(this, _SnapKeyring_registry, "f").delete(id);
|
|
274
364
|
__classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").onUnregister?.(id);
|
|
275
365
|
return true;
|
|
276
366
|
}
|
|
@@ -281,7 +371,7 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
281
371
|
* @returns `true` if the account exists.
|
|
282
372
|
*/
|
|
283
373
|
hasAccount(id) {
|
|
284
|
-
return this.
|
|
374
|
+
return __classPrivateFieldGet(this, _SnapKeyring_registry, "f").has(id);
|
|
285
375
|
}
|
|
286
376
|
/**
|
|
287
377
|
* Get an account by its ID.
|
|
@@ -290,7 +380,7 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
290
380
|
* @returns The account, or `undefined` if not found.
|
|
291
381
|
*/
|
|
292
382
|
lookupAccount(id) {
|
|
293
|
-
return this.
|
|
383
|
+
return __classPrivateFieldGet(this, _SnapKeyring_registry, "f").get(id);
|
|
294
384
|
}
|
|
295
385
|
/**
|
|
296
386
|
* Get an account by address (case-insensitive).
|
|
@@ -302,15 +392,15 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
302
392
|
* @returns The account, or `undefined` if not found.
|
|
303
393
|
*/
|
|
304
394
|
lookupByAddress(address) {
|
|
305
|
-
const id = this.
|
|
395
|
+
const id = __classPrivateFieldGet(this, _SnapKeyring_registry, "f").getAccountId(address);
|
|
306
396
|
if (id !== undefined) {
|
|
307
|
-
return this.
|
|
397
|
+
return __classPrivateFieldGet(this, _SnapKeyring_registry, "f").get(id);
|
|
308
398
|
}
|
|
309
399
|
// The fallback only runs when the exact-match branch above misses,
|
|
310
400
|
// which in practice only happens for EVM addresses with casing
|
|
311
401
|
// differences (checksummed vs lowercase). Non-EVM addresses are
|
|
312
402
|
// case-sensitive and always resolve on the exact branch.
|
|
313
|
-
return this
|
|
403
|
+
return __classPrivateFieldGet(this, _SnapKeyring_registry, "f")
|
|
314
404
|
.values()
|
|
315
405
|
.find((account) => equalsIgnoreCase(account.address, address));
|
|
316
406
|
}
|
|
@@ -324,7 +414,7 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
324
414
|
* @returns An array of all accounts.
|
|
325
415
|
*/
|
|
326
416
|
accounts() {
|
|
327
|
-
return this.
|
|
417
|
+
return __classPrivateFieldGet(this, _SnapKeyring_registry, "f").values();
|
|
328
418
|
}
|
|
329
419
|
/**
|
|
330
420
|
* Serialize this keyring's state.
|
|
@@ -337,7 +427,7 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
337
427
|
*/
|
|
338
428
|
async serialize() {
|
|
339
429
|
const accounts = {};
|
|
340
|
-
for (const account of this.
|
|
430
|
+
for (const account of __classPrivateFieldGet(this, _SnapKeyring_registry, "f").values()) {
|
|
341
431
|
accounts[account.id] = account;
|
|
342
432
|
}
|
|
343
433
|
const state = {
|
|
@@ -349,9 +439,11 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
349
439
|
/**
|
|
350
440
|
* Restore this keyring from a serialized state.
|
|
351
441
|
*
|
|
352
|
-
* Validates the payload (accepting both v1 and v2
|
|
353
|
-
* v1 accounts to v2, then replaces the registry.
|
|
354
|
-
*
|
|
442
|
+
* Validates the payload (accepting both v1 and v2 account shapes), migrates
|
|
443
|
+
* any v1 accounts to v2, then replaces the registry. Also determines whether
|
|
444
|
+
* the snap is v1 or v2 by reading its manifest capabilities: if no
|
|
445
|
+
* capabilities are declared, a {@link SnapKeyringV1} instance is created and
|
|
446
|
+
* held under {@link SnapKeyring.v1}.
|
|
355
447
|
*
|
|
356
448
|
* @param state - The state to deserialize.
|
|
357
449
|
* @returns A promise that resolves when deserialization is complete.
|
|
@@ -362,6 +454,31 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
362
454
|
// Bind the keyring to its snap ID (idempotent for the same ID, throws on
|
|
363
455
|
// mismatch to prevent swapping a keyring's identity via deserialize).
|
|
364
456
|
this.bindSnapId(state.snapId);
|
|
457
|
+
// Refresh capabilities from the snap manifest on every deserialize, falling
|
|
458
|
+
// back to the empty default so a re-hydrate clears any previously-loaded
|
|
459
|
+
// capabilities when the snap no longer declares them.
|
|
460
|
+
const capabilities = __classPrivateFieldGet(this, _SnapKeyring_instances, "m", _SnapKeyring_resolveKeyringCapabilities).call(this);
|
|
461
|
+
this.capabilities = capabilities ?? EMPTY_CAPABILITIES;
|
|
462
|
+
// Determine snap version and create a v1 instance if needed.
|
|
463
|
+
const v1 = capabilities === undefined;
|
|
464
|
+
if (v1) {
|
|
465
|
+
// v1 snap: no declared capabilities. Create a SnapKeyringV1 instance
|
|
466
|
+
// that shares the registry and messenger owned by this class.
|
|
467
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_v1, "f") === undefined) {
|
|
468
|
+
__classPrivateFieldSet(this, _SnapKeyring_v1, new SnapKeyringV1({
|
|
469
|
+
messenger: __classPrivateFieldGet(this, _SnapKeyring_messenger, "f"),
|
|
470
|
+
callbacks: __classPrivateFieldGet(this, _SnapKeyring_callbacks, "f"),
|
|
471
|
+
registry: __classPrivateFieldGet(this, _SnapKeyring_registry, "f"),
|
|
472
|
+
isAnyAccountTypeAllowed: __classPrivateFieldGet(this, _SnapKeyring_isAnyAccountTypeAllowed, "f"),
|
|
473
|
+
}), "f");
|
|
474
|
+
__classPrivateFieldGet(this, _SnapKeyring_v1, "f").bindSnapId(state.snapId);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
else {
|
|
478
|
+
// v2 snap: tear down any stale v1 instance (e.g. after a manifest update).
|
|
479
|
+
await __classPrivateFieldGet(this, _SnapKeyring_v1, "f")?.destroy();
|
|
480
|
+
__classPrivateFieldSet(this, _SnapKeyring_v1, undefined, "f");
|
|
481
|
+
}
|
|
365
482
|
// Migrate v1 accounts to v2.
|
|
366
483
|
const migratedAccounts = {};
|
|
367
484
|
for (const [id, rawAccount] of Object.entries(state.accounts)) {
|
|
@@ -374,7 +491,7 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
374
491
|
}
|
|
375
492
|
}
|
|
376
493
|
// Apply the migrated state to the registry.
|
|
377
|
-
for (const id of [...this.
|
|
494
|
+
for (const id of [...__classPrivateFieldGet(this, _SnapKeyring_registry, "f").keys()]) {
|
|
378
495
|
this.removeAccount(id);
|
|
379
496
|
}
|
|
380
497
|
for (const account of Object.values(migratedAccounts)) {
|
|
@@ -382,14 +499,20 @@ export class SnapKeyring extends SnapKeyringV1 {
|
|
|
382
499
|
}
|
|
383
500
|
}
|
|
384
501
|
}
|
|
385
|
-
_SnapKeyring_callbacks = new WeakMap(), _SnapKeyring_lock = new WeakMap(), _SnapKeyring_instances = new WeakSet(),
|
|
502
|
+
_SnapKeyring_registry = new WeakMap(), _SnapKeyring_messenger = new WeakMap(), _SnapKeyring_callbacks = new WeakMap(), _SnapKeyring_isAnyAccountTypeAllowed = new WeakMap(), _SnapKeyring_lock = new WeakMap(), _SnapKeyring_context = new WeakMap(), _SnapKeyring_v1 = new WeakMap(), _SnapKeyring_instances = new WeakSet(), _SnapKeyring_client_get = function _SnapKeyring_client_get() {
|
|
503
|
+
/* istanbul ignore next */
|
|
504
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_context, "f") === undefined) {
|
|
505
|
+
throw new Error('SnapKeyring is not bound to a snap ID');
|
|
506
|
+
}
|
|
507
|
+
return __classPrivateFieldGet(this, _SnapKeyring_context, "f").client;
|
|
508
|
+
}, _SnapKeyring_withLock =
|
|
386
509
|
/**
|
|
387
510
|
* Run a callback under the appropriate lock.
|
|
388
511
|
*
|
|
389
512
|
* Prefers the injected `withLock` callback (global lock provided by
|
|
390
513
|
* `SnapKeyring`) so that `createAccounts` calls across different snaps
|
|
391
514
|
* are serialized. Falls back to the per-instance `#lock` when no global
|
|
392
|
-
* lock is provided.g. standalone use in tests).
|
|
515
|
+
* lock is provided (e.g. standalone use in tests).
|
|
393
516
|
*
|
|
394
517
|
* @param callback - Operation to run under the lock.
|
|
395
518
|
* @returns The result of the callback.
|
|
@@ -397,6 +520,27 @@ _SnapKeyring_callbacks = new WeakMap(), _SnapKeyring_lock = new WeakMap(), _Snap
|
|
|
397
520
|
async function _SnapKeyring_withLock(callback) {
|
|
398
521
|
return (__classPrivateFieldGet(this, _SnapKeyring_callbacks, "f").withLock ??
|
|
399
522
|
(async (operation) => __classPrivateFieldGet(this, _SnapKeyring_lock, "f").runExclusive(operation)))(callback);
|
|
523
|
+
}, _SnapKeyring_assertInitialized = function _SnapKeyring_assertInitialized() {
|
|
524
|
+
if (__classPrivateFieldGet(this, _SnapKeyring_context, "f") === undefined) {
|
|
525
|
+
throw new Error('SnapKeyring has not been initialized: call deserialize() first');
|
|
526
|
+
}
|
|
527
|
+
}, _SnapKeyring_resolveKeyringCapabilities = function _SnapKeyring_resolveKeyringCapabilities() {
|
|
528
|
+
const snap = __classPrivateFieldGet(this, _SnapKeyring_messenger, "f").call('SnapController:getSnap', this.snapId);
|
|
529
|
+
// READ THIS CAREFULLY:
|
|
530
|
+
// We are not validating the shape of the capabilities here, because there is
|
|
531
|
+
// manifest validation done already on the snaps side, the snaps repo maintains
|
|
532
|
+
// a copy of the `KeyringCapabilitiesStruct`.
|
|
533
|
+
// We must ensure that both structs are always in-sync, otherwise the type-cast
|
|
534
|
+
// could cause runtime issues!
|
|
535
|
+
return snap?.manifest.initialPermissions['endowment:keyring']
|
|
536
|
+
?.capabilities;
|
|
537
|
+
}, _SnapKeyring_getExistingAccount = function _SnapKeyring_getExistingAccount(account) {
|
|
538
|
+
const address = normalizeAccountAddress(account);
|
|
539
|
+
const existing = __classPrivateFieldGet(this, _SnapKeyring_registry, "f").get(account.id);
|
|
540
|
+
if (existing && normalizeAccountAddress(existing) === address) {
|
|
541
|
+
return existing;
|
|
542
|
+
}
|
|
543
|
+
return undefined;
|
|
400
544
|
};
|
|
401
545
|
SnapKeyring.type = `${KeyringType.Snap}`;
|
|
402
546
|
//# sourceMappingURL=SnapKeyring.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SnapKeyring.mjs","sourceRoot":"","sources":["../../src/v2/SnapKeyring.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,oBAAoB,EAAE,8BAA8B;AAM7D,OAAO,EAAE,WAAW,EAAE,iCAAiC;AAIvD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,8BAA8B;AAE9E,OAAO,EAAE,KAAK,EAAE,oBAAoB;AAEpC,OAAO,EACL,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EACjB,uBAAmB;AACpB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,gCAAsB;AAC9D,OAAO,EAAE,aAAa,EAAE,6BAAyB;AAMjD,OAAO,EAAE,gBAAgB,EAAE,oBAAgB;AAE3C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;IAC3C,MAAM,EAAE,MAAM,EAAE;IAChB,QAAQ,EAAE,MAAM,CACd,MAAM,EAAE,EACR,KAAK,CAAC,CAAC,oBAAoB,EAAE,sBAAsB,CAAC,CAAC,CACtD;CACF,CAAC,CAAC;AAkCH;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,OAAO,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,OAAO,WAAY,SAAQ,aAAa;IA4B5C,YAAY,OAA2B;QACrC,KAAK,CAAC,OAAO,CAAC,CAAC;;QA5BjB;;;;WAIG;QACM,yCAAiC;QAE1C;;;WAGG;QACM,oCAAa;QAEtB,iDAAiD;QACjD,qBAAqB;QACrB,iDAAiD;QAExC,SAAI,GAAG,GAAG,WAAW,CAAC,IAAI,EAAW,CAAC;QAY7C,uBAAA,IAAI,0BAAc,OAAO,CAAC,SAAS,MAAA,CAAC;QACpC,uBAAA,IAAI,qBAAS,IAAI,KAAK,EAAE,MAAA,CAAC;QAEzB,8EAA8E;QAC9E,8EAA8E;QAC9E,0FAA0F;QAC1F,IAAI,CAAC,YAAY,GAAG,EAAE,MAAM,EAAE,EAAE,EAAoC,CAAC;IACvE,CAAC;IAED;;;;;OAKG;IACM,KAAK,CAAC,OAAO;QACpB,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAqBD,iDAAiD;IACjD,kBAAkB;IAClB,iDAAiD;IAEjD;;;;OAIG;IACH,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;IACzB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CAAC,SAAoB;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,YAAY,SAAS,wBAAwB,IAAI,CAAC,MAAM,GAAG,CAC5D,CAAC;QACJ,CAAC;QACD,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,cAAc,CAClB,OAA6B;QAE7B,OAAO,uBAAA,IAAI,qDAAU,MAAd,IAAI,EAAW,KAAK,IAAI,EAAE;YAC/B,mFAAmF;YACnF,MAAM,cAAc,GAAG,IAAI,GAAG,EAAU,CAAC;YACzC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;YAEnC,MAAM,QAAQ,GAAqB,EAAE,CAAC;YACtC,MAAM,WAAW,GAAqB,EAAE,CAAC;YACzC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YAE/D,IAAI,CAAC;gBACH,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;oBACvC,IAAI,OAAO,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC,CAAC;oBAC9D,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;oBAE5B,yBAAyB;oBACzB,MAAM,eAAe,GAAG,IAAI,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC;oBACzD,IAAI,eAAe,EAAE,CAAC;wBACpB,+EAA+E;wBAC/E,wDAAwD;wBACxD,OAAO,GAAG,eAAe,CAAC;oBAC5B,CAAC;yBAAM,CAAC;wBACN,MAAM,uBAAA,IAAI,8BAAW,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;wBAEtD,yEAAyE;wBACzE,SAAS;wBACT,IAAI,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,CAAC;4BAC5D,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,CAAC,EAAE,8DAA8D,CACrF,CAAC;wBACJ,CAAC;wBACD,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;wBAC5B,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;wBAEzB,uEAAuE;wBACvE,yEAAyE;wBACzE,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;oBAC5B,CAAC;oBAED,uEAAuE;oBACvE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACzB,CAAC;gBAED,8CAA8C;gBAC9C,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBAC3B,KAAK,MAAM,OAAO,IAAI,WAAW,EAAE,CAAC;wBAClC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;oBAC3B,CAAC;oBAED,4EAA4E;oBAC5E,6CAA6C;oBAC7C,MAAM,uBAAA,IAAI,8BAAW,CAAC,SAAS,EAAE,CAAC;gBACpC,CAAC;gBAED,OAAO,QAAQ,CAAC;YAClB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,uBAAuB;gBACvB,KAAK,MAAM,WAAW,IAAI,YAAY,EAAE,CAAC;oBACvC,6EAA6E;oBAC7E,IAAI,CAAC,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;wBAC1C,IAAI,CAAC;4BACH,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;wBAClD,CAAC;wBAAC,OAAO,aAAa,EAAE,CAAC;4BACvB,kEAAkE;4BAClE,OAAO,CAAC,KAAK,CACX,YAAY,WAAW,CAAC,EAAE,0CAA0C,IAAI,CAAC,MAAM,mCAAmC,EAClH,aAAa,CACd,CAAC;wBACJ,CAAC;oBACH,CAAC;gBACH,CAAC;gBAED,MAAM,KAAK,CAAC;YACd,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,aAAa,CAAC,SAAoB;QACtC,4EAA4E;QAC5E,8EAA8E;QAC9E,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAE9B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAC7C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,uEAAuE;YACvE,wEAAwE;YACxE,WAAW;YACX,OAAO,CAAC,KAAK,CACX,YAAY,SAAS,0CAA0C,IAAI,CAAC,MAAM,IAAI,EAC9E,KAAK,CACN,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,aAAa,CAAC,OASnB;QACC,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACpD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,CAAC,OAAO,wBAAwB,IAAI,CAAC,MAAM,GAAG,CAClE,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,CAAC,iBAAiB,CAAC;YAC5B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO;YACP,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAuB;YAC/C,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM;YAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC;IACL,CAAC;IAED,iDAAiD;IACjD,8EAA8E;IAC9E,iDAAiD;IAEjD;;;;;;;;OAQG;IACH,UAAU,CAAC,OAAuB;QAChC,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QAC3B,IAAI,KAAK,EAAE,CAAC;YACV,uBAAA,IAAI,8BAAW,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,aAAa,CAAC,EAAa;QACzB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzB,uBAAA,IAAI,8BAAW,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;QACnC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACH,UAAU,CAAC,EAAa;QACtB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;OAKG;IACH,aAAa,CAAC,EAAa;QACzB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;;OAQG;IACH,eAAe,CAAC,OAAe;QAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;QAC/C,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/B,CAAC;QACD,mEAAmE;QACnE,+DAA+D;QAC/D,gEAAgE;QAChE,yDAAyD;QACzD,OAAO,IAAI,CAAC,QAAQ;aACjB,MAAM,EAAE;aACR,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,gBAAgB,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IACnE,CAAC;IAED;;;;;;;;OAQG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;IAChC,CAAC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,SAAS;QACb,MAAM,QAAQ,GAAiC,EAAE,CAAC;QAClD,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC;QACjC,CAAC;QACD,MAAM,KAAK,GAAqB;YAC9B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ;SACT,CAAC;QACF,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;;;;;;;;OASG;IACH,KAAK,CAAC,WAAW,CAAC,KAAW;QAC3B,oEAAoE;QACpE,MAAM,CAAC,KAAK,EAAE,sBAAsB,CAAC,CAAC;QAEtC,yEAAyE;QACzE,sEAAsE;QACtE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAgB,CAAC,CAAC;QAExC,6BAA6B;QAC7B,MAAM,gBAAgB,GAAmC,EAAE,CAAC;QAC5D,KAAK,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9D,IAAI,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC5B,OAAO,CAAC,IAAI,CACV,4DAA4D,UAAU,CAAC,EAAE,EAAE,CAC5E,CAAC;gBACF,gBAAgB,CAAC,EAAE,CAAC,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;YACtD,CAAC;iBAAM,CAAC;gBACN,gBAAgB,CAAC,EAAE,CAAC,GAAG,UAA4B,CAAC;YACtD,CAAC;QACH,CAAC;QAED,4CAA4C;QAC5C,KAAK,MAAM,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC3C,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC;QACzB,CAAC;QAED,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,MAAM,CAAC,gBAAgB,CAAC,EAAE,CAAC;YACtD,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;;;AA7WD;;;;;;;;;;GAUG;AACH,KAAK,gCAAmB,QAA+B;IACrD,OAAO,CACL,uBAAA,IAAI,8BAAW,CAAC,QAAQ;QACxB,CAAC,KAAK,EAAE,SAAgC,EAAmB,EAAE,CAC3D,uBAAA,IAAI,yBAAM,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CACtC,CAAC,QAAQ,CAAC,CAAC;AACd,CAAC;AA9Ce,gBAAI,GAAG,GAAG,WAAW,CAAC,IAAI,EAAW,AAAjC,CAAkC","sourcesContent":["import type { KeyringAccount } from '@metamask/keyring-api';\nimport { KeyringAccountStruct } from '@metamask/keyring-api';\nimport type {\n CreateAccountOptions,\n Keyring,\n KeyringCapabilities,\n} from '@metamask/keyring-api/v2';\nimport { KeyringType } from '@metamask/keyring-api/v2';\nimport type { AccountId } from '@metamask/keyring-utils';\nimport type { SnapId } from '@metamask/snaps-sdk';\nimport type { Infer } from '@metamask/superstruct';\nimport { assert, object, record, string, union } from '@metamask/superstruct';\nimport type { Json } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\n\nimport {\n KeyringAccountV1Struct,\n normalizeAccount,\n transformAccount,\n} from '../account';\nimport { isAccountV1, migrateAccountV1 } from '../migrations';\nimport { SnapKeyringV1 } from '../SnapKeyringV1';\nimport type {\n AccountMethod,\n SnapKeyringV1Callbacks,\n SnapKeyringV1Options,\n} from '../SnapKeyringV1';\nimport { equalsIgnoreCase } from '../util';\n\n/**\n * Superstruct schema for {@link SnapKeyringState}.\n *\n * Accepts both v1 accounts (missing `scopes`) and v2 accounts so that\n * persisted state can be validated before migration.\n */\nexport const SnapKeyringStateStruct = object({\n snapId: string(),\n accounts: record(\n string(),\n union([KeyringAccountStruct, KeyringAccountV1Struct]),\n ),\n});\n\n/**\n * Serialized state of a single SnapKeyring instance.\n *\n * Note: this is an internal format only used between SnapKeyring and its\n * parent SnapKeyring. The external KeyringState format (flat `{ account,\n * snapId }` map) is preserved by SnapKeyring.serialize / deserialize.\n *\n * Inferred from {@link SnapKeyringStateStruct}: `snapId` is `string`\n * (not the branded `SnapId`) so the shape stays JSON-compatible without\n * unsafe casts.\n */\nexport type SnapKeyringState = Infer<typeof SnapKeyringStateStruct>;\n\n/**\n * Callbacks injected by the parent `SnapKeyring` for global coordination.\n */\nexport type SnapKeyringCallbacks = SnapKeyringV1Callbacks & {\n /**\n * Run a callback under a global lock to prevent TOCTOU races in\n * `createAccounts` when multiple snaps call `assertAccountCanBeUsed`\n * concurrently.\n *\n * Optional: if omitted, `SnapKeyringV2` falls back to its own per-instance\n * `Mutex`, which is sufficient when the keyring is used standalone.\n */\n withLock?: <Result>(callback: () => Promise<Result>) => Promise<Result>;\n};\n\ntype SnapKeyringOptions = Omit<SnapKeyringV1Options, 'callbacks'> & {\n callbacks: SnapKeyringCallbacks;\n};\n\n/**\n * Checks if a given keyring is a Snap keyring (v2).\n *\n * @param keyring - The keyring to check.\n * @returns `true` if the keyring is a Snap keyring (v2), `false` otherwise.\n */\nexport function isSnapKeyring(keyring: Keyring): keyring is SnapKeyring {\n return keyring.type === KeyringType.Snap;\n}\n\n/**\n * Per-snap keyring wrapper that implements `Keyring`.\n *\n * Extends `SnapKeyringV1` — the v1 event-driven flow (account lifecycle,\n * request handling, asset events) is inherited. This class adds the\n * `KeyringV2` batch interface (`createAccounts`, `deleteAccount`, etc.) and\n * owns the `capabilities` property.\n *\n * The `KeyringAccountRegistry`, snap client, and messenger are all inherited\n * from `SnapKeyringV1` so there is no duplicated state.\n */\nexport class SnapKeyring extends SnapKeyringV1 implements Keyring {\n /**\n * V2-typed view of the callbacks. Stored separately from the V1 private\n * field so that V2-specific callbacks are accessible without casting.\n * Both fields hold the same object reference.\n */\n readonly #callbacks: SnapKeyringCallbacks;\n\n /**\n * Mutex that serializes `createAccounts` calls on this snap instance.\n * Owned here so that each `SnapKeyring` is fully self-contained.\n */\n readonly #lock: Mutex;\n\n // ──────────────────────────────────────────────\n // Keyring properties\n // ──────────────────────────────────────────────\n\n readonly type = `${KeyringType.Snap}` as const;\n\n static readonly type = `${KeyringType.Snap}` as const;\n\n /**\n * Capabilities are snap-specific. Initialized empty and can be updated\n * by the parent when snap metadata becomes available.\n */\n capabilities: KeyringCapabilities;\n\n constructor(options: SnapKeyringOptions) {\n super(options);\n this.#callbacks = options.callbacks;\n this.#lock = new Mutex();\n\n // Default capabilities — parent updates this when snap metadata is available.\n // We cast here because KeyringCapabilities requires a non-empty scopes array,\n // but we don't have the snap metadata at construction time (e.g. during deserialization).\n this.capabilities = { scopes: [] } as unknown as KeyringCapabilities;\n }\n\n /**\n * Destroy this keyring.\n *\n * Delegates to the parent `SnapKeyringV1.destroy()` to reject any pending\n * requests inherited from the v1 flow.\n */\n override async destroy(): Promise<void> {\n await super.destroy();\n }\n\n /**\n * Run a callback under the appropriate lock.\n *\n * Prefers the injected `withLock` callback (global lock provided by\n * `SnapKeyring`) so that `createAccounts` calls across different snaps\n * are serialized. Falls back to the per-instance `#lock` when no global\n * lock is provided.g. standalone use in tests).\n *\n * @param callback - Operation to run under the lock.\n * @returns The result of the callback.\n */\n async #withLock<Result>(callback: () => Promise<Result>): Promise<Result> {\n return (\n this.#callbacks.withLock ??\n (async (operation: () => Promise<Result>): Promise<Result> =>\n this.#lock.runExclusive(operation))\n )(callback);\n }\n\n // ──────────────────────────────────────────────\n // Keyring methods\n // ──────────────────────────────────────────────\n\n /**\n * Returns all accounts managed by this keyring.\n *\n * @returns A promise that resolves to an array of all accounts.\n */\n async getAccounts(): Promise<KeyringAccount[]> {\n return this.accounts();\n }\n\n /**\n * Returns the account with the specified ID.\n *\n * @param accountId - ID of the account to retrieve.\n * @returns A promise that resolves to the account.\n */\n async getAccount(accountId: AccountId): Promise<KeyringAccount> {\n const account = this.lookupAccount(accountId);\n if (!account) {\n throw new Error(\n `Account '${accountId}' not found in snap '${this.snapId}'`,\n );\n }\n return account;\n }\n\n /**\n * Creates one or more new accounts according to the provided options.\n *\n * Deterministic account creation MUST be idempotent, meaning that for\n * deterministic algorithms, like BIP-44, calling this method with the same\n * options should always return the same accounts, even if the accounts\n * already exist in the keyring.\n *\n * NOTE: If some accounts are not allowed (non-unique address, unsupported\n * generic account), this method will skip their creation and ask the Snap\n * to remove them from its state.\n *\n * @param options - Options describing how to create the account(s).\n * @returns A promise that resolves to an array of the created account objects.\n */\n async createAccounts(\n options: CreateAccountOptions,\n ): Promise<KeyringAccount[]> {\n return this.#withLock(async () => {\n // Keep track of address/account ID part of this batch, to avoid having duplicates.\n const batchAddresses = new Set<string>();\n const batchIds = new Set<string>();\n\n const accounts: KeyringAccount[] = [];\n const newAccounts: KeyringAccount[] = [];\n const snapAccounts = await this.client.createAccounts(options);\n\n try {\n for (const snapAccount of snapAccounts) {\n let account = normalizeAccount(transformAccount(snapAccount));\n const { address } = account;\n\n // Check for idempotency.\n const existingAccount = this.getExistingAccount(account);\n if (existingAccount) {\n // NOTE: We re-use the account from the internal state to avoid having the Snap\n // mutating the account object without updating the map.\n account = existingAccount;\n } else {\n await this.#callbacks.assertAccountCanBeUsed(account);\n\n // Also check for transient accounts that are not yet part of the keyring\n // state.\n if (batchAddresses.has(address) || batchIds.has(account.id)) {\n throw new Error(\n `Account '${account.id}' is already part of this batch (same address or account ID)`,\n );\n }\n batchAddresses.add(address);\n batchIds.add(account.id);\n\n // NOTE: This method does not rely on the `AccountCreated` event to add\n // accounts to the keyring, so we have to add them to the state manually.\n newAccounts.push(account);\n }\n\n // New AND existing accounts are returned to the caller no matter what.\n accounts.push(account);\n }\n\n // We update the keyring state only if needed.\n if (newAccounts.length > 0) {\n for (const account of newAccounts) {\n this.setAccount(account);\n }\n\n // NOTE: We assume this will never fail, thus, we don't need to rollback the\n // keyring state if anything goes wrong here.\n await this.#callbacks.saveState();\n }\n\n return accounts;\n } catch (error) {\n // Rollback Snap state.\n for (const snapAccount of snapAccounts) {\n // Make sure to only delete accounts that were not part of the keyring state.\n if (!this.getExistingAccount(snapAccount)) {\n try {\n await this.client.deleteAccount(snapAccount.id);\n } catch (rollbackError) {\n // Best-effort rollback; log snap-side failures for observability.\n console.error(\n `Account '${snapAccount.id}' may not have been removed from snap '${this.snapId}' during createAccounts rollback:`,\n rollbackError,\n );\n }\n }\n }\n\n throw error;\n }\n });\n }\n\n /**\n * Deletes the account with the specified ID.\n *\n * Removes the account from the local registry (firing `onUnregister` so the\n * parent can update its index), then asks the snap to delete it.\n *\n * @param accountId - ID of the account to delete.\n */\n async deleteAccount(accountId: AccountId): Promise<void> {\n // Always remove the account from the registry, even if the Snap is going to\n // fail to delete it. removeAccount fires onUnregister to clean #accountIndex.\n this.removeAccount(accountId);\n\n try {\n await this.client.deleteAccount(accountId);\n } catch (error) {\n // If the Snap failed to delete the account, log the error and continue\n // with the account deletion, otherwise the account will be stuck in the\n // keyring.\n console.error(\n `Account '${accountId}' may not have been removed from snap '${this.snapId}':`,\n error,\n );\n }\n }\n\n /**\n * Submits a request to the keyring.\n *\n * Validates that the account belongs to this wrapper, then delegates\n * to the inherited `submitSnapRequest` for the full request lifecycle\n * (sync / async / redirect).\n *\n * @param request - The keyring request to submit.\n * @param request.id - The request ID (unused — a fresh ID is generated internally).\n * @param request.origin - The sender origin.\n * @param request.scope - The CAIP-2 chain ID.\n * @param request.account - The account ID.\n * @param request.request - The inner JSON-RPC request.\n * @param request.request.method - The method to call.\n * @param request.request.params - The method parameters.\n * @returns A promise that resolves to the response.\n */\n async submitRequest(request: {\n id: string;\n origin: string;\n scope: string;\n account: AccountId;\n request: {\n method: string;\n params?: Json[] | Record<string, Json>;\n };\n }): Promise<Json> {\n const account = this.lookupAccount(request.account);\n if (!account) {\n throw new Error(\n `Account '${request.account}' not found in snap '${this.snapId}'`,\n );\n }\n return this.submitSnapRequest({\n origin: request.origin,\n account,\n method: request.request.method as AccountMethod,\n params: request.request.params,\n scope: request.scope,\n noPending: false,\n });\n }\n\n // ──────────────────────────────────────────────\n // Internal API (used by parent SnapKeyring for event handling, routing, etc.)\n // ──────────────────────────────────────────────\n\n /**\n * Upsert an account into the registry.\n *\n * Fires `onRegister` only when the account is new (not already present).\n * Safe to call for updates -- the parent index is already populated in that\n * case and the no-op path avoids a redundant write.\n *\n * @param account - The account to add or update.\n */\n setAccount(account: KeyringAccount): void {\n const isNew = !this.registry.has(account.id);\n this.registry.set(account);\n if (isNew) {\n this.#callbacks.onRegister?.(account.id);\n }\n }\n\n /**\n * Remove an account from the registry.\n *\n * Fires `onUnregister` so the parent can drop the account from its index.\n *\n * @param id - The account ID to remove.\n * @returns `true` if the account was removed, `false` if it was not found.\n */\n removeAccount(id: AccountId): boolean {\n if (!this.registry.has(id)) {\n return false;\n }\n this.registry.delete(id);\n this.#callbacks.onUnregister?.(id);\n return true;\n }\n\n /**\n * Check whether an account exists in this keyring.\n *\n * @param id - The account ID to check.\n * @returns `true` if the account exists.\n */\n hasAccount(id: AccountId): boolean {\n return this.registry.has(id);\n }\n\n /**\n * Get an account by its ID.\n *\n * @param id - The account ID to look up.\n * @returns The account, or `undefined` if not found.\n */\n lookupAccount(id: AccountId): KeyringAccount | undefined {\n return this.registry.get(id);\n }\n\n /**\n * Get an account by address (case-insensitive).\n *\n * Performs an O(1) exact lookup first; falls back to a linear scan to\n * handle addresses stored with different casing.\n *\n * @param address - The address to look up.\n * @returns The account, or `undefined` if not found.\n */\n lookupByAddress(address: string): KeyringAccount | undefined {\n const id = this.registry.getAccountId(address);\n if (id !== undefined) {\n return this.registry.get(id);\n }\n // The fallback only runs when the exact-match branch above misses,\n // which in practice only happens for EVM addresses with casing\n // differences (checksummed vs lowercase). Non-EVM addresses are\n // case-sensitive and always resolve on the exact branch.\n return this.registry\n .values()\n .find((account) => equalsIgnoreCase(account.address, address));\n }\n\n /**\n * Get all accounts in this keyring (synchronous).\n *\n * This exists alongside the async `getAccounts()` (from `Keyring`) because\n * `SnapKeyring` needs synchronous access for iteration in `serialize`,\n * `listAccounts`, `hasSnapId`, etc. without awaiting.\n *\n * @returns An array of all accounts.\n */\n accounts(): KeyringAccount[] {\n return this.registry.values();\n }\n\n /**\n * Serialize this keyring's state.\n *\n * The returned object uses the internal per-snap format; the parent\n * `SnapKeyring` reconstructs the flat external format in its own\n * `serialize()`.\n *\n * @returns The serialized state.\n */\n async serialize(): Promise<SnapKeyringState> {\n const accounts: SnapKeyringState['accounts'] = {};\n for (const account of this.registry.values()) {\n accounts[account.id] = account;\n }\n const state: SnapKeyringState = {\n snapId: this.snapId,\n accounts,\n };\n return state;\n }\n\n /**\n * Restore this keyring from a serialized state.\n *\n * Validates the payload (accepting both v1 and v2 accounts), migrates any\n * v1 accounts to v2, then replaces the registry. If validation fails, the\n * existing registry is left untouched.\n *\n * @param state - The state to deserialize.\n * @returns A promise that resolves when deserialization is complete.\n */\n async deserialize(state: Json): Promise<void> {\n // Validate the raw payload — accepts both v1 and v2 account shapes.\n assert(state, SnapKeyringStateStruct);\n\n // Bind the keyring to its snap ID (idempotent for the same ID, throws on\n // mismatch to prevent swapping a keyring's identity via deserialize).\n this.bindSnapId(state.snapId as SnapId);\n\n // Migrate v1 accounts to v2.\n const migratedAccounts: Record<string, KeyringAccount> = {};\n for (const [id, rawAccount] of Object.entries(state.accounts)) {\n if (isAccountV1(rawAccount)) {\n console.info(\n `SnapKeyring - Found a KeyringAccountV1, migrating to V2: ${rawAccount.id}`,\n );\n migratedAccounts[id] = migrateAccountV1(rawAccount);\n } else {\n migratedAccounts[id] = rawAccount as KeyringAccount;\n }\n }\n\n // Apply the migrated state to the registry.\n for (const id of [...this.registry.keys()]) {\n this.removeAccount(id);\n }\n\n for (const account of Object.values(migratedAccounts)) {\n this.setAccount(account);\n }\n }\n\n // ──────────────────────────────────────────────\n // Private helpers\n // ──────────────────────────────────────────────\n}\n"]}
|
|
1
|
+
{"version":3,"file":"SnapKeyring.mjs","sourceRoot":"","sources":["../../src/v2/SnapKeyring.ts"],"names":[],"mappings":"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","sourcesContent":["import type { KeyringAccount } from '@metamask/keyring-api';\nimport { KeyringAccountStruct } from '@metamask/keyring-api';\nimport type {\n CreateAccountOptions,\n Keyring,\n KeyringCapabilities,\n} from '@metamask/keyring-api/v2';\nimport { KeyringType } from '@metamask/keyring-api/v2';\nimport { KeyringInternalSnapClient } from '@metamask/keyring-internal-snap-client/v2';\nimport { KeyringAccountRegistry } from '@metamask/keyring-sdk';\nimport type { AccountId } from '@metamask/keyring-utils';\nimport type { SnapId } from '@metamask/snaps-sdk';\nimport type { Infer } from '@metamask/superstruct';\nimport { assert, object, record, string, union } from '@metamask/superstruct';\nimport type { Json } from '@metamask/utils';\nimport { Mutex } from 'async-mutex';\n\nimport {\n KeyringAccountV1Struct,\n normalizeAccount,\n normalizeAccountAddress,\n transformAccount,\n} from '../account';\nimport { isAccountV1, migrateAccountV1 } from '../migrations';\nimport type { SnapKeyringMessenger } from '../SnapKeyringMessenger';\nimport { SnapKeyringV1 } from '../SnapKeyringV1';\nimport type { AccountMethod, SnapKeyringV1Callbacks } from '../SnapKeyringV1';\nimport { equalsIgnoreCase } from '../util';\n\n/**\n * Default, empty capabilities used until the snap manifest is read on\n * `deserialize`. Typed (no cast) so that adding a new required\n * `KeyringCapabilities` field surfaces here as a compile error.\n */\nexport const EMPTY_CAPABILITIES: KeyringCapabilities = Object.freeze({\n scopes: [],\n});\n\n/**\n * Superstruct schema for {@link SnapKeyringState}.\n *\n * Accepts both v1 accounts (missing `scopes`) and v2 accounts so that\n * persisted state can be validated before migration.\n */\nexport const SnapKeyringStateStruct = object({\n snapId: string(),\n accounts: record(\n string(),\n union([KeyringAccountStruct, KeyringAccountV1Struct]),\n ),\n});\n\n/**\n * Serialized state of a single SnapKeyring instance.\n *\n * Note: this is an internal format only used between SnapKeyring and its\n * parent SnapKeyring. The external KeyringState format (flat `{ account,\n * snapId }` map) is preserved by SnapKeyring.serialize / deserialize.\n *\n * Inferred from {@link SnapKeyringStateStruct}: `snapId` is `string`\n * (not the branded `SnapId`) so the shape stays JSON-compatible without\n * unsafe casts.\n */\nexport type SnapKeyringState = Infer<typeof SnapKeyringStateStruct>;\n\n/**\n * Callbacks injected by the parent `SnapKeyring` for global coordination.\n */\nexport type SnapKeyringCallbacks = SnapKeyringV1Callbacks & {\n /**\n * Run a callback under a global lock to prevent TOCTOU races in\n * `createAccounts` when multiple snaps call `assertAccountCanBeUsed`\n * concurrently.\n *\n * Optional: if omitted, `SnapKeyringV2` falls back to its own per-instance\n * `Mutex`, which is sufficient when the keyring is used standalone.\n */\n withLock?: <Result>(callback: () => Promise<Result>) => Promise<Result>;\n};\n\ntype SnapKeyringOptions = {\n messenger: SnapKeyringMessenger;\n callbacks: SnapKeyringCallbacks;\n isAnyAccountTypeAllowed?: boolean;\n};\n\n/**\n * Holds the v2 snap client once a {@link SnapKeyring} instance has been bound\n * via {@link SnapKeyring.bindSnapId}.\n */\ntype SnapKeyringContext = {\n snapId: SnapId;\n client: KeyringInternalSnapClient;\n};\n\n/**\n * Checks if a given keyring is a Snap keyring (v2).\n *\n * @param keyring - The keyring to check.\n * @returns `true` if the keyring is a Snap keyring (v2), `false` otherwise.\n */\nexport function isSnapKeyring(keyring: Keyring): keyring is SnapKeyring {\n return keyring.type === KeyringType.Snap;\n}\n\n/**\n * Per-snap keyring that implements `Keyring` (v2).\n *\n * Owns the account registry and messenger. For v1 snaps (those that do not\n * declare `endowment:keyring` capabilities in their manifest), a\n * {@link SnapKeyringV1} instance is created on `deserialize` and held under\n * {@link SnapKeyring.v1}. V2 snaps have `v1 === undefined` and communicate\n * directly through the v2 client without the `{ pending, result }` envelope.\n */\nexport class SnapKeyring implements Keyring {\n /** Account registry — shared by reference with the v1 instance when present. */\n readonly #registry: KeyringAccountRegistry;\n\n /** Messenger for snap controller calls and event publishing. */\n readonly #messenger: SnapKeyringMessenger;\n\n readonly #callbacks: SnapKeyringCallbacks;\n\n readonly #isAnyAccountTypeAllowed: boolean;\n\n /**\n * Mutex that serializes `createAccounts` calls on this snap instance.\n * Owned here so that each `SnapKeyring` is fully self-contained.\n */\n readonly #lock: Mutex;\n\n /** V2 snap client. Set via {@link bindSnapId}. */\n #context: SnapKeyringContext | undefined;\n\n /**\n * V1 instance, present only when the snap does not declare v2 capabilities.\n * Created on `deserialize()` after reading the snap manifest.\n */\n #v1: SnapKeyringV1 | undefined;\n\n // ──────────────────────────────────────────────\n // Keyring properties\n // ──────────────────────────────────────────────\n\n readonly type = `${KeyringType.Snap}` as const;\n\n static readonly type = `${KeyringType.Snap}` as const;\n\n /**\n * Capabilities are snap-specific. Initialized empty and can be updated\n * by the parent when snap metadata becomes available.\n */\n capabilities: KeyringCapabilities;\n\n constructor({\n messenger,\n callbacks,\n isAnyAccountTypeAllowed = false,\n }: SnapKeyringOptions) {\n this.#registry = new KeyringAccountRegistry();\n this.#messenger = messenger;\n this.#callbacks = callbacks;\n this.#isAnyAccountTypeAllowed = isAnyAccountTypeAllowed;\n this.#lock = new Mutex();\n\n // Default capabilities; replaced from the snap manifest on `deserialize`.\n this.capabilities = EMPTY_CAPABILITIES;\n }\n\n /**\n * Gets the v1 instance for this snap, or `undefined` if the snap is v2-only.\n *\n * @returns The v1 instance, or `undefined` if the snap is v2-only.\n *\n * Use this to make v1 calls explicit:\n * ```ts\n * keyring.v1?.signTransaction(account, tx);\n * ```\n */\n get v1(): SnapKeyringV1 | undefined {\n return this.#v1;\n }\n\n /**\n * The snap ID this instance is scoped to.\n *\n * @returns The snap ID.\n * @throws If the keyring has not been initialized yet.\n */\n get snapId(): SnapId {\n /* istanbul ignore next */\n if (this.#context === undefined) {\n throw new Error(\n 'SnapKeyring has not been initialized: call deserialize() first',\n );\n }\n return this.#context.snapId;\n }\n\n /**\n * Bind this keyring to a snap ID and initialize the v2 client.\n *\n * Idempotent for the same `snapId`; throws if called again with a different\n * one to prevent accidentally swapping a keyring's identity.\n *\n * @param snapId - The snap ID to bind to.\n */\n protected bindSnapId(snapId: SnapId): void {\n if (this.#context !== undefined && this.#context.snapId !== snapId) {\n throw new Error(\n `SnapKeyring bound to '${this.#context.snapId}' cannot be rebound to '${snapId}'`,\n );\n }\n if (this.#context === undefined) {\n this.#context = {\n snapId,\n client: new KeyringInternalSnapClient({\n messenger: this.#messenger,\n snapId,\n }),\n };\n }\n }\n\n /**\n * Returns the v2 snap client.\n *\n * @returns The v2 snap client.\n * @throws If the keyring is not yet initialized.\n */\n get #client(): KeyringInternalSnapClient {\n /* istanbul ignore next */\n if (this.#context === undefined) {\n throw new Error('SnapKeyring is not bound to a snap ID');\n }\n return this.#context.client;\n }\n\n /**\n * Destroy this keyring, rejecting any pending v1 requests.\n *\n * @returns A promise that resolves when the keyring is destroyed.\n */\n async destroy(): Promise<void> {\n await this.#v1?.destroy();\n this.#v1 = undefined;\n }\n\n /**\n * Run a callback under the appropriate lock.\n *\n * Prefers the injected `withLock` callback (global lock provided by\n * `SnapKeyring`) so that `createAccounts` calls across different snaps\n * are serialized. Falls back to the per-instance `#lock` when no global\n * lock is provided (e.g. standalone use in tests).\n *\n * @param callback - Operation to run under the lock.\n * @returns The result of the callback.\n */\n async #withLock<Result>(callback: () => Promise<Result>): Promise<Result> {\n return (\n this.#callbacks.withLock ??\n (async (operation: () => Promise<Result>): Promise<Result> =>\n this.#lock.runExclusive(operation))\n )(callback);\n }\n\n // ──────────────────────────────────────────────\n // Keyring methods\n // ──────────────────────────────────────────────\n\n /**\n * Returns all accounts managed by this keyring.\n *\n * @returns A promise that resolves to an array of all accounts.\n */\n async getAccounts(): Promise<KeyringAccount[]> {\n this.#assertInitialized();\n return this.accounts();\n }\n\n /**\n * Returns the account with the specified ID.\n *\n * @param accountId - ID of the account to retrieve.\n * @returns A promise that resolves to the account.\n */\n async getAccount(accountId: AccountId): Promise<KeyringAccount> {\n this.#assertInitialized();\n const account = this.lookupAccount(accountId);\n if (!account) {\n throw new Error(\n `Account '${accountId}' not found in snap '${this.snapId}'`,\n );\n }\n return account;\n }\n\n /**\n * Creates one or more new accounts according to the provided options.\n *\n * Deterministic account creation MUST be idempotent, meaning that for\n * deterministic algorithms, like BIP-44, calling this method with the same\n * options should always return the same accounts, even if the accounts\n * already exist in the keyring.\n *\n * NOTE: If some accounts are not allowed (non-unique address, unsupported\n * generic account), this method will skip their creation and ask the Snap\n * to remove them from its state.\n *\n * @param options - Options describing how to create the account(s).\n * @returns A promise that resolves to an array of the created account objects.\n */\n async createAccounts(\n options: CreateAccountOptions,\n ): Promise<KeyringAccount[]> {\n this.#assertInitialized();\n return this.#withLock(async () => {\n // Keep track of address/account ID part of this batch, to avoid having duplicates.\n const batchAddresses = new Set<string>();\n const batchIds = new Set<string>();\n\n const accounts: KeyringAccount[] = [];\n const newAccounts: KeyringAccount[] = [];\n let snapAccounts: KeyringAccount[];\n if (this.#v1) {\n // v1 snap: the v1 client wraps options in `{ options }` before sending\n // `keyring_createAccounts`, while the v2 client sends flat options.\n // Route through v1 so v1 snaps receive the format they expect.\n snapAccounts = await this.#v1.createAccounts(options);\n } else {\n // v2 snap: call snap directly with flat options.\n snapAccounts = await this.#client.createAccounts(options);\n }\n\n try {\n for (const snapAccount of snapAccounts) {\n let account = normalizeAccount(transformAccount(snapAccount));\n const { address } = account;\n\n // Check for idempotency.\n const existingAccount = this.#getExistingAccount(account);\n if (existingAccount) {\n // NOTE: We re-use the account from the internal state to avoid having the Snap\n // mutating the account object without updating the map.\n account = existingAccount;\n } else {\n await this.#callbacks.assertAccountCanBeUsed(account);\n\n // Also check for transient accounts that are not yet part of the keyring\n // state.\n if (batchAddresses.has(address) || batchIds.has(account.id)) {\n throw new Error(\n `Account '${account.id}' is already part of this batch (same address or account ID)`,\n );\n }\n batchAddresses.add(address);\n batchIds.add(account.id);\n\n // NOTE: This method does not rely on the `AccountCreated` event to add\n // accounts to the keyring, so we have to add them to the state manually.\n newAccounts.push(account);\n }\n\n // New AND existing accounts are returned to the caller no matter what.\n accounts.push(account);\n }\n\n // We update the keyring state only if needed.\n if (newAccounts.length > 0) {\n for (const account of newAccounts) {\n this.setAccount(account);\n }\n\n // NOTE: We assume this will never fail, thus, we don't need to rollback the\n // keyring state if anything goes wrong here.\n await this.#callbacks.saveState();\n }\n\n return accounts;\n } catch (error) {\n // Rollback Snap state.\n for (const snapAccount of snapAccounts) {\n // Make sure to only delete accounts that were not part of the keyring state.\n if (!this.#getExistingAccount(snapAccount)) {\n try {\n await this.#client.deleteAccount(snapAccount.id);\n } catch (rollbackError) {\n // Best-effort rollback; log snap-side failures for observability.\n console.error(\n `Account '${snapAccount.id}' may not have been removed from snap '${this.snapId}' during createAccounts rollback:`,\n rollbackError,\n );\n }\n }\n }\n\n throw error;\n }\n });\n }\n\n /**\n * Deletes the account with the specified ID.\n *\n * Removes the account from the local registry (firing `onUnregister` so the\n * parent can update its index), then asks the snap to delete it.\n *\n * @param accountId - ID of the account to delete.\n */\n async deleteAccount(accountId: AccountId): Promise<void> {\n this.#assertInitialized();\n // Always remove the account from the registry, even if the Snap is going to\n // fail to delete it. removeAccount fires onUnregister to clean #accountIndex.\n this.removeAccount(accountId);\n\n try {\n await this.#client.deleteAccount(accountId);\n } catch (error) {\n // If the Snap failed to delete the account, log the error and continue\n // with the account deletion, otherwise the account will be stuck in the\n // keyring.\n console.error(\n `Account '${accountId}' may not have been removed from snap '${this.snapId}':`,\n error,\n );\n }\n }\n\n /**\n * Submits a request to the keyring.\n *\n * For v1 snaps (those without declared capabilities), delegates to the v1\n * event-driven flow that handles the `{ pending, result }` envelope.\n * For v2 snaps, calls the snap directly via the v2 client which returns\n * `Json` with no envelope.\n *\n * @param request - The keyring request to submit.\n * @param request.id - The request ID.\n * @param request.origin - The sender origin.\n * @param request.scope - The CAIP-2 chain ID.\n * @param request.account - The account ID.\n * @param request.request - The inner JSON-RPC request.\n * @param request.request.method - The method to call.\n * @param request.request.params - The method parameters.\n * @returns A promise that resolves to the response.\n */\n async submitRequest(request: {\n id: string;\n origin: string;\n scope: string;\n account: AccountId;\n request: {\n method: string;\n params?: Json[] | Record<string, Json>;\n };\n }): Promise<Json> {\n this.#assertInitialized();\n const account = this.lookupAccount(request.account);\n if (!account) {\n throw new Error(\n `Account '${request.account}' not found in snap '${this.snapId}'`,\n );\n }\n if (this.#v1) {\n // v1 snap: use event-driven flow with { pending, result } envelope handling.\n return this.#v1.submitSnapRequest({\n origin: request.origin,\n account,\n method: request.request.method as AccountMethod,\n params: request.request.params,\n scope: request.scope,\n noPending: false,\n });\n }\n // v2 snap: call snap directly, returns Json (no envelope).\n return this.#client.submitRequest({\n id: request.id,\n origin: request.origin,\n scope: request.scope,\n account: request.account,\n request: request.request,\n });\n }\n\n // ──────────────────────────────────────────────\n // Internal API (used by parent SnapKeyring for event handling, routing, etc.)\n // ──────────────────────────────────────────────\n\n /**\n * Upsert an account into the registry.\n *\n * Fires `onRegister` only when the account is new (not already present).\n * Safe to call for updates -- the parent index is already populated in that\n * case and the no-op path avoids a redundant write.\n *\n * @param account - The account to add or update.\n */\n setAccount(account: KeyringAccount): void {\n const isNew = !this.#registry.has(account.id);\n this.#registry.set(account);\n if (isNew) {\n this.#callbacks.onRegister?.(account.id);\n }\n }\n\n /**\n * Remove an account from the registry.\n *\n * Fires `onUnregister` so the parent can drop the account from its index.\n *\n * @param id - The account ID to remove.\n * @returns `true` if the account was removed, `false` if it was not found.\n */\n removeAccount(id: AccountId): boolean {\n if (!this.#registry.has(id)) {\n return false;\n }\n this.#registry.delete(id);\n this.#callbacks.onUnregister?.(id);\n return true;\n }\n\n /**\n * Check whether an account exists in this keyring.\n *\n * @param id - The account ID to check.\n * @returns `true` if the account exists.\n */\n hasAccount(id: AccountId): boolean {\n return this.#registry.has(id);\n }\n\n /**\n * Get an account by its ID.\n *\n * @param id - The account ID to look up.\n * @returns The account, or `undefined` if not found.\n */\n lookupAccount(id: AccountId): KeyringAccount | undefined {\n return this.#registry.get(id);\n }\n\n /**\n * Get an account by address (case-insensitive).\n *\n * Performs an O(1) exact lookup first; falls back to a linear scan to\n * handle addresses stored with different casing.\n *\n * @param address - The address to look up.\n * @returns The account, or `undefined` if not found.\n */\n lookupByAddress(address: string): KeyringAccount | undefined {\n const id = this.#registry.getAccountId(address);\n if (id !== undefined) {\n return this.#registry.get(id);\n }\n // The fallback only runs when the exact-match branch above misses,\n // which in practice only happens for EVM addresses with casing\n // differences (checksummed vs lowercase). Non-EVM addresses are\n // case-sensitive and always resolve on the exact branch.\n return this.#registry\n .values()\n .find((account) => equalsIgnoreCase(account.address, address));\n }\n\n /**\n * Get all accounts in this keyring (synchronous).\n *\n * This exists alongside the async `getAccounts()` (from `Keyring`) because\n * `SnapKeyring` needs synchronous access for iteration in `serialize`,\n * `listAccounts`, `hasSnapId`, etc. without awaiting.\n *\n * @returns An array of all accounts.\n */\n accounts(): KeyringAccount[] {\n return this.#registry.values();\n }\n\n /**\n * Serialize this keyring's state.\n *\n * The returned object uses the internal per-snap format; the parent\n * `SnapKeyring` reconstructs the flat external format in its own\n * `serialize()`.\n *\n * @returns The serialized state.\n */\n async serialize(): Promise<SnapKeyringState> {\n const accounts: SnapKeyringState['accounts'] = {};\n for (const account of this.#registry.values()) {\n accounts[account.id] = account;\n }\n const state: SnapKeyringState = {\n snapId: this.snapId,\n accounts,\n };\n return state;\n }\n\n /**\n * Restore this keyring from a serialized state.\n *\n * Validates the payload (accepting both v1 and v2 account shapes), migrates\n * any v1 accounts to v2, then replaces the registry. Also determines whether\n * the snap is v1 or v2 by reading its manifest capabilities: if no\n * capabilities are declared, a {@link SnapKeyringV1} instance is created and\n * held under {@link SnapKeyring.v1}.\n *\n * @param state - The state to deserialize.\n * @returns A promise that resolves when deserialization is complete.\n */\n async deserialize(state: Json): Promise<void> {\n // Validate the raw payload — accepts both v1 and v2 account shapes.\n assert(state, SnapKeyringStateStruct);\n\n // Bind the keyring to its snap ID (idempotent for the same ID, throws on\n // mismatch to prevent swapping a keyring's identity via deserialize).\n this.bindSnapId(state.snapId as SnapId);\n\n // Refresh capabilities from the snap manifest on every deserialize, falling\n // back to the empty default so a re-hydrate clears any previously-loaded\n // capabilities when the snap no longer declares them.\n const capabilities = this.#resolveKeyringCapabilities();\n this.capabilities = capabilities ?? EMPTY_CAPABILITIES;\n\n // Determine snap version and create a v1 instance if needed.\n const v1 = capabilities === undefined;\n if (v1) {\n // v1 snap: no declared capabilities. Create a SnapKeyringV1 instance\n // that shares the registry and messenger owned by this class.\n if (this.#v1 === undefined) {\n this.#v1 = new SnapKeyringV1({\n messenger: this.#messenger,\n callbacks: this.#callbacks,\n registry: this.#registry,\n isAnyAccountTypeAllowed: this.#isAnyAccountTypeAllowed,\n });\n this.#v1.bindSnapId(state.snapId as SnapId);\n }\n } else {\n // v2 snap: tear down any stale v1 instance (e.g. after a manifest update).\n await this.#v1?.destroy();\n this.#v1 = undefined;\n }\n\n // Migrate v1 accounts to v2.\n const migratedAccounts: Record<string, KeyringAccount> = {};\n for (const [id, rawAccount] of Object.entries(state.accounts)) {\n if (isAccountV1(rawAccount)) {\n console.info(\n `SnapKeyring - Found a KeyringAccountV1, migrating to V2: ${rawAccount.id}`,\n );\n migratedAccounts[id] = migrateAccountV1(rawAccount);\n } else {\n migratedAccounts[id] = rawAccount as KeyringAccount;\n }\n }\n\n // Apply the migrated state to the registry.\n for (const id of [...this.#registry.keys()]) {\n this.removeAccount(id);\n }\n\n for (const account of Object.values(migratedAccounts)) {\n this.setAccount(account);\n }\n }\n\n // ──────────────────────────────────────────────\n // Private helpers\n // ──────────────────────────────────────────────\n\n /**\n * Assert that the keyring has been initialized.\n *\n * @throws An error if the keyring has not been initialized.\n */\n #assertInitialized(): void {\n if (this.#context === undefined) {\n throw new Error(\n 'SnapKeyring has not been initialized: call deserialize() first',\n );\n }\n }\n\n /**\n * Resolve the keyring capabilities from the snap manifest.\n *\n * @returns The keyring capabilities, or `undefined` if the snap manifest does not declare any capabilities.\n */\n #resolveKeyringCapabilities(): KeyringCapabilities | undefined {\n const snap = this.#messenger.call('SnapController:getSnap', this.snapId);\n // READ THIS CAREFULLY:\n // We are not validating the shape of the capabilities here, because there is\n // manifest validation done already on the snaps side, the snaps repo maintains\n // a copy of the `KeyringCapabilitiesStruct`.\n // We must ensure that both structs are always in-sync, otherwise the type-cast\n // could cause runtime issues!\n return snap?.manifest.initialPermissions['endowment:keyring']\n ?.capabilities as KeyringCapabilities | undefined;\n }\n\n /**\n * Check whether an account with the same ID and address already exists in\n * the registry. Used for idempotent account creation.\n *\n * @param account - The account to check against the registry.\n * @returns The existing account if found, `undefined` otherwise.\n */\n #getExistingAccount(account: KeyringAccount): KeyringAccount | undefined {\n const address = normalizeAccountAddress(account);\n const existing = this.#registry.get(account.id);\n if (existing && normalizeAccountAddress(existing) === address) {\n return existing;\n }\n return undefined;\n }\n}\n"]}
|