@dynamic-labs-wallet/browser 0.0.0-preview.112

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 (55) hide show
  1. package/index.cjs.d.ts +1 -0
  2. package/index.cjs.js +1792 -0
  3. package/index.esm.d.ts +1 -0
  4. package/index.esm.js +1725 -0
  5. package/internal/core/bip340.d.ts +22 -0
  6. package/internal/core/bip340.js +1 -0
  7. package/internal/core/common.d.ts +1 -0
  8. package/internal/core/common.js +1 -0
  9. package/internal/core/ecdsa.d.ts +23 -0
  10. package/internal/core/ecdsa.js +1 -0
  11. package/internal/core/ed25519.d.ts +22 -0
  12. package/internal/core/ed25519.js +1 -0
  13. package/internal/core/index.d.ts +6 -0
  14. package/internal/core/index.js +1 -0
  15. package/internal/core/native.d.ts +103 -0
  16. package/internal/core/native.js +1 -0
  17. package/internal/core/types.d.ts +58 -0
  18. package/internal/core/types.js +1 -0
  19. package/internal/web/generated/libmpc_executor.d.ts +130 -0
  20. package/internal/web/generated/libmpc_executor.js +2 -0
  21. package/internal/web/generated/libmpc_executor_bg.wasm +0 -0
  22. package/internal/web/generated/libmpc_executor_bg.wasm.d.ts +64 -0
  23. package/internal/web/index.d.ts +11 -0
  24. package/internal/web/index.js +1 -0
  25. package/package.json +34 -0
  26. package/src/backup/encryption.d.ts +22 -0
  27. package/src/backup/encryption.d.ts.map +1 -0
  28. package/src/backup/providers/googleDrive.d.ts +19 -0
  29. package/src/backup/providers/googleDrive.d.ts.map +1 -0
  30. package/src/client.d.ts +371 -0
  31. package/src/client.d.ts.map +1 -0
  32. package/src/constants.d.ts +5 -0
  33. package/src/constants.d.ts.map +1 -0
  34. package/src/index.d.ts +6 -0
  35. package/src/index.d.ts.map +1 -0
  36. package/src/mpc/index.d.ts +5 -0
  37. package/src/mpc/index.d.ts.map +1 -0
  38. package/src/mpc/mpc.d.ts +11 -0
  39. package/src/mpc/mpc.d.ts.map +1 -0
  40. package/src/mpc/types.d.ts +5 -0
  41. package/src/mpc/types.d.ts.map +1 -0
  42. package/src/services/iframeDisplay.d.ts +9 -0
  43. package/src/services/iframeDisplay.d.ts.map +1 -0
  44. package/src/services/iframeLocalStorage.d.ts +13 -0
  45. package/src/services/iframeLocalStorage.d.ts.map +1 -0
  46. package/src/services/localStorage.d.ts +32 -0
  47. package/src/services/localStorage.d.ts.map +1 -0
  48. package/src/services/logger.d.ts +3 -0
  49. package/src/services/logger.d.ts.map +1 -0
  50. package/src/services/messageTransportBridge.d.ts +3 -0
  51. package/src/services/messageTransportBridge.d.ts.map +1 -0
  52. package/src/types.d.ts +14 -0
  53. package/src/types.d.ts.map +1 -0
  54. package/src/utils.d.ts +43 -0
  55. package/src/utils.d.ts.map +1 -0
package/index.esm.js ADDED
@@ -0,0 +1,1725 @@
1
+ import { SigningAlgorithm, MPC_RELAY_PROD_API_URL, getMPCChainConfig, BackupLocation, getClientThreshold, MPC_CONFIG, getTSSConfig, WalletOperation, getReshareConfig, DynamicApiClient, IFRAME_DOMAIN_PREPROD } from '@dynamic-labs-wallet/core';
2
+ export * from '@dynamic-labs-wallet/core';
3
+ import { BIP340, Ed25519, Ecdsa, MessageHash, EcdsaKeygenResult, Ed25519KeygenResult, BIP340KeygenResult } from './internal/web';
4
+ export { BIP340, BIP340InitKeygenResult, BIP340KeygenResult, Ecdsa, EcdsaInitKeygenResult, EcdsaKeygenResult, EcdsaPublicKey, EcdsaSignature, Ed25519, Ed25519InitKeygenResult, Ed25519KeygenResult, MessageHash } from './internal/web';
5
+ import { Logger } from '@dynamic-labs/logger';
6
+ import { createRequestChannel, parseMessageTransportData, applyDefaultMessageOrigin, createMessageTransport } from '@dynamic-labs/message-transport';
7
+
8
+ function _extends() {
9
+ _extends = Object.assign || function assign(target) {
10
+ for(var i = 1; i < arguments.length; i++){
11
+ var source = arguments[i];
12
+ for(var key in source)if (Object.prototype.hasOwnProperty.call(source, key)) target[key] = source[key];
13
+ }
14
+ return target;
15
+ };
16
+ return _extends.apply(this, arguments);
17
+ }
18
+
19
+ const getMPCSignatureScheme = ({ signingAlgorithm, baseRelayUrl = MPC_RELAY_PROD_API_URL })=>{
20
+ switch(signingAlgorithm){
21
+ case SigningAlgorithm.ECDSA:
22
+ return new Ecdsa(baseRelayUrl);
23
+ case SigningAlgorithm.ED25519:
24
+ return new Ed25519(baseRelayUrl);
25
+ case SigningAlgorithm.BIP340:
26
+ return new BIP340(baseRelayUrl);
27
+ default:
28
+ throw new Error(`Unsupported signing algorithm: ${signingAlgorithm}`);
29
+ }
30
+ };
31
+ const getMPCSigner = ({ chainName, baseRelayUrl })=>{
32
+ const chainConfig = getMPCChainConfig(chainName);
33
+ const signatureScheme = getMPCSignatureScheme({
34
+ signingAlgorithm: chainConfig.signingAlgorithm,
35
+ baseRelayUrl
36
+ });
37
+ return signatureScheme;
38
+ };
39
+
40
+ const DEFAULT_LOG_LEVEL = 'INFO';
41
+ const STORAGE_KEY = 'dynamic-waas-wallet-client';
42
+ const CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX = 'dynamicWalletKeyShareBackup';
43
+
44
+ const logger = new Logger('DynamicWaasWalletClient');
45
+
46
+ const bytesToBase64 = (arr)=>{
47
+ return btoa(Array.from(arr, (b)=>String.fromCharCode(b)).join(''));
48
+ };
49
+ const stringToBytes = (str)=>{
50
+ return new TextEncoder().encode(str);
51
+ };
52
+ const base64ToBytes = (base64)=>{
53
+ return new Uint8Array(Buffer.from(base64, 'base64'));
54
+ };
55
+ // Helper function to ensure proper base64 padding
56
+ const ensureBase64Padding = (str)=>{
57
+ return str.padEnd(Math.ceil(str.length / 4) * 4, '=');
58
+ };
59
+ const isBrowser = ()=>typeof window !== 'undefined';
60
+ const isHexString = (str)=>{
61
+ // Remove 0x prefix if present
62
+ const hex = str.startsWith('0x') ? str.slice(2) : str;
63
+ // Check if string contains only hex characters
64
+ return /^[0-9A-Fa-f]+$/.test(hex);
65
+ };
66
+ const getClientKeyShareExportFileName = ({ thresholdSignatureScheme, accountAddress })=>{
67
+ return `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${thresholdSignatureScheme}-${accountAddress}.json`;
68
+ };
69
+ const getClientKeyShareBackupInfo = (params)=>{
70
+ var _params_walletProperties, _params_walletProperties_keyShares_;
71
+ const backups = {
72
+ [BackupLocation.DYNAMIC]: [],
73
+ [BackupLocation.GOOGLE_DRIVE]: [],
74
+ [BackupLocation.ICLOUD]: [],
75
+ [BackupLocation.USER]: [],
76
+ [BackupLocation.EXTERNAL]: []
77
+ };
78
+ if (!(params == null ? void 0 : (_params_walletProperties = params.walletProperties) == null ? void 0 : _params_walletProperties.keyShares)) {
79
+ return {
80
+ backups,
81
+ passwordEncrypted: false
82
+ };
83
+ }
84
+ params.walletProperties.keyShares.forEach((keyShare)=>{
85
+ if (backups[keyShare.backupLocation]) {
86
+ backups[keyShare.backupLocation].push(keyShare.id);
87
+ }
88
+ });
89
+ const passwordEncrypted = Boolean((_params_walletProperties_keyShares_ = params.walletProperties.keyShares[0]) == null ? void 0 : _params_walletProperties_keyShares_.passwordEncrypted);
90
+ return {
91
+ backups,
92
+ passwordEncrypted
93
+ };
94
+ };
95
+ /**
96
+ * Helper function to merge keyshares and remove duplicates based on pubkey and secretShare
97
+ * @param existingKeyShares - Array of existing keyshares
98
+ * @param newKeyShares - Array of new keyshares to merge
99
+ * @returns Array of merged unique keyshares
100
+ */ const mergeUniqueKeyShares = (existingKeyShares, newKeyShares)=>{
101
+ const uniqueKeyShares = newKeyShares.filter((newShare)=>!existingKeyShares.some((existingShare)=>{
102
+ if (!(newShare == null ? void 0 : newShare.pubkey) || !(existingShare == null ? void 0 : existingShare.pubkey)) return false;
103
+ return newShare.pubkey.toString() === existingShare.pubkey.toString() && newShare.secretShare === existingShare.secretShare;
104
+ }));
105
+ return [
106
+ ...existingKeyShares,
107
+ ...uniqueKeyShares
108
+ ];
109
+ };
110
+ const timeoutPromise = ({ timeInMs, activity = 'Ceremony' })=>{
111
+ return new Promise((_, reject)=>setTimeout(()=>reject(new Error(`${activity} did not complete in ${timeInMs}ms`)), timeInMs));
112
+ };
113
+ /**
114
+ * Generic helper function to retry a promise-based operations
115
+ *
116
+ * @param operation - The async operation to retry
117
+ * @param config - Configuration options for retry behavior
118
+ * @returns Promise with the operation result
119
+ * @throws Last error encountered after all retries are exhausted
120
+ */ async function retryPromise(operation, { maxAttempts = 5, retryInterval = 500, operationName = 'operation', logContext = {} } = {}) {
121
+ let attempts = 0;
122
+ while(attempts < maxAttempts){
123
+ try {
124
+ return await operation();
125
+ } catch (error) {
126
+ attempts++;
127
+ if (attempts === maxAttempts) {
128
+ logger.error(`Failed to execute ${operationName} after ${maxAttempts} attempts`, _extends({}, logContext, {
129
+ error
130
+ }));
131
+ throw error;
132
+ }
133
+ await new Promise((resolve)=>setTimeout(resolve, retryInterval));
134
+ }
135
+ }
136
+ // TypeScript needs this even though it's unreachable
137
+ throw new Error('Unreachable code');
138
+ }
139
+
140
+ const PBKDF2_ALGORITHM = 'PBKDF2';
141
+ const PBKDF2_ITERATIONS = 100000;
142
+ const PBKDF2_HASH_ALGORITHM = 'SHA-256';
143
+ const AES_GCM_ALGORITHM = 'AES-GCM';
144
+ const AES_GCM_LENGTH = 256;
145
+ const getKey = async ({ password, salt })=>{
146
+ const passwordBytes = stringToBytes(password);
147
+ const initialKey = await crypto.subtle.importKey('raw', passwordBytes, {
148
+ name: 'PBKDF2'
149
+ }, false, [
150
+ 'deriveKey'
151
+ ]);
152
+ return crypto.subtle.deriveKey({
153
+ name: PBKDF2_ALGORITHM,
154
+ salt,
155
+ iterations: PBKDF2_ITERATIONS,
156
+ hash: PBKDF2_HASH_ALGORITHM
157
+ }, initialKey, {
158
+ name: AES_GCM_ALGORITHM,
159
+ length: AES_GCM_LENGTH
160
+ }, false, [
161
+ 'encrypt',
162
+ 'decrypt'
163
+ ]);
164
+ };
165
+ const encryptData = async ({ data, password })=>{
166
+ try {
167
+ // Generate a random salt and IV
168
+ const salt = crypto.getRandomValues(new Uint8Array(16));
169
+ const iv = crypto.getRandomValues(new Uint8Array(12)); // AES-GCM requires 12 bytes
170
+ const key = await getKey({
171
+ password,
172
+ salt
173
+ });
174
+ // Convert the input string to bytes
175
+ const dataBytes = new TextEncoder().encode(data);
176
+ // Encrypt the data
177
+ const encryptedData = await crypto.subtle.encrypt({
178
+ name: AES_GCM_ALGORITHM,
179
+ iv
180
+ }, key, dataBytes);
181
+ // Convert to base64 strings, ensure proper padding
182
+ return {
183
+ salt: bytesToBase64(salt),
184
+ iv: bytesToBase64(iv),
185
+ cipher: bytesToBase64(new Uint8Array(encryptedData))
186
+ };
187
+ } catch (error) {
188
+ throw new Error('Error encrypting data');
189
+ }
190
+ };
191
+ const decryptData = async ({ data, password })=>{
192
+ try {
193
+ const { salt, iv, cipher } = data;
194
+ // Ensure proper base64 padding for all values
195
+ const paddedSalt = ensureBase64Padding(salt);
196
+ const paddedIv = ensureBase64Padding(iv);
197
+ const paddedCipher = ensureBase64Padding(cipher);
198
+ const saltBytes = base64ToBytes(paddedSalt);
199
+ const ivBytes = base64ToBytes(paddedIv);
200
+ const cipherBytes = base64ToBytes(paddedCipher);
201
+ const key = await getKey({
202
+ password,
203
+ salt: saltBytes
204
+ });
205
+ const decryptedData = await crypto.subtle.decrypt({
206
+ name: AES_GCM_ALGORITHM,
207
+ iv: ivBytes
208
+ }, key, cipherBytes);
209
+ return new TextDecoder().decode(decryptedData);
210
+ } catch (error) {
211
+ throw new Error('Decryption failed');
212
+ }
213
+ };
214
+
215
+ const GOOGLE_DRIVE_UPLOAD_API = 'https://www.googleapis.com';
216
+ const uploadFileToGoogleDriveAppStorage = async ({ accessToken, fileName, jsonData })=>{
217
+ return uploadFileToGoogleDrive({
218
+ accessToken,
219
+ fileName,
220
+ jsonData,
221
+ parents: [
222
+ 'appDataFolder'
223
+ ]
224
+ });
225
+ };
226
+ const uploadFileToGoogleDrivePersonal = async ({ accessToken, fileName, jsonData })=>{
227
+ return uploadFileToGoogleDrive({
228
+ accessToken,
229
+ fileName,
230
+ jsonData,
231
+ parents: [
232
+ 'root'
233
+ ]
234
+ });
235
+ };
236
+ const uploadFileToGoogleDrive = async ({ accessToken, fileName, jsonData, parents })=>{
237
+ const metadata = {
238
+ name: fileName,
239
+ mimeType: 'application/json',
240
+ parents
241
+ };
242
+ const form = new FormData();
243
+ form.append('metadata', new Blob([
244
+ JSON.stringify(metadata)
245
+ ], {
246
+ type: 'application/json'
247
+ }));
248
+ form.append('file', new Blob([
249
+ JSON.stringify(jsonData)
250
+ ], {
251
+ type: 'application/json'
252
+ }));
253
+ const response = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/upload/drive/v3/files?uploadType=multipart`, {
254
+ method: 'POST',
255
+ headers: {
256
+ Authorization: `Bearer ${accessToken}`
257
+ },
258
+ body: form
259
+ });
260
+ if (!response.ok) {
261
+ throw new Error('Error uploading file');
262
+ }
263
+ const result = await response.json();
264
+ return result; // Return file metadata, including file ID
265
+ };
266
+ const listFilesFromGoogleDrive = async ({ accessToken, name })=>{
267
+ // Step 1: List all files inside `appDataFolder` with the specified backup filename
268
+ const resp = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files?q=${encodeURIComponent(`name='${name}'`)}&spaces=appDataFolder&orderBy=createdTime desc`, {
269
+ headers: {
270
+ Authorization: `Bearer ${accessToken}`
271
+ }
272
+ });
273
+ const data = await resp.json();
274
+ // If no files found, return null
275
+ if (!data.files || data.files.length === 0) {
276
+ return null;
277
+ }
278
+ const files = data.files;
279
+ return files;
280
+ };
281
+ const downloadFileFromGoogleDrive = async ({ accessToken, name })=>{
282
+ const files = await listFilesFromGoogleDrive({
283
+ accessToken,
284
+ name
285
+ });
286
+ if (!files || files.length === 0) {
287
+ return null;
288
+ }
289
+ // Get the most recent file
290
+ const fileMetadata = files[0];
291
+ // Fetch the file data using the file ID
292
+ const fileRes = await fetch(`${GOOGLE_DRIVE_UPLOAD_API}/drive/v3/files/${fileMetadata.id}?alt=media`, {
293
+ headers: {
294
+ Authorization: `Bearer ${accessToken}`
295
+ }
296
+ });
297
+ // Read the file's raw data
298
+ const fileRawData = await fileRes.text();
299
+ if (fileRawData.length === 0) {
300
+ return null;
301
+ }
302
+ try {
303
+ // Just parse and return the data without validation
304
+ // The client will handle validation of the structure
305
+ return JSON.parse(fileRawData);
306
+ } catch (error) {
307
+ return null;
308
+ }
309
+ };
310
+
311
+ const localStorageWriteTest = {
312
+ tested: false,
313
+ writable: false
314
+ };
315
+ /**
316
+ * Checks whether localStorage is supported on this browser.
317
+ */ const supportsLocalStorage = ()=>{
318
+ if (!isBrowser()) {
319
+ return false;
320
+ }
321
+ try {
322
+ if (typeof globalThis.localStorage !== 'object') {
323
+ return false;
324
+ }
325
+ } catch (e) {
326
+ // DOM exception when accessing `localStorage`
327
+ return false;
328
+ }
329
+ if (localStorageWriteTest.tested) {
330
+ return localStorageWriteTest.writable;
331
+ }
332
+ const randomKey = `lswt-${Math.random()}${Math.random()}`;
333
+ try {
334
+ globalThis.localStorage.setItem(randomKey, randomKey);
335
+ globalThis.localStorage.removeItem(randomKey);
336
+ localStorageWriteTest.tested = true;
337
+ localStorageWriteTest.writable = true;
338
+ } catch (e) {
339
+ // localStorage can't be written to
340
+ localStorageWriteTest.tested = true;
341
+ localStorageWriteTest.writable = false;
342
+ }
343
+ return localStorageWriteTest.writable;
344
+ };
345
+ /**
346
+ * Provides safe access to the globalThis.localStorage property.
347
+ */ const localStorageAdapter = {
348
+ getItem: (key)=>{
349
+ if (!supportsLocalStorage()) {
350
+ return null;
351
+ }
352
+ return globalThis.localStorage.getItem(key);
353
+ },
354
+ removeItem: (key)=>{
355
+ if (!supportsLocalStorage()) {
356
+ return;
357
+ }
358
+ globalThis.localStorage.removeItem(key);
359
+ },
360
+ setItem: (key, value)=>{
361
+ if (!supportsLocalStorage()) {
362
+ return;
363
+ }
364
+ globalThis.localStorage.setItem(key, value);
365
+ }
366
+ };
367
+ /**
368
+ * Returns a localStorage-like object that stores the key-value pairs in
369
+ * memory.
370
+ */ const memoryLocalStorageAdapter = (store = {})=>({
371
+ getItem: (key)=>store[key] || null,
372
+ removeItem: (key)=>{
373
+ delete store[key];
374
+ },
375
+ setItem: (key, value)=>{
376
+ store[key] = value;
377
+ }
378
+ });
379
+
380
+ /**
381
+ * StorageRequestChannelAdapter.getItem() sends a message within host domain
382
+ * the bridge on host will capture this request and forwards it to the iframe via contentWindow.postMessage()
383
+ */ class StorageRequestChannelAdapter {
384
+ async getItem(key) {
385
+ const item = await this.requestChannel.request('getItem', {
386
+ source: 'localStorage',
387
+ key
388
+ });
389
+ return item ? JSON.parse(item) : null;
390
+ }
391
+ async setItem(key, value) {
392
+ const stringifiedValue = typeof value === 'object' ? JSON.stringify(value) : value;
393
+ return this.requestChannel.request('setItem', {
394
+ source: 'localStorage',
395
+ key,
396
+ data: stringifiedValue
397
+ });
398
+ }
399
+ async removeItem(key) {
400
+ return this.requestChannel.request('deleteItem', {
401
+ source: 'localStorage',
402
+ key
403
+ });
404
+ }
405
+ constructor(messageTransport){
406
+ this.requestChannel = createRequestChannel(messageTransport);
407
+ }
408
+ }
409
+
410
+ const setupMessageTransportBridge = (messageTransport, iframe, iframeOrigin)=>{
411
+ if (!(iframe == null ? void 0 : iframe.contentWindow)) {
412
+ throw new Error('Iframe or contentWindow not available');
413
+ }
414
+ const logger = new Logger('debug');
415
+ messageTransport.on((message)=>{
416
+ // Forward the message to webview via postMessage
417
+ if (message.origin === 'host') {
418
+ var _iframe_contentWindow;
419
+ logger.debug(`[host bridge] host --> bridge --> iframe`, message);
420
+ iframe == null ? void 0 : (_iframe_contentWindow = iframe.contentWindow) == null ? void 0 : _iframe_contentWindow.postMessage(message, iframeOrigin);
421
+ }
422
+ });
423
+ const handleIncomingMessage = (message)=>{
424
+ const { data } = message;
425
+ if (!data) return;
426
+ if ((data == null ? void 0 : data.origin) !== 'webview') {
427
+ logger.debug(`skipped message: ${JSON.stringify(data)}: origin is not host`);
428
+ return;
429
+ }
430
+ if (typeof data !== 'object') {
431
+ logger.debug(`skipped message: ${JSON.stringify(data)}: data is not an object`);
432
+ return;
433
+ }
434
+ try {
435
+ const message = parseMessageTransportData(data);
436
+ logger.debug(`[host bridge] iframe --> bridge --> host`, message);
437
+ messageTransport.emit(message);
438
+ } catch (error) {
439
+ if (!(error instanceof SyntaxError)) {
440
+ logger.error('Error handling incoming message:', error);
441
+ }
442
+ }
443
+ };
444
+ /**
445
+ * Handle incoming message from android client
446
+ */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
447
+ // @ts-ignore
448
+ document.addEventListener('message', handleIncomingMessage);
449
+ /**
450
+ * Handle incoming message from iOS client
451
+ */ window.addEventListener('message', handleIncomingMessage);
452
+ };
453
+
454
+ class IframeDisplayChannelAdapter {
455
+ async displayClientShares(accountAddress) {
456
+ await this.requestChannel.request('displayClientShares', accountAddress);
457
+ }
458
+ async displayPrivateKey(accountAddress, privateKey) {
459
+ await this.requestChannel.request('displayPrivateKey', accountAddress, privateKey);
460
+ }
461
+ constructor(messageTransport){
462
+ this.requestChannel = createRequestChannel(messageTransport);
463
+ }
464
+ }
465
+
466
+ class DynamicWalletClient {
467
+ async initialize() {
468
+ if (this.initializePromise) {
469
+ return await this.initializePromise;
470
+ }
471
+ this.logger.debug('Initializing Dynamic Waas Wallet SDK');
472
+ this.initializePromise = this._initialize();
473
+ const result = await this.initializePromise;
474
+ this.logger.debug('Dynamic Waas Wallet SDK initialized');
475
+ return result;
476
+ }
477
+ /**
478
+ * Initializes the iframe display for a specific container.
479
+ *
480
+ * @param {HTMLElement} container - The container to which the iframe will be attached.
481
+ * @returns {Promise<{
482
+ * iframe: HTMLIFrameElement;
483
+ * iframeDisplay: IframeDisplayChannelAdapter;
484
+ * cleanup: () => void;
485
+ * }>} A promise that resolves when the iframe is loaded.
486
+ */ async initializeIframeDisplayForContainer({ container }) {
487
+ try {
488
+ const iframe = await this.loadIframeForContainer(container);
489
+ const transport = applyDefaultMessageOrigin({
490
+ defaultOrigin: 'host',
491
+ messageTransport: createMessageTransport()
492
+ });
493
+ setupMessageTransportBridge(transport, iframe, this.iframeDomain);
494
+ const iframeDisplay = new IframeDisplayChannelAdapter(transport);
495
+ return {
496
+ iframe,
497
+ iframeDisplay,
498
+ cleanup: ()=>{
499
+ container.removeChild(iframe);
500
+ }
501
+ };
502
+ } catch (error) {
503
+ this.logger.error('Error initializing iframe:', error);
504
+ throw error;
505
+ }
506
+ }
507
+ /**
508
+ * this is called on class construction time
509
+ * @returns {Promise<void>} that resolves when the iframe is loaded and the message transport and iframe storage are initialized
510
+ */ initializeIframeCommunication() {
511
+ if (!this.iframeLoadPromise) {
512
+ this.iframeLoadPromise = this.doInitializeIframeCommunication();
513
+ }
514
+ return this.iframeLoadPromise;
515
+ }
516
+ /**
517
+ * initialize the iframe communication by awaiting the iframe load promise
518
+ * and initializing the message transport and iframe storage after iframe is successfully loaded
519
+ */ async doInitializeIframeCommunication() {
520
+ try {
521
+ await this.loadIframe();
522
+ this.initializeMessageTransport();
523
+ this.initializeIframeStorage();
524
+ } catch (error) {
525
+ this.logger.error('Error initializing iframe:', error);
526
+ throw error;
527
+ }
528
+ }
529
+ /**
530
+ * create a promise to load an iframe
531
+ * @returns {Promise<void>} that resolves when the iframe is loaded
532
+ */ loadIframe() {
533
+ return new Promise((resolve, reject)=>{
534
+ const iframe = document.createElement('iframe');
535
+ const iframeTimeoutId = setTimeout(()=>{
536
+ reject(new Error('Iframe load timeout'));
537
+ }, 10000);
538
+ iframe.style.display = 'none';
539
+ iframe.setAttribute('title', 'Dynamic Wallet Iframe');
540
+ iframe.style.position = 'fixed';
541
+ iframe.style.top = '0';
542
+ iframe.style.left = '0';
543
+ iframe.style.width = '0';
544
+ iframe.style.height = '0';
545
+ iframe.style.border = 'none';
546
+ iframe.style.pointerEvents = 'none';
547
+ const params = new URLSearchParams({
548
+ instanceId: this.instanceId,
549
+ hostOrigin: window.location.origin
550
+ });
551
+ iframe.src = `${this.iframeDomain}/waas/${this.environmentId}?${params.toString()}`;
552
+ this.logger.debug('Creating iframe with src:', iframe.src);
553
+ document.body.appendChild(iframe);
554
+ iframe.onload = ()=>{
555
+ clearTimeout(iframeTimeoutId);
556
+ this.logger.debug('Iframe loaded successfully');
557
+ this.iframe = iframe;
558
+ resolve();
559
+ };
560
+ iframe.onerror = (error)=>{
561
+ clearTimeout(iframeTimeoutId);
562
+ this.logger.error('Iframe failed to load:', error);
563
+ reject(new Error('Failed to load iframe'));
564
+ };
565
+ });
566
+ }
567
+ /**
568
+ * Load an iframe for a specific container
569
+ * @param {HTMLElement} container - The container to which the iframe will be attached
570
+ * @returns {Promise<HTMLIFrameElement>} that resolves when the iframe is loaded
571
+ */ loadIframeForContainer(container) {
572
+ return new Promise((resolve, reject)=>{
573
+ const iframe = document.createElement('iframe');
574
+ const iframeTimeoutId = setTimeout(()=>{
575
+ reject(new Error('Iframe load timeout'));
576
+ }, 10000);
577
+ iframe.style.display = 'block';
578
+ iframe.style.width = '100%';
579
+ iframe.style.height = '100%';
580
+ iframe.setAttribute('title', 'Dynamic Wallet Storage');
581
+ iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
582
+ const params = new URLSearchParams({
583
+ instanceId: this.instanceId,
584
+ hostOrigin: window.location.origin
585
+ });
586
+ iframe.src = `${this.iframeDomain}/waas/${this.environmentId}?${params.toString()}`;
587
+ this.logger.debug('Creating iframe with src:', iframe.src);
588
+ // Add iframe to the provided container
589
+ container.appendChild(iframe);
590
+ iframe.onload = ()=>{
591
+ clearTimeout(iframeTimeoutId);
592
+ this.logger.debug('Iframe loaded successfully');
593
+ this.iframe = iframe;
594
+ resolve(iframe);
595
+ };
596
+ iframe.onerror = (error)=>{
597
+ clearTimeout(iframeTimeoutId);
598
+ this.logger.error('Iframe failed to load:', error);
599
+ reject(new Error('Failed to load iframe'));
600
+ };
601
+ });
602
+ }
603
+ /**
604
+ * initialize the message transport after iframe is successfully loaded
605
+ */ initializeMessageTransport() {
606
+ const transport = applyDefaultMessageOrigin({
607
+ defaultOrigin: 'host',
608
+ messageTransport: createMessageTransport()
609
+ });
610
+ this.messageTransport = transport;
611
+ if (!this.iframe) {
612
+ throw new Error('Iframe not available');
613
+ }
614
+ setupMessageTransportBridge(this.messageTransport, this.iframe, this.iframeDomain);
615
+ }
616
+ /**
617
+ * initialize the iframe storage after iframe is successfully loaded
618
+ */ initializeIframeStorage() {
619
+ if (!this.messageTransport) {
620
+ throw new Error('Message transport not initialized');
621
+ }
622
+ this.iframeStorage = new StorageRequestChannelAdapter(this.messageTransport);
623
+ }
624
+ /**
625
+ * Gets the initialized iframe instance. This method ensures the iframe is properly loaded and configured.
626
+ * The first call will initialize and await the iframe loading process.
627
+ * Subsequent calls will return the same iframe instance immediately.
628
+ *
629
+ * @throws {Error} If iframe initialization fails
630
+ * @throws {Error} If message transport initialization fails
631
+ * @throws {Error} If iframe storage initialization fails
632
+ * @returns {Promise<HTMLIFrameElement>} The initialized iframe element
633
+ */ async getIframe() {
634
+ await this.initializeIframeCommunication();
635
+ if (!this.iframe) {
636
+ throw new Error('Failed to initialize iframe');
637
+ }
638
+ if (!this.messageTransport) {
639
+ throw new Error('Failed to initialize message transport');
640
+ }
641
+ if (!this.iframeStorage) {
642
+ throw new Error('Failed to initialize iframe storage');
643
+ }
644
+ return this.iframe;
645
+ }
646
+ /**
647
+ * Gets the initialized iframe storage instance. This method ensures the iframe storage is properly configured.
648
+ * The first call will initialize and await the iframe communication process.
649
+ * Subsequent calls will return the same storage instance immediately.
650
+ *
651
+ * @throws {Error} If iframe storage initialization fails
652
+ * @returns {Promise<SupportedStorage>} The initialized iframe storage instance
653
+ */ async getIframeStorage() {
654
+ await this.initializeIframeCommunication();
655
+ if (!this.iframeStorage) {
656
+ throw new Error('Failed to initialize iframe storage');
657
+ }
658
+ return this.iframeStorage;
659
+ }
660
+ /**
661
+ * Client initialization logic
662
+ */ async _initialize() {
663
+ try {
664
+ const initializePromises = [
665
+ this.restoreWallets(),
666
+ this.initializeIframeCommunication()
667
+ ];
668
+ await Promise.all(initializePromises);
669
+ return {
670
+ error: null
671
+ };
672
+ } catch (error) {
673
+ return {
674
+ error
675
+ };
676
+ }
677
+ }
678
+ async serverInitializeKeyGen({ chainName, clientKeygenIds, thresholdSignatureScheme, onError, onCeremonyComplete }) {
679
+ // Initialize keygen, create room, and create the wallet account on the server
680
+ const data = await this.apiClient.createWalletAccount({
681
+ chainName,
682
+ clientKeygenIds,
683
+ thresholdSignatureScheme,
684
+ onError,
685
+ onCeremonyComplete
686
+ });
687
+ return data;
688
+ }
689
+ async clientInitializeKeyGen({ chainName, thresholdSignatureScheme }) {
690
+ // Get the mpc signer
691
+ const mpcSigner = getMPCSigner({
692
+ chainName,
693
+ baseRelayUrl: this.baseMPCRelayApiUrl
694
+ });
695
+ const clientThreshold = getClientThreshold(thresholdSignatureScheme);
696
+ const keygenInitResults = await Promise.all(Array(clientThreshold).fill(null).map(()=>mpcSigner.initKeygen()));
697
+ return keygenInitResults;
698
+ }
699
+ async derivePublicKey({ chainName, keyShare, derivationPath }) {
700
+ const mpcSigner = getMPCSigner({
701
+ chainName,
702
+ baseRelayUrl: this.baseMPCRelayApiUrl
703
+ });
704
+ let publicKey;
705
+ if (mpcSigner instanceof Ecdsa) {
706
+ publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
707
+ } else if (mpcSigner instanceof Ed25519) {
708
+ publicKey = await mpcSigner.derivePubkey(keyShare, derivationPath);
709
+ }
710
+ return publicKey;
711
+ }
712
+ async clientKeyGen({ chainName, roomId, serverKeygenIds, clientKeygenInitResults, thresholdSignatureScheme }) {
713
+ // Get the chain config and the mpc signer
714
+ const mpcSigner = getMPCSigner({
715
+ chainName,
716
+ baseRelayUrl: this.baseMPCRelayApiUrl
717
+ });
718
+ // Get the MPC config for the threshold signature scheme
719
+ const mpcConfig = MPC_CONFIG[thresholdSignatureScheme];
720
+ // For each client keygen init result, create an array of other parties' keygenIds
721
+ const clientKeygenResults = await Promise.all(clientKeygenInitResults.map((currentInit)=>{
722
+ // Get all other client keygenIds (excluding current one)
723
+ const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
724
+ // Combine server keygenIds with other client keygenIds
725
+ const allOtherKeygenIds = [
726
+ ...serverKeygenIds,
727
+ ...otherClientKeygenIds
728
+ ];
729
+ return mpcSigner.keygen(roomId, mpcConfig.numberOfParties, mpcConfig.threshold, currentInit, allOtherKeygenIds);
730
+ }));
731
+ // only need one client keygen result to derive the public key
732
+ const [clientKeygenResult] = clientKeygenResults;
733
+ const chainConfig = getMPCChainConfig(chainName);
734
+ const derivationPath = new Uint32Array(chainConfig.derivationPath);
735
+ const rawPublicKey = await this.derivePublicKey({
736
+ chainName,
737
+ keyShare: clientKeygenResult,
738
+ derivationPath
739
+ });
740
+ return {
741
+ rawPublicKey,
742
+ clientKeygenResults
743
+ };
744
+ }
745
+ async keyGen({ chainName, thresholdSignatureScheme, onError, onCeremonyComplete }) {
746
+ try {
747
+ const clientKeygenInitResults = await this.clientInitializeKeyGen({
748
+ chainName,
749
+ thresholdSignatureScheme
750
+ });
751
+ const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
752
+ const { roomId, serverKeygenIds } = await this.serverInitializeKeyGen({
753
+ chainName,
754
+ clientKeygenIds,
755
+ thresholdSignatureScheme,
756
+ onCeremonyComplete
757
+ });
758
+ const { rawPublicKey, clientKeygenResults: clientKeyShares } = await this.clientKeyGen({
759
+ chainName,
760
+ roomId,
761
+ serverKeygenIds,
762
+ clientKeygenInitResults,
763
+ thresholdSignatureScheme
764
+ });
765
+ return {
766
+ rawPublicKey,
767
+ clientKeyShares
768
+ };
769
+ } catch (error) {
770
+ this.logger.error('Error creating wallet account', error);
771
+ throw new Error('Error creating wallet account');
772
+ }
773
+ }
774
+ async importRawPrivateKey({ chainName, privateKey, thresholdSignatureScheme, onError, onCeremonyComplete }) {
775
+ const mpcSigner = getMPCSigner({
776
+ chainName,
777
+ baseRelayUrl: this.baseMPCRelayApiUrl
778
+ });
779
+ const clientKeygenInitResults = await this.clientInitializeKeyGen({
780
+ chainName,
781
+ thresholdSignatureScheme
782
+ });
783
+ const clientKeygenIds = clientKeygenInitResults.map((result)=>result.keygenId);
784
+ const { roomId, serverKeygenIds } = await this.apiClient.importPrivateKey({
785
+ chainName,
786
+ clientKeygenIds,
787
+ thresholdSignatureScheme,
788
+ onError,
789
+ onCeremonyComplete
790
+ });
791
+ const { threshold } = getTSSConfig(thresholdSignatureScheme);
792
+ const clientKeygenResults = await Promise.all(clientKeygenInitResults.map(async (currentInit, index)=>{
793
+ const otherClientKeygenIds = clientKeygenInitResults.filter((init)=>init.keygenId !== currentInit.keygenId).map((init)=>init.keygenId);
794
+ if (index === 0) {
795
+ const otherKeyGenIds = [
796
+ ...serverKeygenIds,
797
+ ...otherClientKeygenIds
798
+ ];
799
+ const importerKeygenResult = await mpcSigner.importPrivateKeyImporter(roomId, threshold, privateKey, currentInit, otherKeyGenIds);
800
+ return importerKeygenResult;
801
+ } else {
802
+ const recipientKeygenResult = await mpcSigner.importPrivateKeyRecipient(roomId, threshold, currentInit, [
803
+ ...serverKeygenIds,
804
+ ...otherClientKeygenIds
805
+ ]);
806
+ return recipientKeygenResult;
807
+ }
808
+ }));
809
+ const [clientKeygenResult] = clientKeygenResults;
810
+ const rawPublicKey = await this.derivePublicKey({
811
+ chainName,
812
+ keyShare: clientKeygenResult,
813
+ derivationPath: undefined
814
+ });
815
+ return {
816
+ rawPublicKey,
817
+ clientKeyShares: clientKeygenResults
818
+ };
819
+ }
820
+ async serverSign({ walletId, message }) {
821
+ // Create the room and sign the message
822
+ if (typeof message !== 'string') {
823
+ message = '0x' + Buffer.from(message).toString('hex');
824
+ }
825
+ const data = await this.apiClient.signMessage({
826
+ walletId,
827
+ message
828
+ });
829
+ return data;
830
+ }
831
+ async clientSign({ chainName, message, roomId, keyShare, derivationPath }) {
832
+ try {
833
+ const mpcSigner = getMPCSigner({
834
+ chainName,
835
+ baseRelayUrl: this.baseMPCRelayApiUrl
836
+ });
837
+ let formattedMessage;
838
+ //note: Ecdsa can also be used by bitcoin, but only keccak256 is used by ethereum
839
+ if (mpcSigner instanceof Ecdsa) {
840
+ formattedMessage = MessageHash.keccak256(message);
841
+ } else if (mpcSigner instanceof Ed25519) {
842
+ if (typeof message === 'string') {
843
+ if (!isHexString(message)) {
844
+ formattedMessage = Buffer.from(message).toString('hex');
845
+ } else {
846
+ formattedMessage = Buffer.from(message, 'hex');
847
+ }
848
+ } else {
849
+ formattedMessage = message;
850
+ }
851
+ } else if (mpcSigner instanceof BIP340 && typeof message === 'string') {
852
+ formattedMessage = new TextEncoder().encode(message);
853
+ } else {
854
+ throw new Error('Unsupported signer type');
855
+ }
856
+ const signature = await mpcSigner.sign(roomId, keyShare, formattedMessage, derivationPath);
857
+ return signature;
858
+ } catch (error) {
859
+ this.logger.error('Error in clientSign:', error);
860
+ throw error;
861
+ }
862
+ }
863
+ //todo: need to modify with imported flag
864
+ async sign({ accountAddress, message, chainName, password = undefined }) {
865
+ await this.verifyPassword({
866
+ accountAddress,
867
+ password,
868
+ walletOperation: WalletOperation.SIGN_MESSAGE
869
+ });
870
+ const wallet = await this.getWallet({
871
+ accountAddress,
872
+ password,
873
+ walletOperation: WalletOperation.SIGN_MESSAGE
874
+ });
875
+ // Perform the server sign
876
+ const data = await this.serverSign({
877
+ walletId: wallet.walletId,
878
+ message
879
+ });
880
+ const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
881
+ // Perform the client sign and return the signature
882
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
883
+ accountAddress
884
+ });
885
+ const signature = await this.clientSign({
886
+ chainName,
887
+ message,
888
+ roomId: data.roomId,
889
+ keyShare: clientKeyShares[0],
890
+ derivationPath
891
+ });
892
+ return signature;
893
+ }
894
+ async refreshWalletAccountShares({ accountAddress, chainName, password = undefined }) {
895
+ await this.verifyPassword({
896
+ accountAddress,
897
+ password,
898
+ walletOperation: WalletOperation.REFRESH
899
+ });
900
+ const wallet = await this.getWallet({
901
+ accountAddress,
902
+ walletOperation: WalletOperation.NO_OPERATION,
903
+ password
904
+ });
905
+ const mpcSigner = getMPCSigner({
906
+ chainName,
907
+ baseRelayUrl: this.baseMPCRelayApiUrl
908
+ });
909
+ // Create the room and refresh the shares
910
+ const data = await this.apiClient.refreshWalletAccountShares({
911
+ walletId: wallet.walletId
912
+ });
913
+ const roomId = data.roomId;
914
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
915
+ accountAddress
916
+ });
917
+ const refreshResults = await Promise.all(clientKeyShares.map((clientKeyShare)=>mpcSigner.refresh(roomId, clientKeyShare)));
918
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
919
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo()
920
+ });
921
+ await this.setClientKeySharesToLocalStorage({
922
+ accountAddress,
923
+ clientKeyShares: refreshResults,
924
+ overwriteOrMerge: 'overwrite'
925
+ });
926
+ await this.storeEncryptedBackupByWallet({
927
+ accountAddress,
928
+ password: password != null ? password : this.environmentId
929
+ });
930
+ return refreshResults;
931
+ }
932
+ async getExportId({ chainName, clientKeyShare }) {
933
+ const mpcSigner = getMPCSigner({
934
+ chainName,
935
+ baseRelayUrl: this.baseMPCRelayApiUrl
936
+ });
937
+ const exportId = await mpcSigner.exportID(clientKeyShare);
938
+ return exportId;
939
+ }
940
+ /**
941
+ * Helper function to create client shares required to complete a reshare ceremony.
942
+ * @param {string} chainName - The chain to create shares for
943
+ * @param {WalletProperties} wallet - The wallet to reshare
944
+ * @param {ThresholdSignatureScheme} oldThresholdSignatureScheme - The current threshold signature scheme
945
+ * @param {ThresholdSignatureScheme} newThresholdSignatureScheme - The target threshold signature scheme
946
+ * @returns {Promise<{
947
+ * newClientInitKeygenResults: ClientInitKeygenResult[],
948
+ * newClientKeygenIds: string[],
949
+ * existingClientKeygenIds: string[],
950
+ * existingClientKeyShares: ClientKeyShare[]
951
+ * }>} Object containing new and existing client keygen results, IDs and shares
952
+ * @todo Support higher to lower reshare strategies
953
+ */ async reshareStrategy({ chainName, wallet, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme }) {
954
+ const mpcSigner = getMPCSigner({
955
+ chainName,
956
+ baseRelayUrl: this.baseMPCRelayApiUrl
957
+ });
958
+ // Determine share counts based on threshold signature schemes
959
+ const { newClientShareCount, existingClientShareCount } = getReshareConfig({
960
+ oldThresholdSignatureScheme,
961
+ newThresholdSignatureScheme
962
+ });
963
+ // Create new client shares
964
+ const newClientInitKeygenResults = await Promise.all(Array.from({
965
+ length: newClientShareCount
966
+ }, ()=>mpcSigner.initKeygen()));
967
+ const newClientKeygenIds = newClientInitKeygenResults.map((result)=>result.keygenId);
968
+ // Get existing client shares
969
+ const existingClientKeyShares = (await this.getClientKeySharesFromLocalStorage({
970
+ accountAddress
971
+ })).slice(0, existingClientShareCount);
972
+ const existingClientKeygenIds = await Promise.all(existingClientKeyShares.map(async (keyShare)=>await this.getExportId({
973
+ chainName,
974
+ clientKeyShare: keyShare
975
+ })));
976
+ return {
977
+ newClientInitKeygenResults,
978
+ newClientKeygenIds,
979
+ existingClientKeygenIds,
980
+ existingClientKeyShares
981
+ };
982
+ }
983
+ async reshare({ chainName, accountAddress, oldThresholdSignatureScheme, newThresholdSignatureScheme, password = undefined }) {
984
+ await this.verifyPassword({
985
+ accountAddress,
986
+ password,
987
+ walletOperation: WalletOperation.RESHARE
988
+ });
989
+ const { existingClientShareCount } = getReshareConfig({
990
+ oldThresholdSignatureScheme,
991
+ newThresholdSignatureScheme
992
+ });
993
+ const wallet = await this.getWallet({
994
+ accountAddress,
995
+ walletOperation: WalletOperation.NO_OPERATION,
996
+ shareCount: existingClientShareCount,
997
+ password
998
+ });
999
+ const { newClientInitKeygenResults, newClientKeygenIds, existingClientKeygenIds, existingClientKeyShares } = await this.reshareStrategy({
1000
+ chainName,
1001
+ accountAddress,
1002
+ wallet,
1003
+ oldThresholdSignatureScheme,
1004
+ newThresholdSignatureScheme
1005
+ });
1006
+ const clientKeygenIds = [
1007
+ ...newClientKeygenIds,
1008
+ ...existingClientKeygenIds
1009
+ ];
1010
+ // Server to create the room and complete the server reshare logics
1011
+ const data = await this.apiClient.reshare({
1012
+ walletId: wallet.walletId,
1013
+ clientKeygenIds: clientKeygenIds,
1014
+ oldThresholdSignatureScheme,
1015
+ newThresholdSignatureScheme
1016
+ });
1017
+ const { roomId, serverKeygenIds, newServerKeygenIds = [] } = data;
1018
+ // Get the MPC config for the threshold signature scheme
1019
+ const oldMpcConfig = MPC_CONFIG[oldThresholdSignatureScheme];
1020
+ const newMpcConfig = MPC_CONFIG[newThresholdSignatureScheme];
1021
+ const allPartyKeygenIds = [
1022
+ ...clientKeygenIds,
1023
+ ...serverKeygenIds,
1024
+ ...newServerKeygenIds
1025
+ ];
1026
+ const mpcSigner = getMPCSigner({
1027
+ chainName,
1028
+ baseRelayUrl: this.baseMPCRelayApiUrl
1029
+ });
1030
+ const reshareResults = await Promise.all([
1031
+ ...newClientInitKeygenResults.map((keygenResult)=>mpcSigner.reshareNewParty(roomId, oldMpcConfig.threshold, newMpcConfig.threshold, keygenResult, allPartyKeygenIds)),
1032
+ ...existingClientKeyShares.map((keyShare)=>mpcSigner.reshareRemainingParty(roomId, newMpcConfig.threshold, keyShare, allPartyKeygenIds))
1033
+ ]);
1034
+ await this.setClientKeySharesToLocalStorage({
1035
+ accountAddress,
1036
+ clientKeyShares: reshareResults,
1037
+ overwriteOrMerge: 'overwrite'
1038
+ });
1039
+ await this.storeEncryptedBackupByWallet({
1040
+ accountAddress,
1041
+ password
1042
+ });
1043
+ return reshareResults;
1044
+ }
1045
+ async exportKey({ accountAddress, displayContainer, chainName, password = undefined }) {
1046
+ const wallet = await this.getWallet({
1047
+ accountAddress,
1048
+ password,
1049
+ walletOperation: WalletOperation.EXPORT_PRIVATE_KEY
1050
+ });
1051
+ const mpcSigner = getMPCSigner({
1052
+ chainName,
1053
+ baseRelayUrl: this.baseMPCRelayApiUrl
1054
+ });
1055
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1056
+ accountAddress
1057
+ });
1058
+ const exportId = await this.getExportId({
1059
+ chainName,
1060
+ clientKeyShare: clientKeyShares[0]
1061
+ });
1062
+ const data = await this.apiClient.exportKey({
1063
+ walletId: wallet.walletId,
1064
+ exportId
1065
+ });
1066
+ const keyExportRaw = await mpcSigner.exportFullPrivateKey(data.roomId, clientKeyShares[0], exportId);
1067
+ if (!keyExportRaw) {
1068
+ throw new Error('Error exporting private key');
1069
+ }
1070
+ const derivationPath = wallet.derivationPath && wallet.derivationPath != '' ? new Uint32Array(Object.values(JSON.parse(wallet.derivationPath))) : undefined;
1071
+ let derivedPrivateKey;
1072
+ if (mpcSigner instanceof Ecdsa) {
1073
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1074
+ } else if (mpcSigner instanceof Ed25519) {
1075
+ derivedPrivateKey = keyExportRaw;
1076
+ } else if (mpcSigner instanceof BIP340) {
1077
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, derivationPath);
1078
+ }
1079
+ return {
1080
+ derivedPrivateKey
1081
+ };
1082
+ }
1083
+ async offlineExportKey({ chainName, keyShares, derivationPath }) {
1084
+ try {
1085
+ if (!keyShares || keyShares.length < 2) {
1086
+ throw new Error(`Must provide at least min threshold of key shares`);
1087
+ }
1088
+ const mpcSigner = getMPCSigner({
1089
+ chainName,
1090
+ baseRelayUrl: this.baseMPCRelayApiUrl
1091
+ });
1092
+ const walletKeyShares = keyShares.map((keyShare)=>{
1093
+ return mpcSigner instanceof Ecdsa ? new EcdsaKeygenResult(keyShare.pubkey, keyShare.secretShare) : mpcSigner instanceof Ed25519 ? new Ed25519KeygenResult(keyShare.pubkey, keyShare.secretShare) : new BIP340KeygenResult(keyShare.pubkey, keyShare.secretShare);
1094
+ });
1095
+ const keyExportRaw = await mpcSigner.offlineExportFullPrivateKey(walletKeyShares);
1096
+ if (!keyExportRaw) {
1097
+ throw new Error('Error exporting private key: Export returned null');
1098
+ }
1099
+ const chainConfig = getMPCChainConfig(chainName);
1100
+ const walletDerivationPath = !derivationPath ? undefined : new Uint32Array(chainConfig.derivationPath);
1101
+ let derivedPrivateKey;
1102
+ if (mpcSigner instanceof Ecdsa) {
1103
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
1104
+ } else if (mpcSigner instanceof Ed25519) {
1105
+ derivedPrivateKey = keyExportRaw;
1106
+ } else if (mpcSigner instanceof BIP340) {
1107
+ derivedPrivateKey = await mpcSigner.derivePrivateKeyFromXpriv(keyExportRaw, walletDerivationPath);
1108
+ }
1109
+ const rawPublicKey = await this.derivePublicKey({
1110
+ chainName,
1111
+ keyShare: walletKeyShares[0],
1112
+ derivationPath: walletDerivationPath
1113
+ });
1114
+ return {
1115
+ derivedPrivateKey,
1116
+ rawPublicKey
1117
+ };
1118
+ } catch (error) {
1119
+ this.logger.error('Error in offlineExportKey:', error);
1120
+ throw error;
1121
+ }
1122
+ }
1123
+ async encryptKeyShare({ keyShare, password }) {
1124
+ const serializedKeyShare = JSON.stringify(keyShare);
1125
+ const encryptedKeyShare = await encryptData({
1126
+ data: serializedKeyShare,
1127
+ password: password != null ? password : this.environmentId
1128
+ });
1129
+ // stringify the encrypted key share, convert to base64, and store it
1130
+ const serializedEncryptedKeyShare = Buffer.from(JSON.stringify(encryptedKeyShare)).toString('base64');
1131
+ return serializedEncryptedKeyShare;
1132
+ }
1133
+ /**
1134
+ * helper function to store encrypted backup by wallet from iframe local storage
1135
+ */ async getClientKeySharesFromLocalStorage({ accountAddress }) {
1136
+ var _this_iframeStorage;
1137
+ await this.initializeIframeCommunication();
1138
+ const walletObject = await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.getItem(accountAddress));
1139
+ if (!walletObject) {
1140
+ this.logger.debug(`No item found in iframe local storage for accountAddress: ${accountAddress}`);
1141
+ return [];
1142
+ }
1143
+ try {
1144
+ return (walletObject == null ? void 0 : walletObject.clientKeyShares) || [];
1145
+ } catch (error) {
1146
+ this.logger.error(`Error parsing clientKeyShares: ${error} for accountAddress: ${accountAddress}`);
1147
+ return [];
1148
+ }
1149
+ }
1150
+ /**
1151
+ * helper function to store encrypted backup by wallet from iframe local storage
1152
+ */ async setClientKeySharesToLocalStorage({ accountAddress, clientKeyShares, overwriteOrMerge = 'merge' }) {
1153
+ var _this_iframeStorage;
1154
+ await this.initializeIframeCommunication();
1155
+ await ((_this_iframeStorage = this.iframeStorage) == null ? void 0 : _this_iframeStorage.setItem(accountAddress, {
1156
+ clientKeyShares: overwriteOrMerge === 'overwrite' ? clientKeyShares : mergeUniqueKeyShares(await this.getClientKeySharesFromLocalStorage({
1157
+ accountAddress
1158
+ }), clientKeyShares)
1159
+ }));
1160
+ }
1161
+ /**
1162
+ * Encrypts and stores wallet key shares as backups.
1163
+ *
1164
+ * This method encrypts all client key shares for a specific wallet and stores them
1165
+ * in Dynamic's backend. If Google Drive backup is already configured for this wallet,
1166
+ * it will also update the backup in Google Drive.
1167
+ *
1168
+ * The method performs the following steps:
1169
+ * 1. Encrypts all client key shares with the provided password (or environment ID if no password)
1170
+ * 2. Stores the encrypted key shares in Dynamic's backend
1171
+ * 3. If Google Drive backup exists, updates the backup there as well
1172
+ * 4. Updates the local wallet map with the new backup information
1173
+ * 5. Persists the updated wallet map to storage
1174
+ *
1175
+ * @param {Object} params - The parameters for the backup operation
1176
+ * @param {string} params.accountAddress - The account address of the wallet to backup
1177
+ * @param {string} [params.password] - Optional password for encrypting the key shares
1178
+ */ async storeEncryptedBackupByWallet({ accountAddress, clientKeyShares = undefined, password = undefined }) {
1179
+ const keySharesToBackup = clientKeyShares != null ? clientKeyShares : await this.getClientKeySharesFromLocalStorage({
1180
+ accountAddress
1181
+ });
1182
+ const encryptedKeyShares = await Promise.all(keySharesToBackup.map((keyShare)=>this.encryptKeyShare({
1183
+ keyShare,
1184
+ password
1185
+ })));
1186
+ if (!this.walletMap[accountAddress].walletId) {
1187
+ throw new Error(`WalletId not found for accountAddress: ${accountAddress}`);
1188
+ }
1189
+ const data = await this.apiClient.storeEncryptedBackupByWallet({
1190
+ walletId: this.walletMap[accountAddress].walletId,
1191
+ encryptedKeyShares,
1192
+ passwordEncrypted: Boolean(password) && password !== this.environmentId
1193
+ });
1194
+ const hasGoogleDriveBackup = this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[BackupLocation.GOOGLE_DRIVE].length > 0;
1195
+ if (hasGoogleDriveBackup) {
1196
+ var _user_verifiedCredentials_find;
1197
+ const user = await this.apiClient.getUser();
1198
+ const oauthAccountId = user == null ? void 0 : (_user_verifiedCredentials_find = user.verifiedCredentials.find((credential)=>credential.oauthProvider === 'google')) == null ? void 0 : _user_verifiedCredentials_find.id;
1199
+ const googleDriveKeyShareIds = await this.backupKeySharesToGoogleDrive({
1200
+ accountAddress,
1201
+ password,
1202
+ oauthAccountId
1203
+ });
1204
+ data.keyShares.push({
1205
+ backupLocation: BackupLocation.GOOGLE_DRIVE,
1206
+ id: googleDriveKeyShareIds
1207
+ });
1208
+ }
1209
+ const updatedBackupInfo = getClientKeyShareBackupInfo({
1210
+ walletProperties: {
1211
+ derivationPath: this.walletMap[accountAddress].derivationPath,
1212
+ keyShares: data.keyShares,
1213
+ thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme
1214
+ }
1215
+ });
1216
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1217
+ clientKeySharesBackupInfo: updatedBackupInfo
1218
+ });
1219
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1220
+ return data;
1221
+ }
1222
+ async storeEncryptedBackupByWalletWithRetry({ accountAddress, clientKeyShares, password }) {
1223
+ await retryPromise(()=>this.storeEncryptedBackupByWallet({
1224
+ accountAddress,
1225
+ clientKeyShares,
1226
+ password
1227
+ }), {
1228
+ operationName: 'store encrypted backup',
1229
+ logContext: {
1230
+ walletAddress: accountAddress,
1231
+ keyShares: clientKeyShares == null ? void 0 : clientKeyShares.map((keyShare)=>this.encryptKeyShare({
1232
+ keyShare,
1233
+ password
1234
+ }))
1235
+ }
1236
+ });
1237
+ }
1238
+ async updatePassword({ accountAddress, existingPassword, newPassword }) {
1239
+ await this.getWallet({
1240
+ accountAddress,
1241
+ password: existingPassword,
1242
+ walletOperation: WalletOperation.REACH_ALL_PARTIES
1243
+ });
1244
+ await this.storeEncryptedBackupByWallet({
1245
+ accountAddress,
1246
+ password: newPassword
1247
+ });
1248
+ }
1249
+ async decryptKeyShare({ keyShare, password }) {
1250
+ const decodedKeyShare = JSON.parse(Buffer.from(keyShare, 'base64').toString());
1251
+ const decryptedKeyShare = await decryptData({
1252
+ data: decodedKeyShare,
1253
+ password: password != null ? password : this.environmentId
1254
+ });
1255
+ const deserializedKeyShare = JSON.parse(decryptedKeyShare);
1256
+ return deserializedKeyShare;
1257
+ }
1258
+ /**
1259
+ * Helper function to determine keyshare recovery strategy for dynamic shares.
1260
+ * For REFRESH operations, retrieves enough shares to meet the client threshold.
1261
+ * For all other operations, retrieves just 1 share.
1262
+ *
1263
+ * @param clientKeyShareBackupInfo - Information about backed up key shares
1264
+ * @param thresholdSignatureScheme - The signature scheme being used (2-of-2, 2-of-3, etc)
1265
+ * @param walletOperation - The operation being performed (REFRESH, SIGN_MESSAGE, etc)
1266
+ * @param shareCount - The number of shares to recover if specified for reshare operations
1267
+ * @returns @shares: Object mapping backup locations to arrays of share IDs to recover
1268
+ * @returns @requiredShareCount: The number of shares required to recover
1269
+ */ recoverStrategy({ clientKeyShareBackupInfo, thresholdSignatureScheme, walletOperation, shareCount = undefined }) {
1270
+ const { backups } = clientKeyShareBackupInfo;
1271
+ const { clientThreshold } = MPC_CONFIG[thresholdSignatureScheme];
1272
+ let requiredShareCount = walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.RESHARE ? clientThreshold : 1;
1273
+ // Override requiredShareCount if shareCount is provided
1274
+ if (shareCount !== undefined) {
1275
+ requiredShareCount = shareCount;
1276
+ }
1277
+ const dynamicShares = backups[BackupLocation.DYNAMIC].slice(0, requiredShareCount);
1278
+ return {
1279
+ shares: {
1280
+ [BackupLocation.DYNAMIC]: dynamicShares
1281
+ },
1282
+ requiredShareCount
1283
+ };
1284
+ }
1285
+ async recoverEncryptedBackupByWallet({ accountAddress, password, walletOperation, shareCount = undefined, storeRecoveredShares = true }) {
1286
+ const wallet = this.walletMap[accountAddress];
1287
+ this.logger.debug(`recoverEncryptedBackupByWallet wallet: ${walletOperation}`, wallet);
1288
+ const { shares } = this.recoverStrategy({
1289
+ clientKeyShareBackupInfo: wallet.clientKeySharesBackupInfo,
1290
+ thresholdSignatureScheme: wallet.thresholdSignatureScheme,
1291
+ walletOperation,
1292
+ shareCount
1293
+ });
1294
+ const { dynamic: dynamicKeyShareIds } = shares;
1295
+ const data = await this.apiClient.recoverEncryptedBackupByWallet({
1296
+ walletId: wallet.walletId,
1297
+ keyShareIds: dynamicKeyShareIds
1298
+ });
1299
+ const dynamicKeyShares = data.keyShares.filter((keyShare)=>keyShare.encryptedAccountCredential !== null && keyShare.backupLocation === BackupLocation.DYNAMIC);
1300
+ const decryptedKeyShares = await Promise.all(dynamicKeyShares.map((keyShare)=>this.decryptKeyShare({
1301
+ keyShare: keyShare.encryptedAccountCredential,
1302
+ password: password != null ? password : this.environmentId
1303
+ })));
1304
+ if (storeRecoveredShares) {
1305
+ await this.setClientKeySharesToLocalStorage({
1306
+ accountAddress,
1307
+ clientKeyShares: decryptedKeyShares,
1308
+ overwriteOrMerge: 'merge'
1309
+ });
1310
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1311
+ }
1312
+ return decryptedKeyShares;
1313
+ }
1314
+ async restoreWallets() {
1315
+ const wallets = await this.storage.getItem(this.storageKey);
1316
+ if (!wallets) {
1317
+ return;
1318
+ }
1319
+ this.walletMap = JSON.parse(wallets);
1320
+ }
1321
+ async backupKeySharesToGoogleDrive({ accountAddress, fileName, oauthAccountId, password }) {
1322
+ await this.getWallet({
1323
+ accountAddress,
1324
+ walletOperation: WalletOperation.REACH_ALL_PARTIES,
1325
+ password
1326
+ });
1327
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1328
+ accountAddress
1329
+ });
1330
+ if (clientKeyShares.length === 0) {
1331
+ throw new Error('No key shares found');
1332
+ }
1333
+ const encryptedKeyShares = await Promise.all(clientKeyShares.map((keyShare)=>this.encryptKeyShare({
1334
+ keyShare,
1335
+ password
1336
+ })));
1337
+ const accessToken = await this.apiClient.getAccessToken({
1338
+ oauthAccountId
1339
+ });
1340
+ const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
1341
+ const suggestedFileName = getClientKeyShareExportFileName({
1342
+ thresholdSignatureScheme,
1343
+ accountAddress
1344
+ });
1345
+ const backupData = {
1346
+ keyShares: encryptedKeyShares,
1347
+ metadata: {
1348
+ version: '1.0',
1349
+ createdAt: new Date().toISOString(),
1350
+ accountAddress,
1351
+ thresholdSignatureScheme,
1352
+ hasPassword: true,
1353
+ encryption: {
1354
+ algorithm: AES_GCM_ALGORITHM,
1355
+ keyDerivation: PBKDF2_ALGORITHM,
1356
+ iterations: PBKDF2_ITERATIONS,
1357
+ hashAlgorithm: PBKDF2_HASH_ALGORITHM,
1358
+ algorithmLength: AES_GCM_LENGTH
1359
+ },
1360
+ shareCount: encryptedKeyShares.length
1361
+ }
1362
+ };
1363
+ // TODO: handle errors
1364
+ await Promise.all([
1365
+ uploadFileToGoogleDriveAppStorage({
1366
+ accessToken,
1367
+ fileName: fileName != null ? fileName : suggestedFileName,
1368
+ jsonData: backupData
1369
+ }),
1370
+ uploadFileToGoogleDrivePersonal({
1371
+ accessToken,
1372
+ fileName: fileName != null ? fileName : suggestedFileName,
1373
+ jsonData: backupData
1374
+ })
1375
+ ]);
1376
+ const data = await this.apiClient.markKeySharesAsBackedUpGoogleDrive({
1377
+ walletId: this.walletMap[accountAddress].walletId
1378
+ });
1379
+ const ids = data.keyShares.map((keyShare)=>keyShare.id);
1380
+ this.walletMap[accountAddress].clientKeySharesBackupInfo.backups[BackupLocation.GOOGLE_DRIVE] = ids;
1381
+ await this.storage.setItem(this.storageKey, JSON.stringify(this.walletMap));
1382
+ return ids;
1383
+ }
1384
+ async restoreBackupFromGoogleDrive({ accountAddress, oauthAccountId, name, displayContainer, password }) {
1385
+ await this.getWallet({
1386
+ accountAddress
1387
+ });
1388
+ const accessToken = await this.apiClient.getAccessToken({
1389
+ oauthAccountId
1390
+ });
1391
+ const thresholdSignatureScheme = this.walletMap[accountAddress].thresholdSignatureScheme;
1392
+ const suggestedFileName = getClientKeyShareExportFileName({
1393
+ thresholdSignatureScheme,
1394
+ accountAddress
1395
+ });
1396
+ const backupData = await downloadFileFromGoogleDrive({
1397
+ accessToken,
1398
+ name: name != null ? name : suggestedFileName
1399
+ });
1400
+ if (!backupData) {
1401
+ throw new Error('No backup file found');
1402
+ }
1403
+ // Validate the backup data structure
1404
+ if (!backupData.keyShares || !backupData.metadata) {
1405
+ throw new Error('Invalid backup format: missing keyShares or metadata');
1406
+ }
1407
+ const { keyShares } = backupData;
1408
+ const decryptedKeyShares = await Promise.all(keyShares.map((keyShare)=>this.decryptKeyShare({
1409
+ keyShare,
1410
+ password
1411
+ })));
1412
+ await this.setClientKeySharesToLocalStorage({
1413
+ accountAddress,
1414
+ clientKeyShares: decryptedKeyShares,
1415
+ overwriteOrMerge: 'merge'
1416
+ });
1417
+ // Display the client shares in the container via iframe
1418
+ const { iframeDisplay } = await this.initializeIframeDisplayForContainer({
1419
+ container: displayContainer
1420
+ });
1421
+ iframeDisplay.displayClientShares(accountAddress);
1422
+ return decryptedKeyShares;
1423
+ }
1424
+ async exportClientKeyshares({ accountAddress, password }) {
1425
+ await this.verifyPassword({
1426
+ accountAddress,
1427
+ password,
1428
+ walletOperation: WalletOperation.REACH_ALL_PARTIES
1429
+ });
1430
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1431
+ accountAddress
1432
+ });
1433
+ if (!accountAddress) {
1434
+ throw new Error('Must provide an account address');
1435
+ }
1436
+ const derivationPath = this.walletMap[accountAddress].derivationPath;
1437
+ const text = JSON.stringify({
1438
+ keyShares: clientKeyShares,
1439
+ derivationPath
1440
+ });
1441
+ const blob = new Blob([
1442
+ text
1443
+ ], {
1444
+ type: 'text/plain'
1445
+ });
1446
+ const url = URL.createObjectURL(blob);
1447
+ const a = document.createElement('a');
1448
+ a.href = url;
1449
+ a.download = `${CLIENT_KEYSHARE_EXPORT_FILENAME_PREFIX}-${accountAddress}.txt`;
1450
+ a.click();
1451
+ }
1452
+ async getClientKeyShares({ accountAddress, password }) {
1453
+ await this.getWallet({
1454
+ accountAddress,
1455
+ password,
1456
+ walletOperation: WalletOperation.REACH_THRESHOLD
1457
+ });
1458
+ return this.getClientKeySharesFromLocalStorage({
1459
+ accountAddress
1460
+ });
1461
+ }
1462
+ /**
1463
+ * Helper function to check if the required wallet fields are present and valid
1464
+ * @param accountAddress - The account address of the wallet to check
1465
+ * @param walletOperation - The wallet operation that determines required fields
1466
+ * @returns boolean indicating if wallet needs to be re-fetched and restored from server
1467
+ */ async checkWalletFields({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD, shareCount }) {
1468
+ let keyshareCheck = false;
1469
+ let walletCheck = false;
1470
+ let thresholdSignatureSchemeCheck = false;
1471
+ let derivationPathCheck = false;
1472
+ // check if wallet exists
1473
+ const existingWallet = this.walletMap[accountAddress];
1474
+ if (existingWallet) {
1475
+ walletCheck = true;
1476
+ }
1477
+ // check if threshold signature scheme exists
1478
+ if (existingWallet == null ? void 0 : existingWallet.thresholdSignatureScheme) {
1479
+ thresholdSignatureSchemeCheck = true;
1480
+ }
1481
+ // check if derivation path exists
1482
+ if ((existingWallet == null ? void 0 : existingWallet.derivationPath) || (existingWallet == null ? void 0 : existingWallet.derivationPath) === '') {
1483
+ derivationPathCheck = true;
1484
+ }
1485
+ // check if wallet already exists with sufficient keyshares
1486
+ if (existingWallet) {
1487
+ const { shares } = this.recoverStrategy({
1488
+ clientKeyShareBackupInfo: existingWallet.clientKeySharesBackupInfo || {
1489
+ backups: getClientKeyShareBackupInfo()
1490
+ },
1491
+ thresholdSignatureScheme: existingWallet.thresholdSignatureScheme,
1492
+ walletOperation,
1493
+ shareCount
1494
+ });
1495
+ const { dynamic: requiredDynamicKeyShareIds = [] } = shares;
1496
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1497
+ accountAddress
1498
+ });
1499
+ if (requiredDynamicKeyShareIds.length <= ((clientKeyShares == null ? void 0 : clientKeyShares.length) || 0)) {
1500
+ keyshareCheck = true;
1501
+ }
1502
+ }
1503
+ return walletCheck && thresholdSignatureSchemeCheck && keyshareCheck && derivationPathCheck;
1504
+ }
1505
+ /**
1506
+ * verifyPassword attempts to recover and decrypt a single client key share using the provided password.
1507
+ * If successful, the key share is encrypted with the new password. This method solely performs the recovery
1508
+ * and decryption without storing the restored key shares. If unsuccessful, it throws an error.
1509
+ */ async verifyPassword({ accountAddress, password = undefined, walletOperation = WalletOperation.NO_OPERATION }) {
1510
+ await this.getWallet({
1511
+ accountAddress,
1512
+ password,
1513
+ walletOperation
1514
+ });
1515
+ if (await this.requiresPasswordForOperation({
1516
+ accountAddress,
1517
+ walletOperation
1518
+ }) && !password) {
1519
+ throw new Error('Password is required for operation but not provided');
1520
+ }
1521
+ // silent return if no password is provided and operation does not require a password
1522
+ if (!password) {
1523
+ return;
1524
+ }
1525
+ const { backups } = await this.getWalletClientKeyShareBackupInfo({
1526
+ accountAddress
1527
+ });
1528
+ const { dynamic: dynamicKeyShareIds = [] } = backups;
1529
+ if (!dynamicKeyShareIds || dynamicKeyShareIds.length === 0) {
1530
+ throw new Error('No dynamic key shares found');
1531
+ }
1532
+ try {
1533
+ await this.recoverEncryptedBackupByWallet({
1534
+ accountAddress,
1535
+ password,
1536
+ walletOperation,
1537
+ shareCount: 1,
1538
+ storeRecoveredShares: false
1539
+ });
1540
+ } catch (error) {
1541
+ this.logger.error('Error in verifying password', error);
1542
+ throw new Error('Incorrect password');
1543
+ }
1544
+ }
1545
+ async isPasswordEncrypted({ accountAddress }) {
1546
+ const clientKeySharesBackupInfo = await this.getWalletClientKeyShareBackupInfo({
1547
+ accountAddress
1548
+ });
1549
+ return clientKeySharesBackupInfo == null ? void 0 : clientKeySharesBackupInfo.passwordEncrypted;
1550
+ }
1551
+ /**
1552
+ * check if the operation requires a password
1553
+ */ async requiresPasswordForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
1554
+ const isEncrypted = await this.isPasswordEncrypted({
1555
+ accountAddress
1556
+ });
1557
+ if (!isEncrypted) {
1558
+ return false;
1559
+ }
1560
+ return this.requiresRestoreBackupSharesForOperation({
1561
+ accountAddress,
1562
+ walletOperation
1563
+ });
1564
+ }
1565
+ /**
1566
+ * check if the operation requires restoring backup shares
1567
+ */ async requiresRestoreBackupSharesForOperation({ accountAddress, walletOperation = WalletOperation.REACH_THRESHOLD }) {
1568
+ const clientKeySharesBackupInfo = await this.getWalletClientKeyShareBackupInfo({
1569
+ accountAddress
1570
+ });
1571
+ const clientKeyShares = await this.getClientKeySharesFromLocalStorage({
1572
+ accountAddress
1573
+ });
1574
+ if (walletOperation === WalletOperation.REACH_ALL_PARTIES || walletOperation === WalletOperation.REFRESH || walletOperation === WalletOperation.RESHARE) {
1575
+ return true;
1576
+ }
1577
+ const { requiredShareCount } = this.recoverStrategy({
1578
+ clientKeyShareBackupInfo: clientKeySharesBackupInfo,
1579
+ thresholdSignatureScheme: this.walletMap[accountAddress].thresholdSignatureScheme,
1580
+ walletOperation
1581
+ });
1582
+ if (clientKeyShares.length >= requiredShareCount) {
1583
+ return false;
1584
+ }
1585
+ return true;
1586
+ }
1587
+ async getWalletClientKeyShareBackupInfo({ accountAddress }) {
1588
+ var _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC, _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups, _this_walletMap_accountAddress_clientKeySharesBackupInfo, _this_walletMap_accountAddress, _user_verifiedCredentials;
1589
+ // Return existing backup info if it exists
1590
+ if (((_this_walletMap_accountAddress = this.walletMap[accountAddress]) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo = _this_walletMap_accountAddress.clientKeySharesBackupInfo) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups = _this_walletMap_accountAddress_clientKeySharesBackupInfo.backups) == null ? void 0 : (_this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC = _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups[BackupLocation.DYNAMIC]) == null ? void 0 : _this_walletMap_accountAddress_clientKeySharesBackupInfo_backups_BackupLocation_DYNAMIC.length) > 0) {
1591
+ return this.walletMap[accountAddress].clientKeySharesBackupInfo;
1592
+ }
1593
+ // Get backup info from server
1594
+ const user = await this.apiClient.getUser();
1595
+ const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
1596
+ return getClientKeyShareBackupInfo({
1597
+ walletProperties: wallet == null ? void 0 : wallet.walletProperties
1598
+ });
1599
+ }
1600
+ async getWallet({ accountAddress, walletOperation = WalletOperation.NO_OPERATION, shareCount = undefined, password = undefined }) {
1601
+ var _user_verifiedCredentials;
1602
+ const existingWalletCheck = await this.checkWalletFields({
1603
+ accountAddress,
1604
+ walletOperation,
1605
+ shareCount
1606
+ });
1607
+ if (existingWalletCheck) {
1608
+ this.logger.debug(`Wallet ${accountAddress} already exists`);
1609
+ return this.walletMap[accountAddress];
1610
+ }
1611
+ // Fetch and restore wallet from server
1612
+ const user = await this.apiClient.getUser();
1613
+ const wallet = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.find((vc)=>vc.address.toLowerCase() === accountAddress.toLowerCase());
1614
+ this.logger.debug('Restoring wallet', wallet);
1615
+ const walletProperties = wallet.walletProperties;
1616
+ this.walletMap[accountAddress] = _extends({}, this.walletMap[accountAddress], {
1617
+ walletId: wallet.id,
1618
+ chainName: wallet.chainName,
1619
+ accountAddress,
1620
+ thresholdSignatureScheme: walletProperties.thresholdSignatureScheme,
1621
+ derivationPath: walletProperties.derivationPath,
1622
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
1623
+ walletProperties
1624
+ })
1625
+ });
1626
+ if (walletOperation !== WalletOperation.NO_OPERATION && await this.requiresRestoreBackupSharesForOperation({
1627
+ accountAddress,
1628
+ walletOperation
1629
+ })) {
1630
+ const decryptedKeyShares = await this.recoverEncryptedBackupByWallet({
1631
+ accountAddress,
1632
+ password: password != null ? password : this.environmentId,
1633
+ walletOperation: walletOperation,
1634
+ shareCount
1635
+ });
1636
+ const existingKeyShares = await this.getClientKeySharesFromLocalStorage({
1637
+ accountAddress
1638
+ });
1639
+ await this.setClientKeySharesToLocalStorage({
1640
+ accountAddress,
1641
+ clientKeyShares: mergeUniqueKeyShares(existingKeyShares, decryptedKeyShares)
1642
+ });
1643
+ this.logger.debug('Recovered backup', decryptedKeyShares);
1644
+ }
1645
+ const walletCount = Object.keys(this.walletMap).length;
1646
+ if (walletCount === 0) {
1647
+ throw new Error('No wallets found');
1648
+ }
1649
+ // Return the only wallet if there's just one
1650
+ if (walletCount === 1) {
1651
+ return Object.values(this.walletMap)[0];
1652
+ }
1653
+ return this.walletMap[accountAddress];
1654
+ }
1655
+ async getWallets() {
1656
+ var _user_verifiedCredentials;
1657
+ const user = await this.apiClient.getUser();
1658
+ const waasWallets = (_user_verifiedCredentials = user.verifiedCredentials) == null ? void 0 : _user_verifiedCredentials.filter((vc)=>vc.walletName === 'dynamicwaas');
1659
+ const wallets = waasWallets.map((vc)=>{
1660
+ var _this_walletMap_vc_address, _vc_walletProperties;
1661
+ var _this_walletMap_vc_address_derivationPath;
1662
+ return {
1663
+ walletId: vc.id,
1664
+ chainName: vc.chain,
1665
+ accountAddress: vc.address,
1666
+ clientKeySharesBackupInfo: getClientKeyShareBackupInfo({
1667
+ walletProperties: vc.walletProperties || {}
1668
+ }),
1669
+ derivationPath: (_this_walletMap_vc_address_derivationPath = (_this_walletMap_vc_address = this.walletMap[vc.address]) == null ? void 0 : _this_walletMap_vc_address.derivationPath) != null ? _this_walletMap_vc_address_derivationPath : undefined,
1670
+ thresholdSignatureScheme: (_vc_walletProperties = vc.walletProperties) == null ? void 0 : _vc_walletProperties.thresholdSignatureScheme
1671
+ };
1672
+ });
1673
+ this.walletMap = wallets.reduce((acc, wallet)=>{
1674
+ var _acc_accountAddress;
1675
+ const accountAddress = wallet.accountAddress;
1676
+ acc[wallet.accountAddress] = {
1677
+ walletId: wallet.walletId,
1678
+ chainName: wallet.chainName,
1679
+ accountAddress: wallet.accountAddress,
1680
+ clientKeySharesBackupInfo: wallet.clientKeySharesBackupInfo,
1681
+ derivationPath: ((_acc_accountAddress = acc[accountAddress]) == null ? void 0 : _acc_accountAddress.derivationPath) || undefined,
1682
+ thresholdSignatureScheme: wallet.thresholdSignatureScheme
1683
+ };
1684
+ return acc;
1685
+ }, {});
1686
+ return wallets;
1687
+ }
1688
+ constructor({ environmentId, authToken, baseApiUrl, baseMPCRelayApiUrl, storageKey, debug }){
1689
+ this.initializePromise = null;
1690
+ this.logger = logger;
1691
+ this.walletMap = {} // todo: store in session storage
1692
+ ;
1693
+ this.iframeStorage = null;
1694
+ this.iframeDisplay = null;
1695
+ this.memoryStorage = null;
1696
+ this.messageTransport = null;
1697
+ this.iframe = null;
1698
+ this.iframeLoadPromise = null;
1699
+ this.environmentId = environmentId;
1700
+ this.storageKey = `${STORAGE_KEY}-${storageKey != null ? storageKey : environmentId}`;
1701
+ this.baseMPCRelayApiUrl = baseMPCRelayApiUrl;
1702
+ this.apiClient = new DynamicApiClient({
1703
+ environmentId,
1704
+ authToken,
1705
+ baseApiUrl
1706
+ });
1707
+ this.debug = Boolean(debug);
1708
+ this.logger.setLogLevel(this.debug ? 'DEBUG' : DEFAULT_LOG_LEVEL);
1709
+ // setup storage
1710
+ if (supportsLocalStorage()) {
1711
+ this.storage = localStorageAdapter;
1712
+ } else {
1713
+ this.memoryStorage = {};
1714
+ this.storage = memoryLocalStorageAdapter(this.memoryStorage);
1715
+ }
1716
+ // TODO: handle prod and preprod iframe domains
1717
+ this.iframeDomain = IFRAME_DOMAIN_PREPROD;
1718
+ // Generate unique instanceId when client is created
1719
+ this.instanceId = crypto.randomUUID();
1720
+ // initialize the client
1721
+ this.initialize();
1722
+ }
1723
+ }
1724
+
1725
+ export { DynamicWalletClient, base64ToBytes, bytesToBase64, ensureBase64Padding, getClientKeyShareBackupInfo, getClientKeyShareExportFileName, getMPCSignatureScheme, getMPCSigner, isBrowser, isHexString, mergeUniqueKeyShares, retryPromise, stringToBytes, timeoutPromise };